为什么我可以在 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
.
请求旧行为
我一直在测试全局变量,定义和声明,我在这种情况下停止了:
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
.