inline, weak symbol以及其他

首先,如果某个test.h里有如下内容:

class A {
public:
	int x();
}
 
int A::x()
{
}

那如果只有一个test.cpp引用了它:

#include "test.h"
 
int main()
{
	A a;
	a.x();
}

那还好。但如果还有一个test2.cpp引用了它:

#include "test.h"
 
int foo()
{
	A a;
	a.x();
}

然后把test.cpp和test2.cpp分别编译成.o,再链接,就会报错:

> g++ test.o test2.o -o test
test2.o(.text+0x0): In function `A::x()':
: multiple definition of `A::x()'
test.o(.text+0x0): first defined here

因为两个里面都有A::x()这个symbol嘛,很正常。

这个时候,你会想到,如果inline了,不就好了么!

class A {
public:
	int x();
}
 
inline int A::x()
{
}

恩,的确这样就没有问题了,因为A::x()被inline了。你看test.o的符号表,也找不到这个东西。

但是有时候你会有一些ws的想法。你想,如果A::x()太复杂,inline不了,那编译器不就sb了么!

于是你尝试把A::x()改成bt一些的东西:

class A {
public:
	int x(int no);
};
 
inline int A::x(int no)
{
	if (no == 1)
		return 1;
	else if (no == 2)
		return 1;
	else 
		return x(no-1) + x(no-2);
}

嘛,就是算Fib数列么,但是有这种递归了,你丫就不好inline了吧。

试一试,诶,test.o的符号表里果然出现了A::x():

00000000 W _ZN1A1xEi

或者来c++filt一下

00000000 W A::x(int)

test2.o里也有了这个东西。

但是链接却成功了!而且工作得也挺正常。为了保证靠谱,我们让这俩东西有依赖:
修改test.cpp

#include "test.h"
 
extern void foo();
 
int main()
{
	A a;
	a.x(10);
	foo();
}

试一试,诶,还是可以的!

那就是说,inline虽然实际上没成功,但是程序员看起来还是成功了的。秘密应该就在那个符号里:
可以注意到符号名前面有个W,也就是说,这是个weak symbol…

俩weak symbol碰一起,既然名字一样,那就合并了吧……
如果没有inline呢?看看老的.o:

00000000 T A::x(int)

这就是一普通的symbol。俩这种symbol碰一起,就算multiple def,俩weak碰一起,就不算……

那既然要合并,应该要内容一样才能合并吧?难道编译器比较了内容?不可能吧……
于是,我们就会有更加ws的想法:万一不一样呢?

再搞一个test2.h,基本和test.h一样,但是A::x()实现有点不同:

class A {
public:
	int x(int no);
};
 
inline int A::x(int no)
{
	if (no == 1)
		return 1;
	else if (no == 2)
		return 2;
	else 
		return x(no-1) + x(no-2);
}

嘛,虽然很难看出来,但是这个数列往后移了一位……

改改test2.cpp,让他引用test2.h。随后分别把test.cpp和test2.cpp编译成.o。看这俩.o,他们的A::x()符号看上去一模一样……
链接一把,嘿,还能成……

随后就会好奇么,到底是啥情况?各自用各自的?调某一个的?如果是某一个,哪个?
把结果打出来看看么:
改test.cpp:

#include "test.h"
#include <iostream>
using namespace std;
 
extern void foo();
 
int main()
{
	A a;
	cout << "test ret: " << a.x(10) << endl;
	foo();
}

和test2.cpp:

#include "test2.h"
#include <iostream>
using namespace std;
 
int foo()
{
	A a;
	cout << "test2 ret: " << a.x(10) << endl;
}

分别编译成.o,链接,一点问题都没有…… 结果呢:

test ret: 55
test2 ret: 55

还真合并了,还调到同一个上去了…… 这里其实已经出bug了:既然我inline了,那我应该用的是自己引用的那个.h,也就是说,应该是不同的……
刚才是用

 g++ test.o test2.o -o test

链接的。如果换个顺序呢?

 g++ test2.o test.o -o test

结果是:

test ret: 89
test2 ret: 89

变了…… 看来对于俩weak symbol,先看见的会被采用…… 这个55就是test.h里那个的结果,89是test2.h里那个的……

那如果一个weak一个普通呢?我们把test.h里的inline删了,再试试看。
这次,不管啥顺序,结果都是55了,也就是说,普通symbol比起weak symbol,有决定性的优势……

本文写到这里就差不多了。反正对于这个事情,还是把类实现放.cpp里好……
或许你觉得冲突概率很小。但是如果不是C++而是C呢?万一你恰好和另一个库的函数重名了呢?万一有一个还是inline呢?
所以还是大家都C++并且用名字空间的好……

嘛,其实这个问题基本上是不会出现的,虽说用来考C++啥的,倒是有可能…… 但是这个是标准规定的现象还是编译器自己搞的事情,还有待研究……