假设您有以下 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
实际上包含它的定义 in name.o
。另一个,标有U
仅仅是一个参考。代码为print_name
可以在这里找到,但是代码printf
不能。当您的实际程序运行时,它将需要找到所有引用的符号并在中查找它们的定义other目标文件,以便链接在一起形成完整的程序或完整的库。因此,目标文件是在源文件中找到的定义,转换为二进制形式,可用于放入完整的程序中。
您可以将 .o 文件一一链接在一起,但您不会这样做:它们通常有很多,而且它们是实现细节。您确实希望将它们全部收集到相关对象的捆绑包中,并具有易于识别的名称。这些捆绑包称为图书馆它们有两种形式:静态和动态。
A 静态库(在 Unix 中)几乎总是带有后缀.a
(示例包括libc.a
这是C核心库,libm.a
这是 C 数学库)等等。继续您构建静态库的示例ar rc libname.a name.o
。如果你跑nm
on libname.a
你会看到这个:
name.o:
00000000 T print_name
U printf
正如您所看到的,它主要是一个带有索引查找的目标文件大表all里面的名字。就像目标文件一样,它包含每个中定义的符号.o
以及它们所引用的符号。如果您要链接another.o(例如date.o
to print_date
),您会看到另一个与上面类似的条目。
如果将静态库链接到可执行文件中,它将整个库嵌入到可执行文件中。这就像链接所有个体一样.o
文件。正如您可以想象的那样,这可能会使您的程序变得非常大,尤其是当您使用(就像大多数现代应用程序一样)大量库时。
A dynamic or 共享库后缀为.so
。与它的静态类似物一样,它是一个大型目标文件表,引用所有已编译的代码。你会用它来构建它cc -shared libname.so name.o
。看着与nm
不过与静态库有很大不同。在我的系统上,它包含大约两打符号,其中只有两个是print_name
and printf
:
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 地狱”。
(如果您考虑一下,其中许多是程序使用或不使用引用和指针而不是直接将类的对象嵌入到其他对象中的原因。这个类比非常直接。)
好的,这是很多细节,我跳过了很多,例如链接过程的实际工作原理。我希望你能遵循它。如果没有要求澄清。