“静态链接”和“dynamic链接”是什么意思?
我经常听到“静态链接”和“dynamic链接”这两个术语,通常涉及用C , C ++或C#编写的代码,但对于这两者我都不太了解。 他们究竟是什么,他们到底在说什么,他们之间有什么联系?
有(在大多数情况下,折扣解释代码)从源代码(你写的)到可执行代码(你运行的)获取两个阶段。
首先是将源代码转换成目标模块的编译。
第二,链接是将对象模块组合在一起形成一个可执行文件。
除此之外,还可以区分第三方库是否包含在您的可执行文件中,而不必看到它们的源代码(例如数据库访问库,networking通信和graphics用户界面),或者用不同的语言编译代码C和汇编代码),然后将它们链接在一起。
当您将文件静态链接到可执行文件时,该文件的内容将在链接时包含在内。 换句话说,文件的内容被物理地插入到您要运行的可执行文件中。
当您dynamic链接时,可执行文件中包含指向要链接文件的指针(例如,文件的文件名),并且链接时不包含该文件的内容。 只有在以后运行可执行文件时,才会购买这些dynamic链接的文件,而且只能将其购买到可执行文件的内存副本中,而不是磁盘上的副本。
这基本上是延迟链接的一种方法。 还有一种更延迟的方法(在某些系统上称为后期绑定),除非实际尝试调用其中的函数,否则不会引入dynamic链接的文件。
静态链接的文件在链接时被“locking”到可执行文件,所以它们永远不会改变。 由可执行文件引用的dynamic链接文件可以通过replace磁盘上的文件来更改。
这允许更新function而不必重新链接代码; 加载程序每次运行时都会重新链接。
这既好又坏 – 一方面,它允许更容易的更新和错误修复,另一方面它可能导致程序停止工作,如果更新是不兼容的 – 这有时是负责可怕的“DLL地狱”,有些人如果将dynamic链接的库replace为不兼容的库(这样做的开发者应该期望被追捕并严惩,那么应用程序可以被破坏)。
作为一个例子 ,我们来看一下用户为了静态和dynamic链接而编译main.c
文件的情况。
Phase Static Dynamic -------- ---------------------- ------------------------ +---------+ +---------+ | main.c | | main.c | +---------+ +---------+ Compile........|.........................|................... +---------+ +---------+ +---------+ +--------+ | main.o | | crtlib | | main.o | | crtimp | +---------+ +---------+ +---------+ +--------+ Link...........|..........|..............|...........|....... | | +-----------+ | | | +---------+ | +---------+ +--------+ | main |-----+ | main | | crtdll | +---------+ +---------+ +--------+ Load/Run.......|.........................|..........|........ +---------+ +---------+ | | main in | | main in |-----+ | memory | | memory | +---------+ +---------+
您可以在静态情况下看到主程序和C运行时库在链接时(由开发人员)链接在一起。 由于用户通常不能重新链接可执行文件,因此他们被卡在图书馆的行为中。
在dynamic情况下,主程序与C运行时导入库(声明dynamic库中的内容但实际上没有定义它的东西)链接。 即使实际的代码丢失,这也允许链接器链接。
然后,在运行时,操作系统加载程序将主程序与C运行时DLL(dynamic链接库或共享库或其他术语)进行后期链接。
C运行时的所有者可以随时放入一个新的DLL来提供更新或错误修复。 如前所述,这既有优点也有缺点。
我认为这个问题的一个很好的答案应该解释什么是链接。
当你编译一些C代码(例如)时,它被翻译成机器语言。 只是一个字节序列,运行时会导致处理器添加,减去,比较,“转到”,读取内存,写入内存等等。 这东西存储在对象(.o)文件中。
现在,很久以前,计算机科学家发明了这个“子程序”的东西。 执行 – 这 – 块-的代码和回报 – 在这里。 不久之后,他们意识到最有用的子程序可以存储在一个特殊的地方,并被任何需要它们的程序所使用。
现在,程序员在初期将不得不打开这些子程序所在的内存地址。 就像CALL 0x5A62
。 如果这些内存地址需要改变,那么这是很乏味和有问题的。
所以,这个过程是自动的。 你写一个调用printf()
的程序,编译器不知道printf
的内存地址。 因此,编译器只是写CALL 0x0000
,并添加一个注释到对象文件,说“必须用printf的内存位置replace这个0x0000”。
静态链接意味着链接器程序(GNU之一被称为ld )将printf
的机器代码直接添加到您的可执行文件,并将0x0000更改为printf
的地址。 当您的可执行文件被创build时会发生这种情况
dynamic链接意味着上述步骤不会发生。 可执行文件仍然有一个说明“必须用printf的内存位置replace0x000”。 操作系统的加载程序需要findprintf代码,将其加载到内存中,并在每次程序运行时更正CALL地址。
程序调用一些静态链接的printf
是很常见的(像printf
这样的标准库函数通常是静态链接的)和其他dynamic链接的函数。 静态的“成为可执行文件的一部分”,dynamic文件在可执行文件运行时“join”。
两种方法都有优缺点,操作系统也有差异。 但既然你没有问,我会在这里结束。
静态链接库在编译时链接。 dynamic链接库在运行时加载。 静态链接将库位打包到可执行文件中。 dynamic链接只能烘焙参考图书馆; dynamic库的位存在于其他位置,并可以稍后换出。
因为以上所有post都没有显示如何静态链接某些内容,并且看到您做了正确的事情,所以我会解决这个问题:
一个简单的C程序
#include <stdio.h> int main(void) { printf("This is a string\n"); return 0; }
dynamic链接C程序
gcc simpleprog.c -o simpleprog
并在二进制file
上运行:
file simpleprog
这将显示它是dynamic链接的东西沿线:
“对于GNU / Linux 2.6.26,BuildID [sha1] = 0xf715572611a8b04f686809d90d1c0d75c6028f0f,未被剥离的”simpleprog:ELF 64位LSB可执行文件,x86-64,版本1(SYSV),dynamic链接(使用共享库)
相反,让我们这次静态链接程序:
gcc simpleprog.c -static -o simpleprog
在这个静态链接的二进制文件上运行文件将显示:
file simpleprog
“simpleprog:GNU / Linux 2.6.26,BuildID [sha1] = 0x8c0b12250801c5a7c7434647b7dc65a644d6132b,静态链接的非易失性64位LSB可执行文件,x86-64,版本1(GNU / Linux)
你可以看到它是愉快的静态链接。 可悲的是,不是所有的库都可以通过这种方式进行静态链接,而且可能需要使用libtool
进行扩展,或者手工链接目标代码和C库。
幸运的是,许多像musl
这样的embedded式C库为几乎所有的库提供了静态链接选项。
现在对你创build的二进制文件进行处理,你可以看到程序开始之前没有任何库被访问:
strace ./simpleprog
现在比较dynamic链接程序的strace
输出,你会发现静态链接版本的strace要短得多!
(我不知道C#,但有一个VM语言的静态链接概念是有趣的)
dynamic链接涉及知道如何find一个你需要从程序中引用的function。 您的语言运行时或操作系统在文件系统,networking或编译代码caching中search一段代码,与引用匹配,然后采取多种措施将其集成到内存中的程序映像中,如重定位。 它们全部在运行时完成。 可以手动完成,也可以通过编译器完成。 有能力更新与搞乱(即DLL地狱)的风险。
静态链接是在编译时完成的,你告诉编译器所有function部件在哪里,并指示它将它们集成在一起。 没有search,没有歧义,没有能力更新没有重新编译。 您的所有依赖关系与您的程序映像在物理上是一样的。