/* 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();
}
$ c++ a.cpp b.cpp -o a.out && ./a.out
$ c++ -c a.cpp
$ c++ -c b.cpp
$ c++ -o a.out a.o b.o
$ ./a.out
$ nm a.o
$ nm b.o
_Z4echov
的记录, 这就是 echo
函数被 name-mangling 之后的标识.C++ 文档和标准中过度赞扬
inline
这个关键字, 然而一笔带过的是, 编译器可以选择忽略 inline
标记, 至于忽略了会有什么后果, 如何链接等细节都一字不提. 如果这是一个封装行非常良好的编译器特性, 那也就没什么. 但实际上这里敷衍了一些重要事实, 既然 0) 有的函数即使程序员写了 inline
, 编译器可以不进行 inline
, 则 1) 很有可能出现这样的函数, 并在编译后被生成到目标文件中去, 而且 2) 由于 inline
函数一般实现在头文件中, 那么 3) 编译器在编译每一个包含了这个头文件的 cpp 文件时, 都会将该函数符号生成到对应目标文件中去, 而若要 4) 链接这多个目标文件不发生链接冲突, 只好由 5) 编译器或者链接器耍一些手段来保证, 可是 6) 耍了手段, 却有可能造成上面的问题发生.与
inline
失败的函数有关的真相是, 编译器会为 inline
函数做上标记, 链接器根据这个标记, 从目标文件中找到这个函数的多处实现, 不负任何责任地选取其中一个作为正身, 并抛弃其它的实现, 无论这些实现是否相同 (链接器也无从判断是否相同).开头的例子非常猎奇, 但是也不排除这样日常的情况: 0) lib 作者因为各种原因, 让
inline
失败的函数编入 lib; 1) 这个函数没有在文档中被提及, 头文件也隐藏得很好; 2) 引用 lib 的程序员定义了一个同 namespace
且同名的 inline
函数 (当然最有可能在全局名字空间这么干了). 于是就悲催了.现在再回头看一下 inline 的描述
- 要想 inline, 得先
inline
- 若是
inline
, 未必 inline