8

我花了几天时间解决一个奇怪的问题,最后发现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()。所以我认为问题发生在链接时。是否可以说链接器随机选择一种实现并丢弃所有其他实现?

4

3 回答 3

13

在 C++ 标准中,内联函数的所有定义都应相同,但不需要诊断。也就是说,您的程序不是有效的 C++ 程序,但实现有权不检测该错误。

见第 3.2.5 条。在这里发帖太长了。

于 2011-07-27T11:00:40.323 回答
6

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

于 2011-07-27T11:01:55.340 回答
5

编译器不需要诊断此 ODR 违规,而且这不是微不足道的。inline关键字意味着不同的翻译单元可能具有相同的符号,因此编译器将其标记为弱。基本用例是在标题中内联定义的函数:包含标题的所有翻译单元都将具有定义,这很好。编译器只需要丢弃除一个定义之外的所有定义并在任何地方使用该定义。

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

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

于 2011-07-27T11:29:49.817 回答