C在不同文件中定义的相同全局变量



我从这里阅读这个代码(中文)。在c中有一段测试全局变量的代码,变量a在文件t.h中被定义,该文件被包含了两次。在文件foo.c中定义了一个具有一定值的struct b和一个main函数。在main.c文件中,定义了两个未初始化的变量。

/* t.h */
#ifndef _H_
#define _H_
int a;
#endif
/* foo.c */
#include <stdio.h>
#include "t.h"
struct {
char a;
int b;
} b = { 2, 4 };
int main();
void foo()
{
printf("foo:t(&a)=0x%08xnt(&b)=0x%08xn
tsizeof(b)=%dntb.a=%dntb.b=%dntmain:0x%08xn",
&a, &b, sizeof b, b.a, b.b, main);
}
/* main.c */
#include <stdio.h>
#include "t.h"
int b;
int c;
int main()
{
foo();
printf("main:t(&a)=0x%08xnt(&b)=0x%08xn
t(&c)=0x%08xntsize(b)=%dntb=%dntc=%dn",
&a, &b, &c, sizeof b, b, c);
return 0;
}

使用Ubuntu GCC 4.4.3编译后,结果如下:

foo:    (&a)=0x0804a024
(&b)=0x0804a014
sizeof(b)=8
b.a=2
b.b=4
main:0x080483e4
main:   (&a)=0x0804a024
(&b)=0x0804a014
(&c)=0x0804a028
size(b)=4
b=2
c=0

变量ab在两个函数中地址相同,但b的大小发生了变化。我不明白它是怎么工作的!

你违反了C语言的"一个定义规则",结果是未定义的行为。"一个定义规则"在标准中没有正式说明。我们正在查看不同源文件中的对象(也就是翻译单元),因此我们关注"外部定义"。"一个外部定义"语义被详细说明(C11 6.9 p5):

外部定义是一个外部声明,它也是函数(而不是内联定义)或对象的定义。如果用外部链接声明的标识符在表达式中使用(除了作为sizeof_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的一个常见扩展是允许多个外部定义,并在C标准的信息附录J.5 (C11 J.5.11)中进行了描述:

对象的标识符可以有多个外部定义,带或没有显式使用关键字extern;如果定义不一致行为未定义(6.9.2)。

(重点是我的。)因为a的定义是一致的,所以没有伤害,但是b的定义不一致。这个扩展解释了为什么你的编译器不会抱怨多个定义的存在。根据C11 6.2.2的引用,链接器将尝试协调对同一对象的多个引用。

链接器通常使用两种模型中的一种来协调多个翻译单元中同一符号的多个定义。这些是"通用模型"one_answers"Ref/Def模型"。在"通用模型"中,以union样式的方式将具有相同名称的多个对象折叠成一个对象,使该对象具有最大定义的大小。在"Ref/Def模型"中,每个外部名称必须只有一个定义。

GNU工具链默认使用"公共模型"one_answers"宽松Ref/Def模型",它对单个翻译单元严格执行一个定义规则,但不会抱怨多个翻译单元的违规。

使用-fno-common选项可以在GNU编译器中抑制"Common Model"。当我在我的系统上进行测试时,它会导致类似于您的代码的"严格Ref/Def模型"行为:

$ cat a.c
#include <stdio.h>
int a;
struct { char a; int b; } b = { 2, 4 };
void foo () { printf("%zun", sizeof(b)); }
$ cat b.c
#include <stdio.h>
extern void foo();
int a, b;
int main () { printf("%zun", 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外部变量模型文档

在形式上,使用外部链接多次定义相同的变量(或函数)是非法的。因此,从形式化的角度来看,程序的行为是未定义的。

实际上,允许具有外部链接的同一变量的多个定义是一种流行的编译器扩展(一种在语言规范中提到的公共扩展)。但是,为了正确使用,每个定义都应该用相同的类型声明它。包含初始化项的定义不能超过一个。

您的案例与公共扩展描述不匹配。您的代码被编译为该公共扩展的副作用,但其行为仍然未定义。

这段代码似乎故意打破了单定义规则。它会调用未定义的行为,不要这样做。

关于全局变量a:不要将全局变量的定义放在头文件中,因为它将包含在多个。c文件中,并导致多次定义。只需将声明放在头文件中,并将定义放在。c文件中。

在t.h:

extern int a;

在foo.c

int a;

关于全局变量b:不要多次定义它,使用static在一个文件中限制变量

在foo.c:

static struct {
char a;
int b;
} b = { 2, 4 };

c

static int b;

b有相同的地址,因为链接器决定为你解决冲突。

sizeof显示不同的值,因为sizeof在编译时求值. 在这个阶段,编译器只知道一个b(在当前文件中定义的那个)。

在编译foo时,范围内的b是两个int向量{2, 4}或当sizeof(int)为4时为8字节。当编译main时,b刚刚被重新声明为int,所以大小为4是有意义的。此外,可能还有"填充字节"添加到结构体"a"之后,以便下一个槽(int)在4字节边界上对齐。

a和b有相同的地址,因为它们出现在文件中的相同点上。b大小不同的事实与变量从哪里开始无关。如果您在其中一个文件的a和b之间添加了一个变量c,则b的地址将不同。

相关内容

  • 没有找到相关文章

最新更新