场景:
$ cat t0.c t1.c
/* t0.c */
int i = 12;
/* t1.c */
int i INIT;
int main(void)
{
return 0;
}
$ gcc t0.c t1.c -DINIT="" -std=c11 -pedantic
<nothing>
$ gcc t0.c t1.c -DINIT="=0" -std=c11 -pedantic
ld: /tmp/ccrTTgwH.o:(.bss+0x0): multiple definition of `i'; /tmp/cckd6R4u.o:(.data+0x0): first defined here
collect2: error: ld returned 1 exit status
为什么=0
很重要?
UPD:
dvl-linux64 $ gcc82 --version
gcc-8.2 (GCC) 8.2.0
尝试gcc 11.2.0
:
$ gcc t0.c t1.c -DINIT="" -std=c11 -pedantic
ld: /tmp/ccmPBPUT.o:t1.c:(.bss+0x0): multiple definition of `i';
$ gcc t0.c t1.c -DINIT="=0" -std=c11 -pedantic
ld: /tmp/ccxw378s.o:t1.c:(.bss+0x0): multiple definition of `i';
应为。
最终表格:
compiler -DINIT="" leads to multiple definition of `i'?
gcc 8.2.0 NO
gcc 11.2.0 YES
clang 8.0.1 NO
clang 13.0.0 YES
就C标准而言,两者都是无效的,因为它们构成了一个标识符的多个外部定义。这实际上是未定义的行为,但是大多数链接器在这种情况下都会抛出错误。
在t1.c中的int i;
的情况下,这被认为是一个暂定定义,因为它没有初始值设定项。然而,如果在同一翻译单元中没有其他定义,则该暂定定义被视为完整定义。
这在C标准的第6.9.2p2节中进行了描述:
具有文件作用域的对象的标识符声明不带初始值设定项,不带存储类说明符或存储类说明符static构成一个临时定义。如果翻译单元包含一个或多个暂定单元标识符的定义,并且翻译单元不包含该标识符的外部定义,那么行为就是就好像翻译单元包含标识符,在翻译结束时具有复合类型单元,初始值设定项等于0。
然而,一些编译器(如gcc(将采用一个暂定的定义和"加入";它与其他对象文件中的实际定义。一旦在多个对象文件中有了完整的定义(即使用初始化器((即使初始化器相同(,它就会生成多定义错误。
-fcommon
选项启用此行为,-fno-common
在gcc中禁用此行为,尽管默认值取决于版本。