动态库的链接和加载是否都发生在运行时?还是只有库的加载发生在运行时?
动态库的链接和加载是否都发生在运行时?还是只有库的加载发生在运行时?
请参考前面关于静态链接和动态链接之间区别的优点。假设您指的是动态链接,那么:
加载和(动态)链接都由链接器完成 - 在Linux和其他类Unix操作系统上,这是由/lib/ld.so
执行的,这几乎在所有情况下都是由操作系统启动的实际程序。ld.so
将您的应用程序- mygameBinary
加载到内存中,然后从文件mygameBinary
读取所需的动态链接库列表。
链接器ld.so
会依次加载每个库到内存中,例如libc.so
、libpthread.so
、libopengl.so
,并查看这些库可能需要哪些其他库,例如libm.so
。
一旦加载完成,就开始进行链接的过程,这是一个查看由一个库或应用程序导出的命名对象或函数,并被另一个库或应用程序导入的过程。然后,链接器更改各种引用和有时代码,以更新每个库中未链接的数据指针和函数调用的位置,使其指向实际的数据或函数。例如,在mygameBinary
中对printf
的调用一开始指向空白(实际上只是调用链接器),但在链接之后变成了跳转到libc
中的printf
函数。
一旦链接完成,应用程序就会启动,通过调用mygameBinary
中的_start
函数,然后调用main
,你的游戏就开始了。
以这种方式进行动态链接是为了支持以下内容:
一些操作系统在细节上有所不同,例如OSX和AIX都将某些库预装到内存中的固定位置。这意味着它们不需要被加载,只需链接即可,这可能会更快。
一些操作系统(如OSX和Linux)支持预链接(pre-linking),这是一个在你启动应用程序之前运行在你的系统上的脚本,它完成链接。当你启动它们时,你就不需要再链接它们了。这很重要,因为链接在启动应用程序时会占用相当多的计算机时间,有些应用程序可能会在一秒钟内启动多次,比如在应用程序构建过程中的gcc
、cpp
和as
,或者在索引计算机数据时的过滤脚本(例如OSX的Spotlight)。
链接是将一些较小的可执行文件连接在一起,形成一个较大的可执行文件的过程。
加载是在执行之前将可执行文件加载到内存中的过程。
连接有两种类型:静态连接和动态连接。
静态连接发生在编译时,因此它发生在加载程序之前。使用静态连接时,您的程序使用的外部符号(例如函数名称)在编译时解析。
动态连接发生在运行时,因此它发生在加载程序时或加载后。使用动态链接时,符号可以在加载时解析,也可以在访问符号时运行时解析(延迟绑定)。后者更为常见。
这两个过程都发生在动态库的运行时。
首先,加载库及其所有依赖项(以及这些库的依赖项等)。然后,动态链接器解析已加载库中的符号。通常,这两个功能由同一软件实现;在Linux上,它是ld.so。
静态库中的符号在链接时解析并包含在可执行文件本身中。但是,静态库可能具有未解决的符号,在运行时由动态库满足。
如何发生这种情况,名称如何散列,运行时解析符号的成本等都在How to Write Shared Libraries中进行了详细描述。
file01.c, file02.c --> 生成 --> file01.o, file02.o --> 这些 .o 信息被合并并放入一个单独的动态库中,即 lib1.a file11.c, file12.c --> 生成 --> file11.o, file12.o --> 这些 .o 信息被合并并放入一个单独的动态库中,即 lib2.a
现在,我有两个库最终链接在一起生成可执行文件(如 .elf 或 .mot 或 .fls)。将来自 lib1.a 和 lib2.a 的信息链接成一个单独的可执行文件的过程称为链接。
现在,我需要将其加载到内存中以运行它以查看最终可执行文件的行为。将最终可执行文件(如 .elf 或 .mot 或 .fls)加载到内存中以运行它的过程称为加载。
希望这能清楚地说明链接和加载的重要性(尽管定义不太恰当 :-)).
Windows和Unix系统在动态库方面采用完全不同的方法。
Windows DLLs没有被链接。因此,您无法在DLL之间共享静态对象。就像您地址空间中的一个单独程序。
Unix共享对象在运行时确实是“链接”的,就像同一项目的不同模块一样,执行符号解析。
动态链接和库加载都发生在运行时,但动态链接是在程序执行之前由系统链接器完成的。因此,例如,如果所需的库缺失,则无法执行程序。另一方面,库加载是由程序本身通过dlopen / LoadLibrary函数完成的。在这种情况下,加载过程由应用程序控制,可以处理错误等。