您违反了 C 的“一个定义规则”,结果是未定义的行为。“单一定义规则”并未在标准中正式声明。我们正在查看不同源文件(又名翻译单元)中的对象,因此我们关注“外部定义”。详细说明了“一个外部定义”语义(C11 6.9 p5):
外部定义是一个外部声明,它也是函数(内联定义除外)或对象的定义。如果用外部链接声明的标识符在表达式中使用(而不是作为sizeof
or_Alignof
运算符的操作数的一部分,其结果是一个整数常量),则在整个程序的某处,该标识符应该只有一个外部定义;否则,不得超过一个。
这基本上意味着您最多只能定义一个对象一次。(如果外部对象从未在程序中的任何地方使用,则 else 子句允许您根本不定义外部对象。)
请注意,您有两个外部定义b
。一个是您在 中初始化的结构,foo.c
另一个是 中的暂定定义,main.c
(C11 6.9.2 p1-2):
如果对象标识符的声明具有文件范围和初始化程序,则该声明是标识符的外部定义。
具有文件范围的对象的标识符声明没有初始化程序,并且没有存储类说明符或具有存储类说明符static
,构成一个暂定定义。如果翻译单元包含一个或多个标识符的暂定定义,并且翻译单元不包含该标识符的外部定义,则行为与翻译单元包含该标识符的文件范围声明完全相同,复合类型为翻译单元的末尾,初始化器等于 0。
所以你有多个b
. 但是,还有另一个错误,因为您定义b
了不同的类型。首先请注意,允许对具有外部链接的同一对象进行多个声明。但是,当在两个不同的源文件中使用相同的名称时,该名称指的是同一个对象 (C11 6.2.2 p2):
在构成整个程序的一组翻译单元和库中,具有外部链接的特定标识符的每个声明都表示相同的对象或函数。
C 严格限制对同一对象的声明(C11 6.2.7 p2):
所有引用相同对象或函数的声明都应具有兼容的类型;否则,行为未定义。
由于b
每个源文件中的类型实际上并不匹配,因此行为未定义。(构成兼容类型的内容在所有 C11 6.2.7 中都有详细描述,但基本上归结为类型必须匹配。)
所以你有两个失败b
:
从技术上讲,您int a
在两个源文件中的声明也违反了“一个定义规则”。请注意,a
具有外部链接(C11 6.2.2 p5):
如果对象标识符的声明具有文件范围且没有存储类说明符,则其链接是外部的。
但是,从前面 C11 6.9.2 的引用来看,这些int a
暂定定义是外部定义,您只能在顶部引用 C11 6.9 中的其中一个。
通常的免责声明适用于未定义的行为。任何事情都可能发生,这将包括您观察到的行为。
C 的一个常见扩展是允许多个外部定义,并在信息性附录 J.5 (C11 J.5.11) 中的 C 标准中进行了描述:
一个对象的标识符可能有多个外部定义,无论是否显式使用关键字extern
; 如果定义不一致,或者初始化了多个,则行为未定义(6.9.2)。
(重点是我的。)由于a
同意的定义,那里没有害处,但b
不同意的定义。这个扩展解释了为什么你的编译器不会抱怨存在多个定义。根据 C11 6.2.2 的引用,链接器将尝试协调对同一对象的多个引用。
链接器通常使用两种模型之一来协调多个翻译单元中同一符号的多个定义。这些是“通用模型”和“参考/定义模型”。在“通用模型”中,多个同名对象以一种union
样式的方式折叠成一个对象,使对象具有最大定义的大小。在“Ref/Def 模型”中,每个外部名称必须只有一个定义。
GNU 工具链默认使用“通用模型”和“宽松的 Ref/Def 模型”,它对单个翻译单元执行严格的定义规则,但不会抱怨跨多个翻译单元的违规行为。
使用该-fno-common
选项可以在 GNU 编译器中抑制“通用模型”。当我在我的系统上对此进行测试时,它导致类似于您的代码的“严格参考/定义模型”行为:
$ cat a.c
#include <stdio.h>
int a;
struct { char a; int b; } b = { 2, 4 };
void foo () { printf("%zu\n", sizeof(b)); }
$ cat b.c
#include <stdio.h>
extern void foo();
int a, b;
int main () { printf("%zu\n", sizeof(b)); foo(); }
$ gcc -fno-common a.c b.c
/tmp/ccd4fSOL.o:(.bss+0x0): multiple definition of `a'
/tmp/ccMoQ72v.o:(.bss+0x0): first defined here
/tmp/ccd4fSOL.o:(.bss+0x4): multiple definition of `b'
/tmp/ccMoQ72v.o:(.data+0x0): first defined here
/usr/bin/ld: Warning: size of symbol `b' changed from 8 in /tmp/ccMoQ72v.o to 4 in /tmp/ccd4fSOL.o
collect2: ld returned 1 exit status
$
我个人认为,无论多对象定义的解析模型如何,都应该始终提供链接器发出的最后一个警告,但这既不是这里也不是那里。
参考资料:
很遗憾,我不能给你我的 C11 标准副本的链接C 中的变量是
什么?extern
“链接器初学者指南”
关于外部变量模型的 SAS 文档