我在移植到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
默认情况下,GCC 编译器启用 C扩展(即使-pedantic
标志有效),它允许跨翻译单元对对象进行多个外部定义。
参考C11 (N1570) J.5.11多个外部定义(信息部分):
标识符可能有多个外部定义 一个对象,无论是否显式使用关键字
extern
;如果 定义不一致,或初始化了多个定义, 行为未定义 (6.9.2)。
请注意,依赖于此行为的应用程序并不严格符合 ISO C 语言。更具体地说,C11 6.9/p5外部定义状态(强调我的):
外部定义是一个外部声明,它也是一个 函数的定义(内联定义除外)或 对象。如果在 表达式(作为
sizeof
或_Alignof
的操作数的一部分除外) 结果为整数常量的运算符),在整个 程序应该只有一个外部定义 标识符;否则,不得超过一个。161)
从技术上讲,违反该规则会调用未定义的行为,这意味着实现可能会也可能不会发出诊断消息。
您可以检查是否已通过以下命令启用此扩展nm
:
nm test.o
0000000000000000 T foo
0000000000000004 C _TestEnum
根据man nm
:
"C"符号是通用的。 常用符号是未初始化的数据。 链接时,可能会出现多个具有相同名称的常用符号。 如果符号在任意位置定义,则常用符号被视为 未定义的引用。
为了禁用此扩展,您可以使用-fno-common
标志。来自 GCC 文档:
Unix C 编译器传统上为 公共块中未初始化的全局变量。这允许 链接器,用于解析同一变量的所有暂定定义 同一对象或非暂定对象的不同编译单元 定义。这是
-fcommon
指定的行为,并且是 大多数目标上的 GCC 默认值。