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

时间:2009-07-31 05:12:20

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

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

你能通过例子解释一下吗?

2 个答案:

答案 0 :(得分:67)

假设您有以下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的代码不能。当您的实际程序运行时,它将需要找到所有引用的符号并在其他目标文件中查找它们的定义,以便链接到一个完整的程序或完整的库中。因此,目标文件是源文件中的定义,转换为二进制形式,可用于放入完整程序。

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

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

name.o:
00000000 T print_name
         U printf

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

如果将静态库链接到可执行文件中,它会将整个库嵌入到可执行文件中。这就像链接所有单个.o文件一样。你可以想象这可以使你的程序非常大,特别是如果你使用(像大多数现代应用程序一样)很多库。

动态共享库后缀为.so。它与静态模拟一样,是一个大型的目标文件表,指的是所有编译的代码。你用cc -shared libname.so name.o构建它。使用nm查看与静态库有很大不同。在我的系统上,它包含大约24个符号,其中只有两个是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地狱”。

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

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

答案 1 :(得分:1)

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