为什么C头文件中的全局变量定义有效?

从我在其他地方看到的许多 stackoverflow 问题中,定义的方法globals是将它们定义在一个.c文件中,然后将其声明为extern头文件,然后将其包含在所需的.c文件中。

然而,今天我在头文件中的代码库全局变量定义中看到,我开始争论,但他坚持认为它会起作用。现在,我不知道为什么,所以我创建了一个小项目来快速测试它:

交流电

#include <stdio.h>
#include "a.h"

int main()
{
    p1.x = 5;
    p1.x = 4;
    com = 6;
    change();
    printf("p1 = %d, %dncom = %dn", p1.x, p1.y, com);
    return 0;
}

公元前

#include "a.h"

void change(void)
{
    p1.x = 7;
    p1.y = 9;
    com = 1;
}

typedef struct coord{
    int x;
    int y;
} coord;

coord p1;
int com;

void change(void);

生成文件

all:
    gcc -c a.c -o a.o
    gcc -c b.c -o b.o
    gcc a.o b.o -o run.out

clean:
    rm a.o b.o run.out

输出

p1 = 7, 9
com = 1

这是如何工作的?这是我设置测试的方式的产物吗?是不是较新的gcc人设法抓住了这种情况?还是我对整件事的解释完全错误?请帮忙...

回答

这依赖于所谓的“通用符号”,它是标准 C 的暂定定义概念的扩展(https://port70.net/~nsz/c/c11/n1570.html#6.9.2p2),除了大多数 UNIX 链接器使它也可以跨翻译单元工作(许多甚至共享动态库)

AFAIK,该功能几乎永远存在,它与 fortran 兼容性/相似性有关。

它的工作原理是编译器将未初始化的(暂定的)全局变量放在一个特殊的“通用”类别中(在nm实用程序中显示为"C",代表“通用”)。

数据符号类别示例:

  #!/bin/sh -eu
(
cat <<EOF
int common_symbol; //C
int zero_init_symbol = 0; //B
int data_init_symbol = 4; //D
const int const_symbol = 4; //R
EOF
) | gcc -xc - -c -o data_symbol_types.o
nm data_symbol_types.o

输出:

0000000000000004 C common_symbol
0000000000000000 R const_symbol
0000000000000000 D data_init_symbol
0000000000000000 B zero_init_symbol

每当链接器看到特定符号的多个重定义时,它通常会生成链接器错误。

但是当这些重新定义属于公共类别时,链接器会将它们合并为一个。此外,如果特定符号有 N-1 个通用定义和一个非暂定定义(在 R、D 或 B 类别中),则所有定义都将合并到一个非暂定定义中,并且不会产生错误。

在其他情况下,您会遇到符号重新定义错误。

尽管通用符号得到了广泛的支持,但它们在技术上不是标准的 C,并且依赖它们在理论上是未定义的行为(尽管在实践中它经常有效)。

clang和 tinycc,据我所知,不会生成通用符号(在那里你应该得到一个重新定义错误)。On gcc,可以使用 禁用通用符号生成-fno-common

(伊恩·兰斯·泰勒 (Ian Lance Taylor) 关于链接器的系列有更多关于通用符号的信息,它还提到链接器如何允许合并不同大小的通用符号,使用最终对象的最大大小:https : //www.airs.com/blog/archives/ 42 . 我相信这个奇怪的技巧曾经被 libc 使用过以达到某种效果)


以上是为什么C头文件中的全局变量定义有效?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>