“静态链接”和“动态链接”是什么意思?


Answers:


445

从源代码(您编写的内容)到可执行代码(您运行的内容)的获取过程(在大多数情况下,对解释代码进行打折)有两个阶段。

首先是编译,它将源代码转换为目标模块。

第二个链接是将对象模块组合在一起以形成可执行文件的链接。

区别在于:允许第三方库包含在您的可执行文件中,而您看不到它们的源代码(例如,用于数据库访问,网络通信和图形用户界面的库),或者用于编译不同语言的代码( 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,以提供更新或错误修复。如前所述,这具有优点和缺点。


11
如果我错了,请纠正我,但是在Windows上,即使软件是动态链接的,软件也会在安装时包括其自己的库。在许多具有程序包管理器的Linux系统上,许多动态链接的库(“共享对象”)实际上是在软件之间共享的。
保罗·费舍尔

6
@PaulF:诸如Windows通用控件,DirectX,.NET等之类的东西随应用程序一起发布,而在Linux上,您倾向于使用apt或yum或类似的东西来管理依赖项-因此,您是正确的。由于DLL趋向于不共享它们,因此Win App会发布自己代码。
paxdiablo

31
在地狱的第九圈中,有一个特殊的地方供那些更新其DLL并破坏向后兼容性的人使用。是的,如果接口消失或被修改,则动态链接将落入堆中。这就是为什么不应该这样做。一定要在您的DLL中添加一个function2(),但是如果人们正在使用它,请不要更改function()。最好的处理方法是对function()进行重新编码,使其调用function2(),但不要更改function()的签名。
paxdiablo

1
@Paul Fisher,我知道这很晚了,但是... Windows DLL附带的库不是完整的库,它只是一堆告诉链接器DLL包含什么的存根。链接器然后可以自动将信息放入.exe以加载DLL,并且符号不会显示为未定义。
Mark Ransom

1
@Santropedro,您在所有方面都是正确的,即lib,import和DLL名称的含义。后缀仅是约定俗成的,因此请不要过多地阅读(例如,DLL可能具有a .dll.so扩展名)-将答案视为解释概念而不是确切的描述。而且,根据本文,这是一个仅显示C运行时文件的静态和动态链接的示例,是的,这就是`crt在所有文件中所指示的内容。
paxdiablo

221

我认为一个好的答案,这个问题应该解释链接什么

例如,当您编译某些C代码时,会将其翻译为机器语言。只是一个字节序列,在运行时会导致处理器加,减,比较“ goto”,读取内存,写入内存之类的事情。这些东西存储在对象(.o)文件中。

现在,很久以前,计算机科学家发明了这种“子程序”。在此处执行此代码块并返回。不久之后,他们意识到最有用的子例程可以存储在一个特殊的位置,并可由需要它们的任何程序使用。

现在,在早期,程序员必须输入这些子例程所在的内存地址。有点像CALL 0x5A62。如果需要更改这些内存地址,这将很繁琐且成问题。

因此,该过程是自动化的。您编写了一个调用的程序printf(),而编译器不知道的内存地址。printf。因此,编译器只写CALL 0x0000,并在目标文件中添加一条注释,说“必须将0x0000替换为printf的存储位置”。

静态链接意味着链接器程序(GNU一个称为ld)将其printf机器代码直接添加到您的可执行文件中,并将0x0000更改为地址printf。创建可执行文件时会发生这种情况。

动态链接意味着上述步骤不会发生。可执行文件仍然有一条注释,指出“必须用printf的存储位置替换0x000”。每次运行程序时,操作系统的加载器都需要找到printf代码,将其加载到内存中,并更正CALL地址。

程序通常会调用某些将被静态链接的函数(标准库函数printf通常是静态链接的)和其他被动态链接的函数。运行可执行文件时,静态文件“成为可执行文件的一部分”,而动态文件“成为”一部分。

两种方法都有优点和缺点,并且操作系统之间也存在差异。但是,既然您不问,我就在这里结束。


4
我也做了,但是我只能选择1个答案。
UnkwnTech

1
Artelius,我正在深入了解您对这些疯狂的低级事物如何工作的解释。请回覆我们必须阅读的书籍,以深入了解上述内容。谢谢。
mahesh

1
抱歉,我无法推荐任何书籍。您应该首先学习汇编语言。然后,Wikipedia可以对此类主题进行适当的概述。您可能需要查看GNU ld文档。
Artelius

31

静态链接库在编译时被链接。动态链接的库在运行时加载。静态链接将库位烘焙到可执行文件中。动态链接仅烘焙对库的引用;动态库的位在其他位置,可以稍后交换。


16

由于以上所有帖子均未实际显示如何静态链接某些内容,并确保您正确地进行了链接,因此我将解决此问题:

一个简单的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,未剥离”

相反,这次让我们静态链接程序:

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短得多!


2

(我不懂C#,但是对VM语言使用静态链接概念很有趣)

动态链接涉及知道如何找到所需的功能,而这些功能只能从程序中获得参考。语言运行时或OS在文件系统,网络或编译后的代码缓存中搜索一段与引用匹配的代码,然后采取多种措施将其集成到内存中的程序映像中,例如重定位。它们都是在运行时完成的。它可以手动完成,也可以由编译器完成。有更新的能力,但有发生混乱的危险(即DLL地狱)。

静态链接是在编译时完成的,您可以告诉编译器所有功能部件在哪里,并指示其集成它们。没有搜索,没有歧义,没有重新编译就没有更新的能力。实际上,您的所有依赖项都与程序映像合一。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.