【问题标题】:Enum storage differences in C vs. C++C 与 C++ 中的枚举存储差异
【发布时间】:2018-01-06 10:09:36
【问题描述】:

我在一个移植到 C++ 的 C 项目中遇到了以下构造;

enum TestEnum 
{
    A=303,
    B=808
} _TestEnum;

int foo()
{
  _TestEnum = B;
}

当使用 GCC 编译并查看生成的代码时,我得到:

nils@doofnase ~ $ gcc -std=c90 -O2 -c ./test.c -o test.o
nils@doofnase ~ $ size test.o
   text    data     bss     dec     hex filename
     59       0       0      59      3b test.o

因此使用了零字节的数据或 BSS 段。

另一方面,如果我用 C++ 编译,我会得到:

nils@doofnase ~ $ g++ -std=c++11 -O2 -c ./test.c -o test.o
nils@doofnase ~ $ size test.o
   text    data     bss     dec     hex filename
     59       0       4      63      3f test.o

我看到在 BSS 中分配了四个字节的存储空间,正如我所期望的那样。

此外,在 C 项目中,枚举定义实际上位于一个头文件中,该头文件包含在多个 c 文件中。该项目编译和链接就好了。当编译和链接为 C++ 时,编译器会抱怨 _TestEnum 是在多个对象中定义的(没错!)。

这里发生了什么?我在看一些古老的 C 语言特例吗?

编辑:为了完整起见,这是 gcc 版本:

nils@doofnase ~ $ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609

【问题讨论】:

  • With -O2 我希望编译器在 C 和 C++ 中都内联 foo() 的代码;) 此外,您应该在 C 和 C++ 中收到 foo() 的警告返回类型 int 并且不返回值。也许代码 sn-p 太小而无法知道应该发生什么......因为它很可能取决于代码的其余部分。
  • 是的,请修复代码,并添加-Wall
  • 请注意,与正则表达式 ^_[A-Z] 匹配的标识符(如您的 _TestEnum)在 C 和 C++ 中都保留,并且引入它们会导致未定义的行为(有关详细信息,请参阅 this post)。

标签: c++ c enums


【解决方案1】:

默认情况下,GCC 编译器启用 C 扩展(即使 -pedantic 标志有效),它允许跨 translation units 的对象的多个外部定义。

参考 C11 (N1570) J.5.11 多个外部定义(信息部分):

标识符的外部定义可能不止一个 一个对象,无论是否明确使用关键字extern;如果 定义不一致,或者不止一个被初始化, 行为未定义 (6.9.2)。

请注意,依赖此行为的应用程序并不严格符合 ISO C 语言。更具体地说,C11 6.9/p5 外部定义声明(强调我的):

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

从技术上讲,违反该规则会调用undefined behavior,这意味着实现可能会或可能不会发出诊断消息。

您可以检查,此扩展已被nm 命令启用:

nm test.o 
0000000000000000 T foo
0000000000000004 C _TestEnum

根据man nm

“C”这个符号很常见。常用符号是未初始化的数据。 链接时,可能会出现多个同名的常用符号。 如果符号在任何地方定义,则常用符号被视为 未定义的引用。

为了禁用此扩展,您可以使用-fno-common 标志。来自 GCC 文档:

Unix C 编译器传统上为 公共块中未初始化的全局变量。这允许 链接器来解析相同变量的所有暂定定义 不同的编译单元到同一个对象,或者到一个非暂定的 定义。这是-fcommon 指定的行为,并且是 GCC 在大多数目标上的默认设置。

【讨论】:

  • 就是这样!非常感谢。
  • 我不同意说“这种行为是不允许的”,因为显然允许实现支持多个外部定义。你说它听起来像 GCC 的方式违反了标准。否则,很好的答案。
  • @trentcl:好点。我将措辞改为“不严格符合”,因为它正式调用了 UB。
  • 这种行为是严格符合的。除其他外,未定义的行为意味着实现不必发出诊断,并且无论实现做什么都完全符合标准。那个“随便”的范围可以从鼻恶魔到给 UB 一个明确的行为。
  • @DavidHammen:我同意。我认为关键是依赖于UB的应用程序不是严格符合的,而不是实现。在这种情况下,我基于 C11 4/p5 一致性。
猜你喜欢
  • 1970-01-01
  • 2014-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-26
  • 2017-09-02
  • 2012-04-30
  • 1970-01-01
相关资源
最近更新 更多