C与C++枚举存储差异解析:移植代码时的编译链接异常
咱们来一步步拆解你遇到的这个跨语言编译的坑,这确实和C的一个古老特性直接相关。
先明确你遇到的核心现象
- 使用GCC以
-std=c90 -O2编译C代码:生成的目标文件中data和BSS段占用均为0,且头文件被多个C文件包含时能正常编译链接 - 使用g++以
-std=c++11 -O2编译:目标文件的BSS段占用4字节,且多文件包含头文件时会报_TestEnum重复定义错误
为什么会出现这些差异?
1. C语言中的「暂定定义(tentative definition)」特性
你写的这段代码:
enum TestEnum { A=303, B=808 } _TestEnum;
在C90标准里,这是同时完成了枚举类型的声明和一个全局变量的暂定定义。
C的这个古老特性规定:如果全局变量的声明没有初始化器,也没有用extern修饰,就属于「暂定定义」。当多个编译单元(.c文件)都包含这个头文件时,每个编译单元里的_TestEnum都是暂定定义,链接器会自动把这些暂定定义合并成一个单一的全局变量定义,不会报重复定义错误。
另外,你看到C编译后data和BSS段占用为0,是因为_TestEnum只有赋值操作但没有被读取,在-O2优化等级下,GCC会把这个“只写不读”的全局变量直接优化掉,所以不会占用任何内存段空间。
2. C++对全局变量的处理规则
C++从诞生起就没有「暂定定义」这个特性。头文件里的_TestEnum会被每个包含它的.cpp文件当成一个独立的全局变量定义,所以链接时多个编译单元的定义冲突,就会报重复定义错误。
而BSS段占用4字节,是因为C标准要求全局变量默认零初始化,这个未显式初始化的_TestEnum会被分配到BSS段(存储未初始化全局变量的区域),且枚举类型在C中默认大小和int一致(4字节),所以会占用4字节空间,而且C++编译器对这类全局变量的优化更保守,不会轻易把它删掉。
这是不是C的古老特性?
没错,这就是C90标准里的「暂定定义」特性,这个特性在C99甚至C11标准里都被保留了,但C始终没有引入这个规则,这也是C和C在链接规则上的一个经典差异点。
兼容C/C++的解决方案
如果要让这段代码同时在C和C++下正常编译,建议把枚举类型声明和变量定义分开:
- 在头文件中只声明枚举类型和变量的外部引用:
enum TestEnum { A=303, B=808 }; extern enum TestEnum _TestEnum;
- 在单独的一个.c或.cpp文件中定义变量:
enum TestEnum _TestEnum;
这样不管是C还是C++编译,都不会出现链接错误,内存占用也会符合预期。
内容的提问来源于stack exchange,提问作者Nils Pipenbrinck

