为何C++标准对odr-used实体缺失定义不要求诊断?
程序中在被舍弃语句之外被odr使用的每个函数或变量,程序都应包含其至少一个定义;无需诊断。[...]
——[basic.def.odr] p11
我的直觉是,若函数或变量被odr使用且无任何定义,实际会触发链接器错误,编译器和链接器应较易检测到这种情况。请问是否存在缺失定义难以诊断的场景,足以证明将其归为无需诊断(IFNDR)的合理性?
确实存在不少场景,导致编译器或链接器无法可靠检测到「odr使用但缺失定义」的问题,这也是标准将其归为无需诊断的核心原因:
模板特化的隐式依赖
模板中若使用了未定义的函数或变量,其依赖的类型仅在编译期实例化阶段才能确定,编译器无法在模板定义时提前预判缺失。比如:template<typename T> void foo() { extern void bar(T); bar(T{}); // odr使用bar,但仅当foo被实例化时才触发定义需求 }如果
foo<int>()从未被调用,缺失bar(int)定义的问题永远不会暴露;但某天在某个翻译单元实例化该模板时,才会突然出现链接错误——编译器无法在模板编写阶段诊断这种潜在问题。条件编译与分支依赖
当odr使用的代码路径依赖编译期宏定义或运行时条件时,只有特定编译配置或运行场景才会触发缺失。比如:#ifdef DEBUG extern void debug_log(const char*); #endif void process() { #ifdef DEBUG debug_log("processing"); // 仅DEBUG宏开启时才odr使用debug_log #endif }开启DEBUG编译时未提供定义会触发链接错误,但关闭DEBUG则无问题。编译器无法预判所有编译配置,因此无法强制诊断这类场景。
跨翻译单元的未触发依赖
当odr使用通过多层间接调用链存在,但顶层调用从未被触发时,链接器不会检查底层依赖。比如:
翻译单元A:extern void func(); void wrapper() { func(); } // odr使用func翻译单元B:
void wrapper(); // 未调用wrapper,链接器不会检查func的定义此时
func虽被odr使用,但因wrapper未被调用,链接器不会报错。只有当某个翻译单元调用wrapper时,问题才会暴露——标准无法要求工具对这种未触发的场景做诊断。inline函数的符号特殊性
inline函数允许在多个翻译单元定义,但若某个单元odr使用了inline函数,却没有任何单元提供其定义,链接器可能因符号处理机制的特殊性忽略该问题,直到函数被实际调用才会报错。
这些场景的共性是:odr使用的触发依赖特定编译、链接或运行条件,工具无法在所有情况下都可靠检测到缺失定义。因此C++标准将其归为无需诊断,允许工具在无法检测时不报错,避免强制要求实现不切实际的全场景检查。
内容的提问来源于stack exchange,提问作者Jan Schultke

