我有一个dummy.hpp
#ifndef DUMMY #define DUMMY void dummy(); #endif
和一个dummy.cpp
#includevoid dummy() { std::cerr << "dummy" << std::endl; }
和main.cpp使用dummy()
#include "dummy.hpp" int main(){ dummy(); return 0; }
然后,我编dummy.cpp三个库libdummy1.a
,libdummy2.a
,libdummy.so
:
g++ -c -fPIC dummy.cpp ar rvs libdummy1.a dummy.o ar rvs libdummy2.a dummy.o g++ -shared -fPIC -o libdummy.so dummy.cpp
当我尝试编译main并链接虚拟库时
g++ -o main main.cpp -L. -ldummy1 -ldummy2
链接器不会产生重复的符号错误。当我静态链接两个相同的库时,为什么会发生这种情况?
当我尝试
g++ -o main main.cpp -L. -ldummy1 -ldummy
也没有重复的符号错误,为什么?
加载程序似乎总是选择动态库,而不是选择.o
文件中编译的代码。
.so
如果它同时存在于.a
和.so
文件中,是否意味着始终从文件加载相同的符号?
这是否意味着静态库中的静态符号表中的符号永远不会与.so
文件中的动态符号表中的符号冲突?
方案1(双静态库)或方案2(静态和共享库)都没有错误,因为链接器会从遇到的静态库或第一个共享库中获取第一个目标文件,从而提供符号定义它还没有一个定义。它只是忽略了以后对同一符号的任何定义,因为它已经有了一个好的符号。通常,链接器仅从库中获取所需的内容。对于静态库,这是完全正确的。对于共享库,如果共享库满足任何缺少的符号,则所有符号都可用。对于某些链接器,共享库的符号无论如何都可以使用,但是其他版本仅在共享库提供至少一个定义的情况下记录对共享库的使用。
这也是为什么您需要在目标文件之后链接库。您可以添加dummy.o
链接命令,只要这些命令出现在库之前,就不会有麻烦。将dummy.o
文件添加到库之后,您将得到双重定义的符号错误。
你这双定义遇到问题的唯一情况是,如果有图书馆1的对象文件定义了dummy
和extra
,并且有一个目标文件在图书馆2种规定dummy
和alternative
,并且代码需要两者的定义extra
和alternative
-那么你有重复的定义dummy
会引起麻烦。实际上,目标文件可能在单个库中,并且会引起麻烦。
考虑:
/* file1.h */ extern void dummy(); extern int extra(int); /* file1.cpp */ #include "file1.h" #includevoid dummy() { std::cerr << "dummy() from " << __FILE__ << '\n'; } int extra(int i) { return i + 37; } /* file2.h */ extern void dummy(); extern int alternative(int); /* file2.cpp */ #include "file2.h" #include void dummy() { std::cerr << "dummy() from " << __FILE__ << '\n'; } int alternative(int i) { return -i; } /* main.cpp */ #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) $
.so
用作Mac OS X上的扩展名是常规的(通常是.dylib
),但它似乎可以工作。
然后,我修改了.cpp
文件中的代码,以便在和之前extra()
调用。重新编译并重建共享库后,我运行了程序。输出的第一行来自的调用。然后,您将获得和按此顺序生成的其他两行,因为调用顺序要求这样做。dummy()
return
alternative()
main()
dummy()
main()
alternative()
extra()
return extra(alternative(54));
$ 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 $
请注意,by调用的函数main()
是与其链接的库中出现的第一个函数。但是(至少在Mac OS X 10.10.5上)链接器不会发生冲突。但是请注意,每个共享库中的代码都调用“自己的”版本dummy()
-两个共享库之间关于哪个函数是不一致dummy()
。(将dummy()
函数放在共享库中的单独目标文件中会很有趣;然后dummy()
调用哪个版本的?)但是在所示的极其简单的情况下,该main()
函数仅能调用其中一个。dummy()
功能。(请注意,对于这种行为,发现平台之间的差异并不奇怪。我已经确定了在哪里测试代码。如果您在某些平台上发现不同的行为,请告诉我。)