如果重新定义内联函数怎么办?
我花了几天时间解决一个奇怪的问题,最后发现项目中有两个具有相同签名的内联函数,它们导致了问题。为了简化情况,这里举一个例子:两个 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
。
我的问题是,无论结果或编译如何执行,都没有错误,甚至没有关于我多次定义内联函数的警告。在这种情况下编译器和链接器到底会发生什么?
编辑:
查看目标文件中的符号
nm a.o b.o | c++filt
两个文件都有记录echo()
。所以我认为问题发生在链接时。是否可以说链接器随机选择一种实现并丢弃所有其他实现?
I've spent days in a weird problem and finally discover that there were two inline
function of the same signature in the project and they caused the problem. To simplify the situation here is an example: two cpp file:
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();
}
Please note that inline
functions echo
have the same signature but different implements. Compile and run
g++ a.cpp b.cpp -o a.out && ./a.out
Or like this
g++ a.cpp -c
g++ b.cpp -c
g++ a.o b.o -o a.out
./a.out
It prints 0 0
. (I was using g++ 4.6.1 for that, and I tested with clang++ 2.9, same result)
That won't happen if turning on optimization, like
g++ -O3 a.cpp b.cpp -o a.out && ./a.out
It is 0 1
this time.
My question is, no matter the result or how the compilation performs, there is no error or even warning about I have defined inline
functions multiple times. What on earth happens to the compiler and linker in this kind of situation?
EDIT:
Take a look at the symbols in the object file
nm a.o b.o | c++filt
Both files have the record echo()
. So I think the problem happens at the link time. Could it be said that the linker randomly picks one implementation and discard all other?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
在 C++ 标准中,规定内联函数的所有定义都应相同,但不需要诊断。也就是说,您的程序不是有效的 C++ 程序,但实现有权不检测该错误。
参见第 3.2.5 条。这里发帖太长了。
In the C++ standard it is stated than all the definitions of an inline function shall be the same, but no diagnostic is required. That is, your program is not a valid C++ program, but an implementation has the right not to detect that error.
See Clause 3.2.5. It's too long to post here.
这种情况(两个具有相同名称和相同签名的内联函数具有不同的实现)会导致未定义的行为。编译器不需要诊断它,尽管它可以尝试这样做。
This very case (two inline functions with the same name and same signatures having different implementations) leads to undefined behavior. The compiler is not required to diagnose it, although it could try to.
编译器不需要诊断这种 ODR 违规,而且这并不简单。
inline
关键字意味着不同的翻译单元可能具有相同的符号,因此它被编译器标记为弱。基本用例是在标头中内联定义的函数:包含标头的所有翻译单元都将具有定义,并且完全没问题。编译器只需要丢弃除一个定义之外的所有定义,并在任何地方使用该定义。检测不同的定义是否完全匹配是一个复杂的问题。链接器必须分析生成的二进制实现并确定两个二进制代码是否与相同的源代码相关。大多数编译器不支持确定这一点。
至于您的特定问题,我不可能知道导致这两个函数被标记为内联的基本原理,但一个常见的错误是使用
inline
关键字来表示优化而不是不要抱怨链接时的重复。inline
关键字在标头中有意义,但在 cpp 文件中意义不大。在 cpp 文件中,如果您想将某些代码片段分解到辅助函数中,则该函数应该标记为static
,或者在未命名命名空间中定义。如果函数是静态的,那么编译器知道该函数的所有用法都在您的翻译单元内,并且它有更多的知识来决定是否要内联函数调用(请注意,即使您不告诉它,它也可以内联,就像即使您告诉它,它也可以决定不内联一样)。The compiler is not required to diagnose this ODR violation, and it is not trivial. The
inline
keyword means that different translation units might have the same symbol, so it is marked weak by the compiler. The basic use case is a function defined inline in a header: all translation units that include the header will have the definition, and it is perfectly fine. The compiler only needs to discard all but one definition and use that definition everywhere.Detecting whether the different definitions are exact matches is a complex problem. The linker would have to analyze the generated binary implementation and determine whether the two binary codes relate to the same source code or not. Most compilers do not have support to determine this.
As of your particular problem, I cannot possibly know the rationale that led to the two functions being marked inline, but a common error is using the
inline
keyword to represent optimize rather than don't complain of repetitions at link time. Theinline
keyword makes sense in headers, but not so much in cpp files. In cpp files, if you want to factor some piece of code into a helper function, that function should be either markedstatic
or be defined within an unnamed namespace. If the function isstatic
then the compiler knows that all of the usages of that function are within your translation unit, and it has greater knowledge to decide whether it wants to inline or not the function call (note that it can inline even if you don't tell it to, in the same way that it can decide not to inline even if you tell it).