先上例子:
#ifndef FOO_HPP
#define FOO_HPP
void foo()
{
}
#endif
#include "foo.hpp"
void func_a()
{
foo();
}
#include "foo.hpp"
void func_b()
{
foo();
}
extern void func_a();
extern void func_b();
int main()
{
func_a();
func_b();
return 0;
}
编译:
$ arm-linux-gnueabihf-g++ a.cpp b.cpp main.cpp -save-temps -O0 -o main
b.o: In function `foo()':
b.cpp:(.text+0x0): multiple definition of `foo()'
a.o:a.cpp:(.text+0x0): first defined here
collect2.exe: error: ld returned 1 exit status
As is known to us all,上面这个例子链接时会报错:foo 函数重复定义。因为a.cpp和b.cpp都包含了foo.hpp文件,相当于foo这个全局函数在a.cpp和b.cpp都有定义了。用nm 命令就可以看到a.o和b.o中都存在相同的T _Z3foov 这个符号,大写的T说明它是global(external)的,如下所示:
$ nm a.o
00000000 T _Z3foov
0000000c T _Z6func_av
$ nm b.o
00000000 T _Z3foov
0000000c T _Z6func_bv
既然foo作为一个全局函数会导致重复定义,那么自然很容易想到,假如加上static 关键字限定其作用域,是不是就没有问题了呢? 尝试一下,链接ok,再看一下目标文件中的符号,a.o和b.o中都存在t _ZL3foov ,哈哈,小写的t说明这就是个local的符号,所以在最终生成的main文件中存在2个地址不同的t _ZL3foov :
$ nm a.o
0000000c T _Z6func_av
00000000 t _ZL3foov
$ nm b.o
0000000c T _Z6func_bv
00000000 t _ZL3foov
$ nm main
000083c8 T _start
000084b4 T _Z6func_av
000084cc T _Z6func_bv
000084c0 t _ZL3foov
000084a8 t _ZL3foov
However,由于static 函数在每个编译单元都有独自一份拷贝,这也显然会造成问题。假如初衷只是将函数定义写在头文件中的话,inline 则是更好的选择,详细的说明可以参考这个:Jon Lee的知乎回答。下面看看改为inline 后的编译结果:_Z3foov 是一个weak symbol。
$ nm a.o
00000000 W _Z3foov
00000000 T _Z6func_av
$ nm b.o
00000000 W _Z3foov
00000000 T _Z6func_bv
$ nm main
000083c8 T _start
000084b4 W _Z3foov
000084a8 T _Z6func_av
000084c0 T _Z6func_bv
BTW,在win10下用g++编译的话,会有些不同:a.o和b.o中各有T _Z3foov ,最终的main.exe中则是有一个U _Z3foov 和一个T _Z3foov 。U代表的含义是:
该符号在当前文件中是未定义的,即该符号的定义在别的文件中。例如,当前文件调用另一个文件中定义的函数,在这个被调用的函数在当前就是未定义的;但是在定义它的文件中类型是T。但是对于全局变量来说,在定义它的文件中,其符号类型为C,在使用它的文件中,其类型为U。
$ nm a.o
0000000000000000 T _Z3foov
0000000000000000 T _Z6func_av
$ nm b.o
0000000000000000 T _Z3foov
0000000000000000 T _Z6func_bv
$ nm main.exe
U _Z3foov
00000001004016f0 T _Z3foov
0000000100401080 T _Z6func_av
00000001004010a0 T _Z6func_bv
在这个知乎专栏中也对不同编译器中inline 的处理方式进行了测试,BTW,文中还测试了在不同的 .cpp 中定义相同的 inline(但不是 static)的 函数(但不是 static),会由于编译顺序不同->链接阶段的符号冲突和混乱链接->导致最终输出结果不一致的情况,说明——
inline 函数应该老老实实在 .h 中定义,而不是为了赶时间手动拷贝到不同的 .cpp 中并顺手修改,以免造成 Debug / Release 模式下结果不同
想要测试的话,把a.cpp和b.cpp做如下更改,分别用不同的编译命令(注意编译文件的顺序和优化选项)看下输出结果就明白啦。
g++ a.cpp b.cpp main.cpp -save-temps -O0 -o main
g++ b.cpp a.cpp main.cpp -save-temps -O0 -o main
g++ a.cpp b.cpp main.cpp -save-temps -O3 -o main
g++ b.cpp a.cpp main.cpp -save-temps -O3 -o main
#include <iostream>
inline void foo()
{
std::cout << "foo a" << std::endl;
}
void func_a()
{
foo();
}
#include <iostream>
inline void foo()
{
std::cout << "foo b" << std::endl;
}
void func_b()
{
foo();
}
Finally小结,还是Jon Lee的知乎回答说得好:
- 谨慎使用 static:如果只是想把函数定义写在头文件中,用 inline,不要用static。
- inline最大的用处是:非template 函数,成员或非成员,把定义放在头文件中,定义前不加inline ,如果头文件被多个translation unit(cpp文件)引用,ODR会报错multiple definition。
- inline 函数的定义不一定要跟声明放在一个头文件里面:定义可以放在一个单独的头文件 .hxx 中,里面需要给函数定义前加上 inline 关键字,原因看下面第 2.点;然后声明 放在另一个头文件 .hh 中,此文件include 上一个 .hxx。这种用法 boost里很常见:优点1. 实现跟API 分离,encapsulation。优点2. 可以解决有关inline函数的循环调用问题。
|