首先,如果某个test.h里有如下内容:
class A {
public:
int x();
}
int A::x()
{
} |
class A {
public:
int x();
}
int A::x()
{
}
那如果只有一个test.cpp引用了它:
#include "test.h"
int main()
{
A a;
a.x();
} |
#include "test.h"
int main()
{
A a;
a.x();
}
那还好。但如果还有一个test2.cpp引用了它:
#include "test.h"
int foo()
{
A a;
a.x();
} |
#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()
{
} |
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);
} |
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();
} |
#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);
} |
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();
} |
#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;
} |
#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++啥的,倒是有可能…… 但是这个是标准规定的现象还是编译器自己搞的事情,还有待研究……