【问题标题】:What is the difference between the global variables in C and C++?C和C++中的全局变量有什么区别?
【发布时间】:2013-08-31 03:39:33
【问题描述】:

我已经测试了以下代码:

在文件 a.c/a.cpp

int a;

在文件 b.c/b.cpp

int a;
int main() { return 0; }

当我用gcc *.c -o test编译源文件时,它成功了。

但是当我用 g++ *.c -o test 编译源文件时,它失败了:

ccIJdJPe.o:b.cpp:(.bss+0x0): multiple definition of 'a'
ccOSsV4n.o:a.cpp:(.bss+0x0): first defined here
collect2.exe: error: ld returned 1 exit status

我真的很困惑。 C和C++中的全局变量有什么区别吗?

【问题讨论】:

  • C++:将extern int a;放在头文件中,在源文件中只声明一次int a;。否则,您将创建两个具有相同名称和范围的不同变量,这是链接器错误。
  • 只需在文件b.c/b.cpp 中声明extern int a 而不是int a 就可以了。
  • 我不明白为什么您没有从 C 中得到链接器错误。您基本上是在尝试声明两个具有相同名称的单独全局变量。跨度>
  • @NeilKirk 是的,我知道如何纠正它。但我对 C 和 C++ 中的不同行为感到困惑。

标签: c++ c gcc global-variables


【解决方案1】:

以下是标准的相关部分。请参阅标准文本下方的说明:

§6.9.2/2 外部对象定义

具有文件范围但没有初始化程序、没有存储类说明符或具有存储类说明符 static 的对象的标识符声明构成暂定定义。如果翻译单元包含一个或多个标识符的暂定定义,并且翻译单元不包含该标识符的外部定义,则行为与翻译单元包含该标识符的文件范围声明完全相同,复合类型为翻译单元的末尾,初始化器等于 0。

ISO C99 §6.9/5 外部定义

外部定义是一个外部声明,它也是函数(内联定义除外)或对象的定义。如果用外部链接声明的标识符在表达式中使用(而不是作为结果为整数常量的 sizeof 运算符的操作数的一部分),则在整个程序的某处,该标识符应有一个外部定义;否则,不得超过一个。

在 C 版本中,“g”全局变量被“合并”为一个,因此最终您将只有一个被声明两次。这没关系,因为当时不需要 extern,或者可能没有退出。因此,这是出于历史和兼容性原因构建旧代码。这是此旧功能的 gcc 扩展。

它基本上让 gcc 为名为“a”的变量分配内存,因此可以有多个声明,但只有一个定义。这就是为什么下面的代码即使使用 gcc 也无法工作的原因。

这也称为暂定定义。 C++ 没有这样的东西,那就是在它编译的时候。 C++没有临时声明的概念。

暂定定义是任何没有存储类说明符和初始化程序的外部数据声明。如果到达翻译单元的末尾并且没有出现带有标识符的初始值设定项的定义,则暂定定义将成为完整定义。在这种情况下,编译器会为定义的对象保留未初始化的空间。

但是请注意,即使使用 gcc,以下代码也无法编译,因为这不再是带有赋值的暂定定义/声明:

在文件“a.c/a.cpp”中

int a = 1;

在文件“b.c/b.cpp”中

int a = 2;
int main() { return 0; }

让我们用更多的例子来超越这个。以下陈述显示了正常定义和暂定定义。请注意,静态会有所不同,因为那是文件范围,不再是外部的。

int i1 = 10;         /* definition, external linkage */
static int i2 = 20;  /* definition, internal linkage */
extern int i3 = 30;  /* definition, external linkage */
int i4;              /* tentative definition, external linkage */
static int i5;       /* tentative definition, internal linkage */
 
int i1;              /* valid tentative definition */
int i2;              /* not legal, linkage disagreement with previous */
int i3;              /* valid tentative definition */
int i4;              /* valid tentative definition */
int i5;              /* not legal, linkage disagreement with previous */

更多详情可在以下页面:

http://c0x.coding-guidelines.com/6.9.2.html

有关详细信息,另请参阅此博客文章:

http://ninjalj.blogspot.co.uk/2011/10/tentative-definitions-in-c.html

【讨论】:

  • 正如您的引述所说,暂定声明与在翻译单元末尾的完整声明完全相同。这意味着每个翻译单元仍然包含a 的定义,并且程序仍然存在错误,因为它违反了 6.9/5。 gcc 只接受代码,因为它实现了标准的扩展。
  • @Charles:谢谢,我对我的版本进行了小幅更新。您仍然缺乏改进这个答案的东西吗?
  • 是的,你发表了评论,我修复了它。很多人浏览 google 都会看到这个,还有其他建议吗?
  • 我认为答案具有误导性。您引用并谈论了暂定定义,但在这种情况下它们是转移注意力。只有当多个明显的定义在翻译中单元是合法的时,一个暂定的定义才有意义。问题的重点是为什么当单独的翻译单元中有多个定义时,C 版本似乎会链接。就个人而言,我认为您应该更清楚代码有误。
  • @Charles:在我看来,这不是误导,也不是红鲱鱼。我个人认为,全面了解情况是值得的。这就是为什么我认为有些人不会简单地理解您的答案的原因之一。太简洁了。
【解决方案2】:

gcc 实现了一个遗留功能,将未初始化的全局变量放置在一个公共块中。

虽然在每个翻译单元中的定义都是暂定的,但在 ISO C 中,在翻译单元的末尾,如果暂定定义尚未合并到非暂定定义中,则它们会“升级”为完整定义。

在标准 C 中,在多个翻译单元中定义具有外部链接的相同变量总是不正确的,即使这些定义来自暂定定义。

要获得与 C++ 相同的行为,您可以将 -fno-common 开关与 gcc 一起使用,这将导致相同的错误。 (如果您使用 GNU 链接器并且不使用 -fno-common,您可能还需要考虑使用 --warn-common / -Wl,--warn-common 选项来突出在遇到具有相同符号的多个常见和非常见符号时的链接时间行为名字。)

来自 gcc 手册页:

-fno-common

在 C 代码中,控制未初始化全局的位置 变量。 Unix C 编译器传统上允许多个 这些变量在不同编译单元中的定义 将变量放在一个公共块中。这是行为 由-fcommon 指定,并且是大多数 GCC 的默认值 目标。另一方面,ISO 不要求这种行为 C,并且在某些目标上可能会带来速度或代码大小的惩罚 变量引用。 -fno-common 选项指定 编译器应该在数据中放置未初始化的全局变量 目标文件的部分,而不是像普通一样生成它们 块。其效果是,如果声明了相同的变量 (没有extern)在两个不同的编译中,你会得到一个 链接它们时出现多定义错误。在这种情况下,您 必须使用 -fcommon 编译。编译 -fno-common 对于它提供更好的目标很有用 性能,或者如果您想验证程序是否可以工作 在总是处理未初始化变量的其他系统上 以这种方式声明。

gcc 的行为是一种常见的行为,它在标准的附件 J(不是规范性的)中进行了描述,该标准描述了对该标准的常用扩展:

J.5.11 多个外部定义

一个对象的标识符可能有多个外部定义,带有或 没有明确使用关键字extern;如果定义不一致,或超过 一个已初始化,行为未定义(6.9.2)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-23
    • 2016-01-17
    • 2012-12-12
    • 2011-01-04
    • 1970-01-01
    相关资源
    最近更新 更多