在 C 代码的编译或链接过程中的什么时候隐式定义了外部变量?

At what point during compilation or linking of C code are extern variables implicitly defined?

如果我的项目在同一目录下有以下3个文件:

mylib.h:

int some_global;
void set_some_global(int value);

mylib.c:

#include "mylib.h"
void set_some_global(int value)
{
    some_global = value;
}

main.c:

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

int main()
{
    set_some_global(42);
    printf("Some global: %d\n", some_global);
    return 0;
}

我用

编译
gcc main.c mylib.c -o prog -Wall -Wpedantic

我没有收到任何错误或警告,prog 程序将 42 打印到控制台。

当我第一次尝试这个时,我预计会出现“多重定义”错误或某种警告,因为 some_global 未在头文件中声明 extern。在研究这个问题后,我发现在 C 语言中 extern 隐含在函数外的变量声明中(并且 C++ 的情况恰恰相反,这可以通过使用 g++ 而不是 [= 来证明) 20=] 在上面的编译行中)。此外,如果我将 mylib.h 中的行从声明更改为定义(例如 int some_global = 1;),我 do 会得到我预期的“多重定义”错误(这并不令人震惊)。

我的主要问题是:在哪里定义变量?它似乎在某处某处隐式定义,但编译器或链接器在什么时候意识到它需要定义该变量并这样做了?

此外,如果我在 mylib.h 文件中将变量显式声明为 extern,为什么会出现“未定义引用”错误,除非我在一个且唯一的一个文件中显式声明该变量*.c?我希望考虑到上面代码工作的原因(extern 是隐式的),明确声明 extern 不会有什么不同。为什么行为会有所不同?


跟进

在下面的答案更正我 mylib.h 中的代码是“暂定定义”而不是声明后,我发现了这个相关答案,其中包含有关此类问题的更多详细信息:

  1. 你的代码编译和 links 没有错误只是因为你使用 gcc 是用 -fcommon 命令行选项编译的 ” -fcommon 将未初始化的全局变量放在公共块中。这允许 linker 将不同编译单元中同一变量的所有暂定定义解析为同一对象,或非暂定定义。(... ) 它主要用于启用遗留代码 link 而不会出错。" 这是版本 10 之前的默认设置,但即使现在许多工具链仍在构建时启用此选项。

  2. 永远不要在头文件中定义数据。在头文件中仅放置 extern 变量定义。

应该是:

extern int some_global;
void set_some_global(int value);

mylib.c:

#include "mylib.h"

int some_global;

void set_some_global(int value)
{
    some_global = value;
}

main.c:

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

int main()
{
    set_some_global(42);
    printf("Some global: %d\n", some_global);
    return 0;
}

int some_global;为暂定定义。在版本 10 之前的 GCC 中,GCC 生成了一个目标文件,将其视为一个公共符号。 (此行为仍然可以通过开关选择,-fcommon。)链接器将一个公共符号的多个定义合并为一个定义。