【问题标题】:Relation between object file and shared object file目标文件和共享目标文件的关系
【发布时间】:2010-11-15 16:43:42
【问题描述】:

共享对象(.so)文件和对象(.o)文件之间的关系是什么?

你能举例说明一下吗?

【问题讨论】:

    标签: c++ linker shared-libraries object-files


    【解决方案1】:

    假设你有以下 C 源文件,命名为 name.c

    #include <stdio.h>
    #include <stdlib.h>
    
    void print_name(const char * name)
    {
        printf("My name is %s\n", name);
    }
    

    当你编译它时,使用cc name.c 生成name.o。 .o 包含 name.c 中定义的所有函数和变量的编译代码和数据,以及将它们的名称与实际代码相关联的索引。如果您查看该索引,例如使用 nm 工具(可在 Linux 和许多其他 Unix 上使用),您会注意到两个条目:

    00000000 T print_name
             U printf
    

    这意味着:.o 中存储了两个符号(函数或变量的名称,但不是类、结构或任何类型的名称)。第一个标记为T,实际上在name.o 中包含了它的定义。另一个标有U 的只是一个参考print_name 的代码可以在这里找到,但 printf 的代码不能。当您的实际程序运行时,它需要找到所有引用的符号并在 other 目标文件中查找它们的定义,以便将它们链接在一起成为一个完整的程序或完整的库。因此,目标文件是在源文件中找到的定义,转换为二进制形式,可用于放入完整的程序中。

    您可以将 .o 文件一个一个链接在一起,但不能:它们通常很多,而且它们是一个实现细节。您真的希望将它们全部收集到相关对象的捆绑中,并具有公认的名称。这些包称为,它们有两种形式:静态和动态。

    静态库(在 Unix 中)几乎总是以 .a 为后缀(例如,libc.a 是 C 核心库,libm.a 是 C 数学库)等等在。继续使用ar rc libname.a name.o 构建静态库的示例。如果你在libname.a 上运行nm,你会看到:

    name.o:
    00000000 T print_name
             U printf
    

    如您所见,它主要是一个包含目标文件的大表,其中包含一个查找所有名称的索引。就像目标文件一样,它包含每个.o 中定义的符号和它们引用的符号。如果您要在 another .o 中链接(例如,date.oprint_date),您会看到与上述类似的另一个条目。

    如果您将静态库链接到可执行文件,它会将整个库嵌入到可执行文件中。这就像链接所有单独的 .o 文件一样。正如您可以想象的那样,这会使您的程序变得非常大,尤其是在您使用(就像大多数现代应用程序一样)大量库的情况下。

    动态共享库后缀为.so。它,就像它的静态模拟一样,是一个大的目标文件表,引用了所有编译的代码。您可以使用cc -shared libname.so name.o 构建它。不过,使用nm 查看与静态库有很大不同。在我的系统上,它包含大约两打符号,其中只有两个是 print_nameprintf

    00001498 a _DYNAMIC
    00001574 a _GLOBAL_OFFSET_TABLE_
             w _Jv_RegisterClasses
    00001488 d __CTOR_END__
    00001484 d __CTOR_LIST__
    00001490 d __DTOR_END__
    0000148c d __DTOR_LIST__
    00000480 r __FRAME_END__
    00001494 d __JCR_END__
    00001494 d __JCR_LIST__
    00001590 A __bss_start
             w __cxa_finalize@@GLIBC_2.1.3
    00000420 t __do_global_ctors_aux
    00000360 t __do_global_dtors_aux
    00001588 d __dso_handle
             w __gmon_start__
    000003f7 t __i686.get_pc_thunk.bx
    00001590 A _edata
    00001594 A _end
    00000454 T _fini
    000002f8 T _init
    00001590 b completed.5843
    000003c0 t frame_dummy
    0000158c d p.5841
    000003fc T print_name
             U printf@@GLIBC_2.0
    

    共享库在一个非常重要的方面不同于静态库:它不会将自身嵌入到您的最终可执行文件中。相反,可执行文件包含对该共享库的引用,该引用不是在链接时而是在运行时解析的。这有很多优点:

    • 您的可执行文件要小得多。它仅包含您通过目标文件显式链接的代码。外部库是引用,它们的代码不会进入二进制文件。
    • 您可以在多个可执行文件之间共享(因此得名)一个库的位。
    • 如果您注意二进制兼容性,您可以在程序运行之间更新库中的代码,该程序将选择新库,而无需您更改它。

    有一些缺点:

    • 将程序链接在一起需要时间。对于共享库,其中一些时间会延迟到每次可执行文件运行时。
    • 过程更复杂。共享库中的所有附加符号都是使库在运行时链接所需的基础架构的一部分。
    • 您将面临库的不同版本之间存在细微不兼容的风险。在 Windows 上,这称为“DLL 地狱”。

    (如果你想一想,其中很多原因是程序使用或不使用引用和指针而不是直接将类的对象嵌入到其他对象中的原因。类比非常直接。)

    好的,这是很多细节,我已经跳过了很多,例如链接过程的实际工作原理。我希望你能跟随它。如果不要求澄清。

    【讨论】:

    • 值得注意的是,共享库是实现插件的常用方法。插件是一种无需更改即可扩展应用程序功能的方法。应用程序可以通过调用 dlopen() 来加载共享库,其中库的路径是一个参数,然后是 dlsym() 以在库中查找特定符号(例如函数)。比应用程序调用函数来执行库中的功能。
    • 对加载和链接世界的精彩介绍 :) +1
    • 您的回答包含惊人数量的不准确之处。您介意将其标记为社区 wiki,以便对其进行更正吗?
    • @quark:您提到链接静态库就像链接所有 .o 文件。 gcc 是否会真正将所有 .o 文件带入最终的可执行文件,还是只会选择那些实际引用的文件?我用这个(tenouk.com/Bufferoverflowc/Bufferoverflow1c.html)作为参考。
    • @dma_k 这里有一些错误的陈述:“当你的实际程序运行时,它需要找到所有引用的符号并在其他目标文件中查找它们的定义,以便将它们链接在一起形成一个完整的程序或完整的库”——这实际上不会发生在运行时,而是发生在静态链接时。 “如果您将静态库链接到可执行文件中,它会将整个库嵌入到可执行文件中”——纯属错误。
    【解决方案2】:

    .so 类似于 Windows 上的 .dll。 .o 与 Visual Studio 下的 .obj 完全相同。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-24
      • 1970-01-01
      • 1970-01-01
      • 2011-02-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多