无论是方案1(双静态库)还是方案2(静态库和共享库),都没有错误,因为链接器会从提供尚未定义的符号定义的第一个静态库或第一个共享库中获取第一个目标文件。 它只会忽略同一符号的后续定义,因为它已经拥有良好的定义。 一般来说,链接器仅从库中获取所需内容。 对于静态库,这是严格正确的。 对于共享库,如果满足任何缺少的符号,则共享库中的所有符号都可用;对于某些链接器,共享库的符号可能始终可用,但其他版本仅在该共享库提供至少一个定义时才记录使用共享库。
这也是为什么您需要在对象文件之后链接库的原因。 您可以将dummy.o添加到链接命令中,只要它出现在库之前,就不会有问题。 将dummy.o文件添加到库之后,您将获得重复定义的符号错误。
唯一遇到此双重定义问题的情况是:Library 1中有一个目标文件同时定义了dummy和extra,并且Library 2中有一个目标文件同时定义了dummy和alternative,并且代码需要extra和alternative的定义 - 然后您将具有造成麻烦的dummy的重复定义。 实际上,目标文件可以在��个库中,也会引起麻烦。
考虑:
extern void dummy();
extern int extra(int);
#include "file1.h"
#include <iostream>
void dummy() { std::cerr << "dummy() from " << __FILE__ << '\n'; }
int extra(int i) { return i + 37; }
extern void dummy();
extern int alternative(int);
#include "file2.h"
#include <iostream>
void dummy() { std::cerr << "dummy() from " << __FILE__ << '\n'; }
int alternative(int i) { return -i; }
#include "file1.h"
#include "file2.h"
int main()
{
return extra(alternative(54));
}
由于
dummy
被双重定义,即使主代码不调用
dummy()
,您也无法将所示的三个源文件的对象文件链接起来。关于:
加载程序似乎总是选择动态库而不是编译在.o文件中的内容。
不,链接器总是无条件地尝试加载目标文件。当它在命令行上遇到库时,它会扫描并收集所需的定义。如果目标文件在库之前,则没有问题,除非两个目标文件定义了相同的符号(‘一个定义规则’是否响铃?)。如果某些目标文件跟随库,则可能会出现冲突,如果库定义了后面的目标文件定义的符号。请注意,在开始时,链接器正在寻找对
main
的定义。它从每个告知它的目标文件中收集已定义和已引用的符号,并不断添加代码(从库中),直到定义了所有引用的符号。
这是否意味着相同的符号始终从.so
文件加载,如果它既在.a
文件中又在.so
文件中?
不,这取决于先遇到哪个。如果先遇到
.a
,则将
.o
文件有效地从库复制到可执行文件中(忽略共享库中的符号,因为可执行文件中已经有一个定义)。如果先遇到
.so
,则忽略
.a
中的定义,因为链接器不再寻找该符号的定义-它已经有了。
这是否意味着静态库中的静态符号表中的符号永远不会与.so
文件中的动态符号表中的符号冲突?
您可能会遇到冲突,但第一次遇到的定义将解决链接器的符号。仅当满足引用的代码通过定义其他需要的符号而导致冲突时,才会遇到冲突。
如果我链接2个共享库,是否会发生冲突并且链接阶段失败?
正如我在评论中所指出的那样:
我的直接反应是“可以”。它取决于两个共享库的内容,但我认为可能会遇到问题。[...思考...] 如何展示此问题?...这并不像乍一看那么容易。展示这样的问题需要什么?...还是我想太多了? [...时间去玩一些示例代码...]
经过一些实验,我的暂定经验答案是“不会”(或者“至少在某些系统上不会遇到冲突”)。我很高兴我犹豫了。
使用上述代码(2个头文件,3个源文件)并在Mac OS X 10.10.5(Yosemite)上使用GCC 5.3.0运行时,我可以运行:
$ g++ -O -c main.cpp
$ g++ -O -c file1.cpp
$ g++ -O -c file2.cpp
$ g++ -shared -o libfile2.so file2.o
$ g++ -shared -o libfile1.so file1.o
$ g++ -o test2 main.o -L. -lfile1 -lfile2
$ ./test2
$ echo $?
239
$ otool -L test2
test2:
libfile2.so (compatibility version 0.0.0, current version 0.0.0)
libfile1.so (compatibility version 0.0.0, current version 0.0.0)
/opt/gcc/v5.3.0/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.21.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1213.0.0)
/opt/gcc/v5.3.0/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1.0.0)
$
在 Mac OS X 上,将 .so
作为扩展名是不常见的(通常是 .dylib
),但它似乎能够使用。
然后,我修改了 .cpp
文件中的代码,使得 extra()
在 return
之前调用 dummy()
,alternative()
和 main()
也是如此。重新编译和重建共享库后,运行程序。输出的第一行来自于由 main()
调用的 dummy()
。然后你会按照 return extra(alternative(54));
的调用顺序得到另外两行的输出,分别由 alternative()
和 extra()
产生。
$ g++ -o test2 main.o -L. -lfile1 -lfile2
$ ./test2
dummy() from file1.cpp
dummy() from file2.cpp
dummy() from file1.cpp
$ g++ -o test2 main.o -L. -lfile2 -lfile1
$ ./test2
dummy() from file2.cpp
dummy() from file2.cpp
dummy() from file1.cpp
$
注意,被main()
调用的函数是它所链接的库中出现的第一个函数。但是(至少在Mac OS X 10.10.5上),链接器不会遇到冲突。请注意,每个共享对象中的代码调用“自己”的dummy()
版本 - 这两个共享库之间存在关于哪个函数是dummy()
的分歧。(如果将dummy()
函数放在共享库的单独对象文件中,那么将调用哪个版本的dummy()
呢?) 但在极其简单的场景中,main()
函数设法只调用了其中一个dummy()
函数。(请注意,我不会对此行为在各平台间存在差异感到惊讶。我已经确定了测试代码的位置。如果您发现某些平台上具有不同的行为,请告诉我。)