【问题标题】:Why doesn't the order of object and library affect linking when shared library is used?为什么使用共享库时对象和库的顺序不影响链接?
【发布时间】:2021-07-03 11:12:06
【问题描述】:

我有以下源代码:

  • foo.h

    void foo();
    
  • foo.cpp

    #include "foo.h"
    #include <iostream>
    
    void foo(){
        std::cout << "This is foo" << std::endl;
    }
    
  • main.cpp:

    #include "foo.h"
    
    int main(){
        foo();
    
        return 0;
    }
    

然后我运行以下命令来生成 foo.cpp 的静态和共享版本:

g++ -c -fPIC foo.cpp
ar rvs libfoo.a foo.o
g++ -shared -o libfoo.so foo.o
g++ -c main.cpp

如果我尝试使用静态 libfoo.amain.o 生成可执行文件,则目标文件和库文件的顺序很重要:

g++ main.o libfoo.a -o main  # works
g++ libfoo.a main.o -o main  # fails with undefined references to `foo()`

正如this post 中所述,顺序很重要。

但是,如果我尝试使用共享的 libfoo.somain.o 构建可执行文件,则目标文件和库文件的顺序不再重要:

g++ main.o libfoo.so -o main   # works
g++ libfoo.so main.o -o main   # also works without errors

这是否意味着当我们链接目标文件和共享库时顺序并不重要?或者这只是一个特例或我不知道的巧合?

以上代码和命令在以下平台测试:

  • Linux:CentOS 7.4
  • gcc:7.3.1 或 4.8.5(均已测试)

【问题讨论】:

  • 链接到静态库只会拉入未解析符号的目标。 (静态库是保存目标文件的容器文件。)共享库包含整个库,即使您的代码只需要该共享库中的 1 个符号。共享库是在加载时解析的符号,而不是链接时。各有利弊;对于我编写静态库的应用程序来说是一个很大的胜利,而共享库是一个很大的痛苦。

标签: c++ gcc linker static-linking dynamic-linking


【解决方案1】:

正如本文所述,顺序很重要。

更详细的解释是here

顺序对于传统 UNIX 链接器很重要,但最近 LLD 决定打破传统,在存档库中记录符号的可用性,即使它们没有立即被引用。

目前在 Linux 上可以使用三种不同的链接器:原始 BFD 链接器、Gold 链接器(大约 2008 年)和 LLD(大约 2019 年)。

使用“正确”的顺序对所有三个都成功(自然)。

使用“错误”的顺序:g++ libfoo.a main.o 使用 BFD 和 Gold 失败,但使用 LLD 成功:

g++ libfoo.a main.o -fuse-ld=lld && echo $?
0

但是,如果我尝试使用共享的 libfoo.so 和 main.o 构建可执行文件,那么目标文件和库文件的顺序就不再重要了

无关紧要的原因是链接器对待.so 的方式与对待.o 的方式类似。特别是,没有必要“抽出”.so 的一部分——你会得到整个.so(就像你使用foo.o 时所做的那样1

所以当你链接g++ libfoo.so main.o时,这个链接几乎等同于使用g++ foo.o main.o,并且前一个命令中的顺序无关紧要,原因相同后一个命令。


1尽管使用-ffunction-sections 构建并使用--gc-sections

【讨论】:

  • 感谢您提供此信息!根据this post 的评论,我们需要 gcc 9.3.0+ 才能使用选项-fuse-ld=lld
【解决方案2】:

因为在第二种情况下不需要强符号名称解析。在 ELF 解释期间,即在您运行程序时,会查找和解析共享对象中的符号。

【讨论】:

  • 有没有关于这种行为的权威文档?我似乎可以在 ld 手册上找到正确的文档。
  • @jdhao 宁愿寻找 ld.so man ,但这将是特定于 linux 的。每个操作系统实现它的方式不同,尽管使用 ELF 的有点相似。一般的想法是符号在程序运行之前保持未解析并在一些特别标记的表中保持引用。需要 ELF 系统上的 .so 文件作为该表的源。 PEXE 系统改为使用导入库,它与 .DLL 文件是分开的。
  • 如果来自共享库的符号在运行时被解析。为什么我们在构建可执行文件时需要链接它。 g++ main.o -o main 不会编译。
  • @jdhao 因为你没有桌子。如果你“链接” .so 库,你链接整个它,而不仅仅是使用的符号。这也将允许在运行时获取指向它们的指针,请参阅 dlopen/dlsym
  • 第一个陈述显然是错误的。第二个语句对我来说完全是胡言乱语。
猜你喜欢
  • 1970-01-01
  • 2014-12-17
  • 2011-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-25
相关资源
最近更新 更多