在Linux系统上使用C++链接共享库和静态库

3

我在测试项目上进行了一些实验,我们称之为 mytest ,它有一个 .cpp 和一个 .h 文件,内容并不是很重要 - 想象一下它包含了一些简单的 hello_world() 类型的函数...

所以,我正在编写一个通用的 makefile,将其编译成各种库输出,其中对输出文件夹运行 ls -l 命令后,会得到以下结果:

libmytest.a
libmytest.so -> libmytest.so.1.0
libmytest.so.1 -> libmytest.so.1.0
libmytest.so.1.0

到目前为止一切都很好,我的共享/静态库已经创建好了。
现在,我的make文件中有一个“make install”目标,它基本上将头文件复制到“/usr/local/include”,并将所有这些库文件复制到“/usr/local/lib”。
然后我又创建了另一个名为“usertest.cpp”的测试cpp文件(对于命名不是非常具有想象力/描述性,请见谅),它链接到库文件。
我以各种方式编译:
1. g++ -Wall -Werror -I. -lmytest 2. g++ -Wall -Werror -I. -lmytest -static
然后我删除了libmytest.so*文件,所以我只在“/usr/local/lib”中有libmytest.a库文件。然后我做了同样的测试:
3. g++ -Wall -Werror -I. -lmytest 4. g++ -Wall -Werror -I. -lmytest -static
最后,我删除了libmytest.a文件,并“复制回.so文件”,所以我只有libmytest.so*库文件在“/usr/local/lib”中。然后我做了同样的测试:
5. g++ -Wall -Werror -I. -lmytest 6. g++ -Wall -Werror -I. -lmytest -static
文件大小结果(以字节为单位)为:
1. 7736        - Makes sense, all libs dynamically linked
2. 19674488    - Makes sense, all libs statically linked
3. 64908       - hmm... not really sure why
4. 19674488    - Makes sense, same as 2.
5. 7736        - Makes sense, same as 1.
6. failed      - Makes sense, no .so files!

我有三个文件大小,小的(7736)是完全动态链接的。大的是静态链接的...那么这个中等大小的文件(64908)是什么?所以我有几个问题:
  • 1. 我假设系统首先查找.so库,然后查找.a库?
  • 3. 这里发生了什么? -它是否在动态链接系统库时看到了我的.a库并动态链接了它?
请注意 所有输出都可以正常运行,并调用库中的函数。

也许之所以5个链接更有趣——你已经删除了所有的库,但它仍然可以工作。 - UKMonkey
@UKMonkey 啊,不好意思 - 我可能没有解释清楚....我的意思是我删除了 .a 文件并且 放回了 .so 文件....我会重新措辞的.... - code_fodder
2个回答

4
对于第一句话,我认为系统首先查找.so库,其次是.a库?
大体上是这样的,但请继续阅读。
第三句话中发生了什么?-它是否动态链接系统库,但当它看到我的.a库时,它会动态链接它吗?
静态库不能被动态链接:它是静态链接的。共享(=动态)系统库被链接, 假设链接器找到并优先使用的系统库实际上是共享库。
默认情况下,链接选项-lmytest指示链接器首先在您在命令行中使用-Ldirname选项指定的搜索目录中按顺序查找名为libmytest.so(共享库)或libmytest.a(静态库)的输入文件,然后在其默认搜索目录中按配置顺序查找。当它在这些目录中找到其中任何一个文件时,它就停止搜索。如果它在同一目录中找到了两个文件,则选择共享库libmytest.so。如果有选择的文件,则将其作为输入输入到链接中。如果搜索失败,则链接器会给出错误:cannot find -lmytest
通过选项-static可以更改此默认行为。如果它出现在命令行中的任何位置,则链接器将忽略所有共享库:然后只能通过找到libmytest.a来满足-lmytest,并且还必须找到静态系统库。 /usr/local/lib是链接器的默认搜索目录之一。因此,当您执行以下操作时:
g++ -Wall -Werror -I. -lmytest

在情况(3)中,链接器找到了/usr/local/lib/libmytest.a,但没有找到/usr/local/lib/libmytest.so,因此libmytest.a 满足 -lmytest 并被输入到链接中。链接器对共享库的默认偏好不受影响。 libmytest.a 对可执行文件大小的贡献并不明显。
静态库与共享库非常不同,它不是链接器生成的 ELF 二进制文件,而是由ar生成的一组对象文件的ar归档文件,即一组恰好是对象文件的文件包。
默认情况下,当将一个ar归档文件输入到链接器时,它会在包中查找任何目标文件,这些目标文件为已经链接到输出文件(程序或共享库)的任何未定义符号引用提供定义。如果它发现了这样的目标文件,它会从存档中提取它们并将它们链接到输出文件中,就好像它们在命令行中被逐个列出,而存档根本没有被提及一样。除了作为可以选择目标文件的包之外,存档对链接没有任何贡献。
因此,如果libmytest.a中有N个目标文件,则将该存档文件输入到链接中可能会向输出文件贡献0到N个目标文件,具体取决于链接中早期积累的那些未定义引用以及哪些目标文件为这些引用提供了定义。
即使您知道在链接中需要哪些libmytest.a对象文件,也不能得出它们的大小总和将被添加到输出文件的结论。编译器通过将对象文件分区为部分,其中一节是链接器识别的输入和输出的最小单位来完成此操作。默认情况下,链接器仅会保留一个输入部分用于输出,仅当该部分提供了链接必须定义的某个符号的链接器所选的定义时。如果输入部分没有这种用途,则链接器将其丢弃。因此,即使链接了对象文件,链接器也可能从输出文件中省略其中的冗余部分。 -l | --library链接器选项的行为记录在2.1 命令行选项中,该选项在GNU ld手册中有记录。

感谢您花时间以易于理解的详细方式进行解释:) - code_fodder

1

很可能 libmytest.a 不是导致二进制文件大小增加的主要因素,而是更大的标准库(这就解释了为什么在3中大小没有增长太多)。

您可以使用 ldd 来调查二进制文件的所有动态依赖关系:

 ldd a.out

(并且其中哪些在使用-static后消失。)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接