在一个内联函数有不同的定义是否是未定义行为?

9

最简代码:

// --------inline.h--------
struct X { 
  static inline void foo ();
};   
#ifdef YES
inline void X::foo () { cout << "YES\n"; }
#else
inline void X::foo () { cout << "NO\n"; }
#endif

// --------file1.cpp--------
#define YES    // <---- 
#include"inline.h"
void fun1 ()
{
  X::foo();
}

// --------file2.cpp--------
#include"inline.h"
void fun2 ()
{
  X::foo();
}

如果我们调用fun1()fun2(),那么它们将分别打印YESNO,这意味着它们引用了同一个X::foo()的不同函数体。

无论这样编码是否正确,我的问题是:
这是一个良好定义的行为还是未定义的行为?

如果您想在两个不同的文件中拥有两个不同的自由函数(而不是类成员),但名称相同,则可以使用“static”关键字(或匿名命名空间)将它们隔离。将file2.cpp中的函数标记为静态意味着在链接期间,没有其他.cpp文件可以看到它。这对于非常大的程序非常有用,因为您可能并不总是确定哪些常见名称已被函数占用。 - Crashworks
在这种情况下,您实际上不需要inline.h和#define hack; 只需在file1.cpp中以一种方式定义“void X :: foo()”(带有或不带有内联),在file2.cpp中定义另一种方式,您将具有完全相同的行为。 - abarnert
省略 inline 会导致一个不同的一次定义规则违反。非内联函数由3.2段3覆盖,内联函数由3.2段5覆盖。许多(大多数?)链接器会捕获非内联违规情况。至少我的链接器没有捕获内联违规情况。 - David Hammen
@DavidHammen:正如我在Als的回答评论中所说,这实际上是相同的违规行为。7.1.2基本上表示您不能使用inline来规避ODR(以及其他一些与此处无关的事情);核心问题仍然是ODR(请注意7.1.2中对3.2的直接引用)。但是,“在这种情况下,它也可能欺骗编译器和/或链接器错过警告您的好机会”。 - abarnert
@DavidHammen:此外,主要的观点是,您可以通过放弃 #define hack 并将内联定义放在 file1.cpp 和 file2.cpp 中来获得完全相同的行为,从而实现更简单的“最小代码”演示。 - abarnert
3个回答

13

是的,这是未定义行为。

参考资料:

C++03标准:

7.1.2函数说明符[dcl.fct.spec]
第4段:

内联函数必须在使用它的每个翻译单位中定义,并且在每种情况下都应该有完全相同的定义(3.2)。[注意:在翻译单位中出现内联函数调用可能会在其定义出现之前遇到。]如果具有外部链接的函数在一个翻译单位中声明为内联,则在出现的所有翻译单元中都必须声明为内联;不需要进行任何诊断。具有外部链接的内联函数在所有翻译单元中应具有相同的地址。外部内联函数中的静态局部变量始终引用同一对象。外部内联函数中的字符串字面值在不同的翻译单位中是相同的对象。

注:3.2是指“一次定义规则”,其中规定:

3.2一次定义规则[basic.def.odr]
第1段:

没有翻译单元可以包含任何变量、函数、类类型、枚举类型或模板的多个定义。


+1。此外,请注意,inline只是一个提示(尽管在这种情况下它可能会欺骗编译器和/或链接器错过一个好的警告机会)。 - abarnert
3
“inline”不仅仅是一个提示。函数调用是否替换为函数体取决于编译器的判断,但一旦使用“inline”,编译器必须遵循某些关于“单一定义规则”的规定。 - Alok Save
是的,但实际上规则表明,除了在重复完全相同的定义时,使用内联无法绕过ODR。 - abarnert
@abarnert:这还不是全部。规则还要求在每个使用该函数的翻译单元中提供定义(odr)。因此还有其他额外的要求。 - CB Bailey
@CharlesBailey:是的,但这与OP的问题无关。 - abarnert

7

未定义。您正在违反 ODR。


4
如果我们调用fun1()和fun2(),那么它们将分别打印“YES”和“NO”,这意味着它们正在引用同一X::foo()的不同函数体。
你试过了吗?使用不同的优化级别?
我得到的结果是“YES”和“NO”,“YES”和“YES”,或“NO”和“NO”,这取决于优化级别和编译对象呈现给链接器的顺序。
毋庸置疑,这是未定义的行为。

+1 给这个信息。我没有注意到。我使用 -O4 编译了。 - iammilind
1
@Als:观察到行为极其不同是未定义行为的很好证据(虽然不完全是证明)。相反的情况才是危险的(观察到一致的行为并不是它不是未定义行为的好证据)。 - abarnert
3
@Als: 我意识到观察到的行为并不是证明。我之前知道这是UB。我认为看看会产生什么结果,并了解如何玩弄它以产生不同的结果会很有趣。 - David Hammen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接