试探性定义为何不会引发多重定义错误?基于C17标准的疑问
你对C17相关条款的理解完全正确,该条款仅约束单个翻译单元内的试探性定义行为,完全没有涉及跨翻译单元的同名符号处理规则。
跨翻译单元出现多个具有外部链接的同名标识符定义,本身在C标准中属于未定义行为,标准没有强制要求编译器必须报错或者做合并处理,完全留给实现自行决定。目前主流编译器有两种完全不同的实现策略:
- GCC、Clang等类UNIX环境的编译器默认采用「公共段模型」:未初始化的试探性定义会被标记为公共段符号,链接时多个同名公共段符号会被合并为单个实例,只有存在显式初始化的强定义时才会直接使用该强定义,不会触发多重定义错误。
- 你使用的MSVC 2019默认采用「强定义模型」:所有文件作用域下未初始化的外部链接变量,会被直接视为已初始化为0的强定义,等价于你显式写了
int i4 = 0;,所以当两个翻译单元都引入头文件里的int i4;时,链接必然会触发多重定义错误,和你的预期完全一致。
问题规避方案
如果要编写跨编译器通用的可移植代码,标准的规避写法如下:
- 头文件中仅放置外部声明:
extern int i4;,绝对不要放置任何试探性定义 - 任选一个关联的源文件,放置唯一的全局定义:可以是未初始化的
int i4;,也可以是显式初始化的int i4 = 0;
如果你需要在MSVC上兼容类UNIX编译器的行为,可以添加编译选项/Zc:tentativeDefinitions-,该选项会关闭MSVC默认的强定义逻辑,启用公共段模型,允许多个试探性定义合并为单个实例,不会触发重定义错误。
内容的提问来源于stack exchange,提问作者vbyzjnlehi
相关产品推荐
相关产品推荐

