目标文件和共享对象文件之间的关系

ASH*_*HOK 25 c++ linker shared-libraries object-files

shared object(.so)文件和object(.o)文件之间的关系是什么?

你可以通过例子解释一下吗?

qua*_*ark 71

假设你有以下C源文件,请调用它 name.c

#include <stdio.h>
#include <stdlib.h>

void print_name(const char * name)
{
    printf("My name is %s\n", name);
}
Run Code Online (Sandbox Code Playgroud)

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

00000000 T print_name
         U printf
Run Code Online (Sandbox Code Playgroud)

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

您可以逐个链接.o文件,但不要:通常有很多,并且它们是实现细节.你真的更喜欢将它们全部收集到相关对象的包中,并且具有公认的名称.这些包称为,它们有两种形式:静态和动态.

静态库(在UNIX中)几乎总是用后缀.a(例子包括libc.a其中是C核心库,libm.a其是C数学库)等.继续这个例子,您将构建静态库ar rc libname.a name.o.如果你跑nmlibname.a你会看到这个:

name.o:
00000000 T print_name
         U printf
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,它主要是一个包含查找其中所有名称的索引的目标文件的大表.就像目标文件一样,它包含每个中定义.o的符号和它们引用的符号.如果你要链接另一个 .o(例如date.oto print_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
Run Code Online (Sandbox Code Playgroud)

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

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

有一些缺点:

  • 将程序链接在一起需要时间.对于共享库,有时这些时间会延迟到每次运行可执行文件时.
  • 这个过程比较复杂.共享库中的所有其他符号都是在运行时使库链接所需的基础结构的一部分.
  • 您可能会遇到库的不同版本之间存在细微不兼容的风险.在Windows上,这称为"DLL地狱".

(如果你考虑一下这些是程序使用或不使用引用和指针的原因,而不是直接将类的对象嵌入到其他对象中.这个类比非常直接.)

好的,这是很多细节,我已经跳了很多,比如链接过程实际上是如何工作的.我希望你能遵循它.如果没有要求澄清.

  • 值得注意的是,共享库是实现插件的常用方法.插件是一种扩展应用程序功能而无需更改它的方法.应用程序可以通过调用带有库的路径的dlopen()作为参数来加载共享库,然后使用dlsym()来查找库中的特定符号(例如函数).比应用程序调用函数从库中执行功能. (8认同)
  • 对加载和链接世界的一个很好的介绍:) +1 (4认同)
  • @dma_k这里有一些错误的陈述:"当你的实际程序运行时,它需要找到所有引用的符号,并在其他目标文件中查找它们的定义,以便链接到一个完整的程序或完整的库" - 这实际上并不是在运行时发生的,它发生在静态链接时."如果您将静态库链接到可执行文件中,它会将整个库嵌入到可执行文件中" - 只是简单的错误. (3认同)

Jim*_*uck 1

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