如果重新定义内联函数呢?

What if redefine an inline function?

本文关键字:函数 定义 新定义 如果      更新时间:2023-10-16

我花了几天时间处理一个奇怪的问题,最终发现项目中有两个相同签名的inline函数,是它们导致了这个问题。为了简化情况,这里有一个例子:两个cpp文件:

a.cpp

#include <iostream>
void b();
inline void echo()
{
    std::cout << 0 << std::endl;
}
int main()
{
    echo();
    b();
    return 0;
}

和b.cpp

#include <iostream>
inline void echo()
{
    std::cout << 1 << std::endl;
}
void b()
{
    echo();
}

请注意,inline函数echo具有相同的签名,但不同的实现。编译并运行

g++ a.cpp b.cpp -o a.out && ./a.out

或者像这样

g++ a.cpp -c
g++ b.cpp -c
g++ a.o b.o -o a.out
./a.out

打印0 0。(我使用的是g++ 4.6.1,我用clang++ 2.9测试,结果相同)

如果打开优化,就不会发生这种情况,比如

g++ -O3 a.cpp b.cpp -o a.out && ./a.out

这次是0 1

我的问题是,无论结果或如何编译执行,都没有错误甚至警告我多次定义inline函数。在这种情况下,编译器和链接器究竟发生了什么?

编辑:

看一下object文件

中的符号
nm a.o b.o | c++filt

两个文件都有记录echo()。所以我认为问题发生在链接时。是否可以说链接器随机选择一个实现并丢弃所有其他实现?

在c++标准中规定内联函数的所有定义必须相同,但是不需要诊断。也就是说,你的程序不是一个有效的c++程序,但是实现有权利不检测这个错误。

参见第3.2.5条。

这种情况(两个具有相同名称和相同签名的内联函数具有不同的实现)会导致未定义的行为。编译器不需要诊断它,尽管它可以尝试。

编译器不需要诊断这种ODR违反,而且它不是微不足道的。inline关键字意味着不同的翻译单元可能有相同的符号,因此它被编译器标记为弱。基本用例是在头文件中内联定义的函数:所有包含头文件的翻译单元都将具有该定义,这是完全正确的。编译器只需要丢弃所有定义,只保留一个定义,并在任何地方使用该定义。

检测不同的定义是否精确匹配是一个复杂的问题。链接器必须分析生成的二进制实现,并确定这两个二进制代码是否与相同的源代码相关。大多数编译器不支持确定这个

对于您的特定问题,我不可能知道导致这两个函数被标记为内联的基本原理,但一个常见的错误是使用inline关键字来表示优化而不是不要抱怨链接时的重复inline关键字在头文件中是有意义的,但在cpp文件中就没那么重要了。在cpp文件中,如果您希望将某些代码片段分解为一个辅助函数,那么该函数应该标记为static,或者在未命名的名称空间中定义。如果函数是static,那么编译器知道该函数的所有用法都在您的翻译单元内,并且它有更多的知识来决定是否要内联或不内联函数调用(注意,即使您不告诉它它也可以内联,就像即使您告诉它它也可以决定不内联一样)。