答案 0 :(得分:403)
从源代码(您编写的内容)到可执行代码(您运行的内容)的两个阶段(在大多数情况下,折扣解释代码)。
第一个是将源代码转换为对象模块的编译。
第二个是链接,它将对象模块组合在一起形成可执行文件。
除其他外,区别在于,允许第三方库包含在您的可执行文件中,而无需查看其源代码(例如用于数据库访问的库,网络通信和图形用户界面),或者用于编译代码。不同的语言(例如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以提供更新或错误修复。如前所述,这有利有弊。
答案 1 :(得分:210)
我认为这个问题的一个好答案应该解释是的链接。
编译某些C代码(例如)时,它会被翻译成机器语言。只是一个字节序列,当运行时,会导致处理器添加,减去,比较,“转到”,读取内存,写入内存等等。这些东西存储在对象(.o)文件中。
现在,很久以前,计算机科学家发明了这种“子程序”的东西。执行 - 这 - 块-的代码和回报 - 在这里。不久之后,他们意识到最有用的子程序可以存储在一个特殊的地方,并被需要它们的任何程序使用。现在,在早期,程序员必须打入这些子程序所在的内存地址。像CALL 0x5A62
这样的东西。如果需要更改这些内存地址,这是繁琐且有问题的。
因此,该过程是自动化的。您编写了一个调用printf()
的程序,编译器不知道printf
的内存地址。所以编译器只写CALL 0x0000
,并在对象文件中添加一条注释,说“必须用 printf ”的内存位置替换这个0x0000。
静态链接意味着链接器程序(GNU称为ld)将printf
的机器代码直接添加到可执行文件中,并将0x0000更改为{{1}的地址}。创建可执行文件时会发生这种情况。
动态链接意味着上述步骤不会发生。可执行文件仍然有一个注释,表示“必须用printf的内存位置替换0x000”。操作系统的加载程序需要找到printf代码,将其加载到内存中,并在每次运行程序时更正CALL地址 。
程序通常会调用一些静态链接的函数(标准库函数,如printf
通常是静态链接的)和其他动态链接的函数。当可执行文件运行时,静态的“成为可执行文件的一部分”,动态的“加入”。
这两种方法都有优点和缺点,操作系统之间也存在差异。但既然你没有问,我会在这里结束。
答案 2 :(得分:29)
静态链接库在编译时链接。动态链接库在运行时加载。静态链接将库位烘焙到您的可执行文件中。动态链接仅烘焙对库的引用;动态库的位存在于别处,可以在以后换出。
答案 3 :(得分:12)
因为以上帖子中没有一个实际显示如何静态链接某些东西,并且看到你正确地做了这些,所以我将解决这个问题:
一个简单的C程序
#include <stdio.h>
int 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,未剥离”< / p>
相反,让我们这次静态链接程序:
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要短得多!
答案 4 :(得分:2)
(我不知道C#,但对于VM语言有一个静态链接概念很有趣)
动态链接涉及了解如何查找所需的功能,您只能从程序中获得参考。您可以在语言运行时或OS上搜索文件系统,网络或编译代码缓存上的一段代码,匹配引用,然后采取多种措施将其集成到内存中的程序映像,如重定位。它们都是在运行时完成的。它可以手动完成,也可以由编译器完成。有能力更新,有可能搞乱(即DLL地狱)。
静态链接在编译时完成,您告诉编译器所有功能部件的位置并指示它集成它们。没有搜索,没有歧义,没有重新编译就无法更新。您的所有依赖项都与您的程序映像完全相同。