c++ - 为什么链接器不提示重复的符号?

标签 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.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 中是否有一个目标文件同时定义了 dummyextra ,并且在库 2 中有一个目标文件定义了 dummyalternative ,并且代码需要 extra 的定义和 alternative — 那么你有 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() .

    关于:

    The loader seems always to choose dynamic libs and not compiled in the .o files.



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

    Does it means the same symbol is always loaded from .so file, if it is both in .a and .so file?



    不;这取决于首先遇到的是哪个。如果.a第一次遇到,.o文件被有效地从库复制到可执行文件中(并且共享库中的符号被忽略,因为在可执行文件中已经有它的定义)。如果.so第一次遇到,定义在.a被忽略,因为链接器不再寻找那个符号的定义——它已经有了。

    Does it mean that symbols in static symbol table in a static library are never in conflict with those in dynamic symbol table in .so file?



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

    If I link 2 shared libs, can I get conflicts and the link phase failed?



    正如我在评论中指出的那样:

    My immediate reaction is "Yes, you can". It would depend on the content of the two shared libraries, but you could run into problems, I believe. […cogitation…] How would you show this problem? … It's not as easy as it seems at first sight. What is required to demonstrate such a problem? … Or am I overthinking this? … […time to go play with some sample code…]



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

    使用上面显示的代码(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()之前returnalternative() 也是如此和 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
    $
    

    注意main()调用的函数是第一个出现在与其链接的库中的。但是(至少在 Mac OS X 10.10.5 上)链接器不会遇到冲突。但是请注意,每个共享对象中的代码都调用了 dummy() 的“自己的”版本。 — 两个共享库之间关于哪个函数是dummy() 存在分歧. (将 dummy() 函数放在共享库中的单独目标文件中会很有趣;然后会调用哪个版本的 dummy()?)但是在所示的极其简单的场景中,main()函数设法只调用 dummy() 之一职能。 (请注意,对于这种行为,发现平台之间的差异我不会感到惊讶。我已经确定了我测试代码的位置。如果您在某些平台上发现不同的行为,请告诉我。)

    关于c++ - 为什么链接器不提示重复的符号?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34454355/

    相关文章:

    c++ - 这是否像 learncpp.com 声称的那样是 GCC 错误?

    c++ - 链接器是做什么的?

    c++ - 将静态库与 c++/cmake 链接

    c++ - 是否可以在 catch 子句中使用 co_await?

    c++ - 应该 std::auto_ptr<>::operator = 重置/取消分配其现有指针?

    c++ - 段错误和内存泄漏

    docker - 在 docker 临时镜像中运行静态构建时的安全性?

    c - STM32F7-从RAM和闪存执行代码

    c++ - 为什么在 2 个 .CPP 文件中包含此头文件(带 Header Guard)会导致命名冲突?

    c++ - Delphi链接器和C++链接器的区别