链接器为什么不抱怨重复的符号?

时间:2015-12-24 14:35:58

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

我有一个dummy.hpp

#ifndef DUMMY
#define DUMMY
void dummy();
#endif

和一个dummy.cpp

#include <iostream>
void dummy() {
      std::cerr << "dummy" << std::endl;
}

和一个使用dummy()

的main.cpp
#include "dummy.hpp"
int main(){

    dummy();
    return 0;
}

然后我将dummy.cpp编译为三个库,libdummy1.alibdummy2.alibdummy.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
  1. 当我尝试编译main并链接虚拟库

    g++ -o main main.cpp -L. -ldummy1 -ldummy2
    

    链接器不会产生重复的符号错误。当我静态链接两个相同的库时,为什么会这样?

  2. 当我尝试

    g++ -o main main.cpp -L. -ldummy1 -ldummy
    

    也没有重复的符号错误,为什么?

  3. 加载器似乎总是选择动态库而不是.o文件中编译的代码。

    是否始终从.so文件加载相同的符号(如果它同时位于.a.so文件中?

    是否意味着静态库中静态符号表中的符号永远不会与.so文件中动态符号表中的符号冲突?

1 个答案:

答案 0 :(得分:4)

方案1(双静态库)或方案2(静态和共享库)中没有错误,因为链接器从静态库或第一个共享库中获取第一个目标文件它提供了一个尚未定义的符号的定义。它只是忽略了同一符号的任何后来的定义,因为它已经有了一个很好的定义。通常,链接器仅从库中获取所需的内容。对于静态库,这是完全正确的。使用共享库,如果满足任何缺少的符号,则共享库中的所有符号都可用;对于某些链接器,共享库的符号可能无论如何都可用,但是如果共享库提供至少一个定义,则其他版本仅记录使用共享库。

这也是您需要在目标文件之后链接库的原因。您可以将dummy.o添加到链接命令中,只要它出现在库之前,就没有问题。在库之后添加dummy.o文件,您将获得双重定义的符号错误。

唯一一次遇到这个双重定义的问题是,如果库1中有一个定义dummyextra的目标文件,那么它就是一个对象库2中的文件定义了dummyalternative,代码需要extraalternative的定义 - 然后您有dummy的重复定义这会引起麻烦。实际上,目标文件可能位于单个库中,会导致麻烦。

考虑:

/* file1.h */
extern void dummy();
extern int extra(int);

/* file1.cpp */
#include "file1.h"
#include <iostream>
void 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 <iostream>
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()。 / p>

关于:

  

加载器似乎总是选择动态库而不是在.o文件中编译。

没有;链接器总是尝试无条件地加载目标文件。它会在命令行遇到库时扫描库​​,收集所需的定义。如果目标文件位于库之前,那么除非两个目标文件定义相同的符号(“一个定义规则”和“响铃”是什么?),否则不会出现问题。如果某些目标文件遵循库,则如果库定义了后面的目标文件定义的符号,则可能会遇到冲突。请注意,当它开始时,链接器正在寻找main的定义。它从被告知的每个目标文件中收集定义的符号和引用的符号,并不断添加代码(来自库),直到定义了所有引用的符号。

  

是否意味着始终从.so文件加载相同的符号,如果它同时位于.a.so文件中?

没有;这取决于首先遇到的问题。如果首先遇到.a,则.o文件会从库中有效地复制到可执行文件中(并且忽略共享库中的符号,因为已经在其中定义了它可执行文件)。如果首先遇到.so,则忽略.a中的定义,因为链接器不再寻找该符号的定义 - 它已经有了一个。

  

这是否意味着静态库中静态符号表中的符号永远不会与.so文件中动态符号表中的符号冲突?

您可能会遇到冲突,但遇到的第一个定义会解析链接器的符号。如果满足引用的代码通过定义所需的其他符号而导致冲突,则它只会遇到冲突。

  

如果我链接2个共享库,我是否会遇到冲突并且链接阶段失败?

正如我在评论中所说:

  

我的直接反应是&#34;是的,你可以&#34;。这将取决于两个共享库的内容,但我相信你可能会遇到问题。 [... cogitation ...] 你会如何表明这个问题? ......它并不像第一眼看上去那么容易。要证明这样的问题需要什么? ......或者我是否在思考这个? ... [...时间去玩一些示例代码......]

经过一些实验,我的临时实证答案是“不,你不能”#34; (或者&#34;不,至少在某些系统上,你不会遇到冲突&#34;)。我高兴我很高兴。

在Mac OS X 10.10.5(Yosemite)上获取上面显示的代码(2个标题,3个源文件),并在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()之前调用returnalternative()和{{1}也是如此}。在重新编译和重建共享库之后,我运行了程序。第一行输出来自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 $ 调用的函数是与其链接的库中出现的第一个函数。但是(至少在Mac OS X 10.10.5上)链接器不会遇到冲突。但请注意,每个共享对象中的代码都会调用它自己的代码。版本main() - 两个共享库之间关于哪个函数是dummy()存在分歧。 (将dummy()函数放在共享库中的单独目标文件中会很有趣;然后调用哪个版本的dummy()?)但是在显示的极其简单的场景中,dummy() function只能调用其中一个main()函数。 (请注意,我发现这种行为的平台之间存在差异并不会感到惊讶。我已经确定了我测试代码的位置。如果您在某个平台上发现不同的行为,请告诉我。)