我经常听到"静态链接"和"动态链接"这两个术语,通常是用C,C++或C#编写的代码,但我对这两者都知之甚少.他们是什么,他们到底在说什么,他们连接的是什么?
从源代码(您编写的内容)到可执行代码(您运行的代码)的两个阶段(在大多数情况下,折扣解释代码).
第一个是将源代码转换为对象模块的编译.
第二个是链接,它将对象模块组合在一起以形成可执行文件.
除其他外,区别在于,允许第三方库包含在您的可执行文件中,而无需查看其源代码(例如用于数据库访问,网络通信和图形用户界面的库),或用于编译不同语言的代码(例如C和汇编代码)然后将它们全部链接在一起.
当你静态文件链接到一个可执行文件,该文件的内容都包含在链接时.换句话说,文件的内容将物理插入到您将运行的可执行文件中.
当您动态链接时,指向要链接的文件的指针(例如,文件的文件名)包含在可执行文件中,并且链接时不包括所述文件的内容.只有当您稍后运行可执行文件时才会购买这些动态链接文件,并且它们只会被购买到可执行文件的内存副本,而不是磁盘上的副本.
它基本上是一种延迟链接的方法.还有一个更加延迟的方法(在某些系统上称为后期绑定),在您实际尝试调用其中的函数之前,它不会引入动态链接文件.
静态链接的文件在链接时被"锁定"到可执行文件,因此它们永远不会更改.可执行文件引用的动态链接文件只需更换磁盘上的文件即可更改.
这允许更新功能而无需重新链接代码; 每次运行时,加载程序都会重新链接.
这既好又坏 - 一方面,它允许更容易的更新和错误修复,另一方面它可以导致程序停止工作,如果更新不兼容 - 这有时负责可怕的"DLL地狱",有些人如果用一个不兼容的库替换一个动态链接的库,那么应用程序就可以被破坏了(顺便说一句,这样做的开发人员应该会被追捕并严厉惩罚).
作为示例,让我们看一下用户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运行时库在链接时链接在一起(由开发人员).由于用户通常无法重新链接可执行文件,因此他们会遇到库的行为.
在动态情况下,主程序与C运行时导入库链接(某些东西声明了动态库中的内容但实际上没有定义它).即使实际代码丢失,这也允许链接器链接.
然后,在运行时,操作系统加载程序执行主程序与C运行时DLL(动态链接库或共享库或其他命名法)的后期链接.
C运行时的所有者可以随时删除新的DLL以提供更新或错误修复.如前所述,这有利有弊.
我认为一个好的答案,这个问题应该解释链接什么是.
当您编译一些C代码(例如)时,它将被转换为机器语言.只是一个字节序列,当运行时,会导致处理器添加,减去,比较,"转到",读取内存,写入内存等等.这些东西存储在对象(.o)文件中.
现在,很久以前,计算机科学家发明了这种"子程序"的东西.执行 - 这 - 块-的代码和回报 - 在这里.不久之后,他们意识到最有用的子程序可以存储在一个特殊的地方,并被需要它们的任何程序使用.
现在,在早期,程序员必须打入这些子程序所在的内存地址.有点像CALL 0x5A62
.如果需要更改那些内存地址,这是繁琐且有问题的.
因此,该过程是自动化的.你编写一个调用的程序,printf()
编译器不知道的内存地址printf
.所以编译器只是写入CALL 0x0000
,并在对象文件中添加一条注释,说"必须将此0x0000替换为printf的内存位置".
静态链接意味着链接器程序(GNU的一个名为ld)将printf
机器代码直接添加到可执行文件中,并将0x0000更改为地址printf
.创建可执行文件时会发生这种情况.
动态链接意味着上述步骤不会发生.可执行文件仍然有一个注释"必须用printf的内存位置替换0x000".每次运行程序时,操作系统的加载程序都需要找到printf代码,将其加载到内存中并更正CALL地址.
程序调用一些静态链接的函数(标准库函数,如printf
通常是静态链接的)和其他动态链接的函数是很常见的.静态的"成为可执行文件的一部分",动态的"加入"可执行文件运行时.
这两种方法都有优点和缺点,操作系统之间也存在差异.但既然你没有问,我会在这里结束.
静态链接库在编译时链接.动态链接库在运行时加载.静态链接将库位烘焙到您的可执行文件中.动态链接仅烘焙对库的引用; 动态库的位存在于别处,可以在以后换出.
因为以上帖子中没有一个实际显示如何静态链接某些内容并且看到您正确地执行了此操作所以我将解决此问题:
一个简单的C程序
#includeint main(void) { printf("This is a string\n"); return 0; }
动态链接C程序
gcc simpleprog.c -o simpleprog
并运行file
二进制文件:
file simpleprog
这将表明它是动态链接的东西:
"simpleprog:ELF 64位LSB可执行文件,x86-64,版本1(SYSV),动态链接(使用共享库),用于GNU/Linux 2.6.26,BuildID [sha1] = 0xf715572611a8b04f686809d90d1c0d75c6028f0f,未剥离"
相反,让我们这次静态链接程序:
gcc simpleprog.c -static -o simpleprog
在此静态链接二进制文件上运行文件将显示:
file simpleprog
"simpleprog:ELF 64位LSB可执行文件,x86-64,版本1(GNU/Linux),静态链接,用于GNU/Linux 2.6.26,BuildID [sha1] = 0x8c0b12250801c5a7c7434647b7dc65a644d6132b,未剥离"
而且你可以看到它是静态链接的.遗憾的是,并非所有库都很容易以这种方式静态链接,并且可能需要libtool
手动使用或链接目标代码和C库.
幸运的是,许多嵌入式C库musl
为几乎所有(如果不是全部)库提供了静态链接选项.
现在strace
你创建了二进制文件,你可以看到在程序开始之前没有访问过的库:
strace ./simpleprog
现在比较strace
动态链接程序的输出,你会发现静态链接版本的strace要短得多!