我花了几天时间处理一个奇怪的问题,最后发现项目中有两个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
个函数。在这种情况下,编译器和链接器究竟发生了什么?
编辑:
查看目标文件中的符号
nm a.o b.o | c++filt
两个文件都有记录echo()
。所以我认为问题发生在链接时。可以说链接器随机选择一个实现并丢弃所有其他实现吗?
答案 0 :(得分:13)
在C ++标准中,声明内联函数的所有定义应相同,但 不需要诊断 。也就是说,您的程序不是有效的C ++程序,但实现有权不检测该错误。
见第3.2.5条。在这里发帖太长了。
答案 1 :(得分:6)
这种情况(具有相同名称和具有不同实现的相同签名的两个内联函数)leads to undefined behavior。编译器不需要诊断它,尽管它可以尝试。
答案 2 :(得分:5)
编译器不需要诊断此ODR违规,这并非易事。 inline
关键字表示不同的翻译单元可能具有相同的符号,因此编译器将其标记为弱。基本用例是在标题中内联定义的函数:包含标题的所有翻译单元都将具有定义,并且完全没问题。编译器只需要丢弃除一个定义之外的所有定义,并在任何地方使用该定义。
检测不同的定义是否完全匹配是一个复杂的问题。链接器必须分析生成的二进制实现并确定两个二进制代码是否与相同的源代码相关。大多数编译器都没有支持来确定这一点。
截至您的特定问题,我不太可能知道导致两个函数被标记为内联的基本原理,但常见错误是使用inline
关键字来表示 optimize 而不是不要在链接时抱怨重复。 inline
关键字在标题中有意义,但在cpp文件中没有那么多。在cpp文件中,如果要将某段代码分解为辅助函数,则该函数应标记为static
或在未命名命名空间中定义。如果函数是static
,则编译器知道该函数的所有用法都在您的翻译单元中,并且它有更多的知识来决定是否要内联函数调用(注意它可以内联)即使你没有告诉它,就像它决定不内联即使你告诉它一样)。