重新定义内联函数会发生什么?

9

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

a.cpp

#include <iostream>

void b();

inline void echo()
{
    std::cout << 0 << std::endl;
}

int main()
{
    echo();
    b();
    return 0;
}

and 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函数的错误甚至警告。在这种情况下编译器和链接器到底发生了什么?

编辑:

查看目标文件中的符号。

nm a.o b.o | c++filt

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


你尝试过使用更高的警告详细程度吗?(-Wall等) - schnaader
我刚刚尝试了-Wall -Wextra,仍然没有警告。 - neuront
3个回答

13
在C++标准中,规定内联函数的所有定义应该相同,但不需要进行诊断。也就是说,您的程序不是一个有效的C++程序,但实现有权利不检测这个错误。
请参见第3.2.5条款。这里太长,无法在此发布。

6
这种情况(有相同名称和签名但实现不同的两个内联函数)会导致未定义行为。编译器不必诊断它,尽管可以尝试这么做。请参考此链接

5
编译器不必要诊断这种 ODR 违规,而且它并不是微不足道的。inline 关键字意味着不同的翻译单元可能具有相同的符号,因此编译器将其标记为弱。基本用例是在头文件中定义内联函数:包含头文件的所有翻译单元都将具有该定义,这非常好。编译器只需要废弃所有但一个定义,并在每个地方使用该定义即可。
检测不同的定义是否完全匹配是一个复杂的问题。链接器必须分析生成的二进制实现,并确定两个二进制代码是否与同一源代码相关。大多数编译器没有支持来确定这一点。
至于您特定的问题,我不可能知道导致这两个函数被标记为内联的原理,但常见的错误是使用 inline 关键字表示优化,而不是在链接时不报告重复。inline 关键字在头文件中很有意义,但在 cpp 文件中则不太合适。在 cpp 文件中,如果想将某些代码片段拆分为辅助函数,则该函数应标记为 static 或在未命名的命名空间内定义。如果函数是静态的,则编译器知道该函数的所有用法都在您的翻译单元内,它可以更好地决定是否要内联或不内联函数调用(请注意,即使您没有告诉编译器,它也可以内联,同样,它也可以决定不内联)。

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