如果重新定义内联函数呢?
What if redefine an inline function?
我花了几天时间处理一个奇怪的问题,最终发现项目中有两个相同签名的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
,那么编译器知道该函数的所有用法都在您的翻译单元内,并且它有更多的知识来决定是否要内联或不内联函数调用(注意,即使您不告诉它它也可以内联,就像即使您告诉它它也可以决定不内联一样)。
- 不同翻译单元中不可重载的非内联函数定义
- Visual Studio中的函数声明和函数定义问题
- 编写代码时C++出现错误:错误 1 错误 C2601:'circle':本地函数定义是非法的
- 具有enable_if外部类原型的模板类构造函数定义
- 类的前向声明之后的类成员函数定义,在类声明之前
- 为函数定义符号不明确的指针参数
- C++模板专用化 - 无法匹配函数定义
- 错误:在第 6 行'{'标记之前,此处不允许使用函数定义
- 找不到 #define 的函数定义
- 根据类型特征更改函数定义?
- 将抽象基类中的所有纯虚函数定义为 varaidaic 模板
- 命名空间更改函数定义
- "Type&"与C++函数定义中的"Type*"
- C++:为什么允许在另一个函数中声明函数,而不允许在函数定义中声明?
- 如何从 C++ 中的现有模板函数定义新函数
- 私有在函数定义/实现的返回值范围内是什么意思 (c++)?
- 越界成员函数定义是否需要一个完全限定的类名,直到全局范围
- 为什么c++允许成员函数定义中实例的私有成员访问
- Qt基类函数定义
- C++函数定义中参数列表后面额外一对括号的含义