为什么我可以在 C 中定义一个变量两次?

Why can I define a variable twice in C?

我一直在测试全局变量,定义和声明,我在这种情况下停止了:

main.c:

#include "stdio.h"

void func(void);
int a;

int main(void) {
    a = 20;
    printf("in main: %d\n", a);
    func();
    
    return 0;
}

add.c:

#include <stdio.h>

void func(void);
int a;

void func() {
    printf("in add: %d\n", a);
}

所以在 C 行

int a;

表示既声明又定义,但是我们知道不允许多次定义一个变量。那么,如果我们有两个 a 的定义和两个声明,为什么这段代码可以编译呢? 我在 CLion 中工作,当我在 main 中的 a 上按“转到 Definition/Declaration”时,它会将指针移动到 add.c 中的 a,当我这样做时在 add.c 中,它移回 main.c,所以我不明白这里发生了什么。

在任何函数之外,int x; 是一个 暂定定义 ,一些编译器和链接器将它们视为一种“合作定义”,其中标识符可以在多个文件中以这种方式声明,将导致只定义一个对象。

由于历史原因,C 的外部声明(函数外的声明)规则有点复杂——C 是随着不同的人开发和试验而成长的,而不是根据我们今天所掌握的知识设计的。

定义: 在函数之外 int x = 3; 是一个定义。它声明标识符 x 并为 int 保留内存,并将 int 初始化为 3.

声明: extern int x;是声明而不是定义。它声明标识符 x 但不为其保留内存。

这两个声明都提供了 x 外部链接。这意味着,当它们出现在不同的源文件中时,标识符的两个实例将被链接以引用内存中的同一事物。

C 标准说“应该有”最多一个带有外部链接的标识符的定义 (C 2018 6.9 5)。 (如果标识符在程序中使用,则必须有定义。如果不在表达式中使用,则不需要定义。)

暂定定义:int x;为杂种。它是一种特殊的可能定义,称为 暂定定义 。 C 标准规定,如果翻译单元(正在编译的源文件,以及它包含的所有文件)中有暂定定义,但没有正则定义,则暂定定义成为正则定义。

现在,如果你违反了“最多应该有”一个定义的规则,会发生什么?事情是这样的:这不是程序必须遵守的规则。当 C 标准说“应该”时,它的意思是,如果一个程序遵守这条规则,那么行为就会像 C 标准所说的那样。如果一个程序不遵守这个规则,C 标准没有定义行为(C 2018 4 2)。相反,我们让编译器和链接器定义行为。

当程序违反最多一个定义的规则时,编译器和链接器的常见行为是:

  • 链接时如果有多个正则定义,报错
  • 如果有多个暂定定义但只有零个或一个常规定义,则将它们合并为一个定义。

这是 GCC 版本 10 之前的 GCC 和相关工具中定义的默认行为,并在 C 2018 标准的 J.5.11 中关于通用扩展的信息部分中明确提及。在当前版本的 GCC 中,任何类型的多个定义在默认情况下都被视为错误。您可以使用命令行开关 -fcommon.

请求旧行为