我经常听到术语“静态链接”和“动态链接”,通常指的是用C、c++或c#编写的代码。它们是什么,它们到底在谈论什么,它们连接了什么?


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


从源代码(您编写的代码)到可执行代码(您运行的代码)有两个阶段(在大多数情况下,不考虑解释代码)。

第一个是编译,它将源代码转换为对象模块。

第二种方法是链接,将目标模块组合在一起以形成可执行文件。

其中的区别在于,允许第三方库包含在可执行文件中而不需要看到它们的源代码(例如用于数据库访问、网络通信和图形用户界面的库),或者用于用不同语言编译代码(例如C语言和汇编代码),然后将它们链接在一起。

当您静态地将文件链接到可执行文件时,该文件的内容将在链接时包含在内。换句话说,文件的内容被物理地插入到您将要运行的可执行文件中。

动态链接时,指向被链接的文件的指针(例如,文件的文件名)包含在可执行文件中,而该文件的内容在链接时不包括在内。只有当您稍后运行可执行文件时,这些动态链接的文件才会被引入,而且它们只会被带入可执行文件的内存副本,而不是磁盘副本。

它基本上是一种延迟链接的方法。还有一种延迟性更强的方法(在某些系统上称为晚绑定),它不会引入动态链接的文件,直到您真正尝试调用其中的函数。

静态链接文件在链接时被“锁定”到可执行文件,因此它们永远不会改变。可执行文件引用的动态链接文件可以通过替换磁盘上的文件进行更改。

这允许更新功能,而无需重新链接代码;每次运行加载器时,加载器都会重新链接。

这是两个好的和坏的,一方面,它允许更容易更新和bug修复,另一方面它可以导致程序停止工作,如果更新不兼容——这是有时负责可怕的“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#,但对于虚拟机语言来说,有一个静态链接的概念是很有趣的)

Dynamic linking involves knowing how to find a required functionality which you only have a reference from your program. You language runtime or OS search for a piece of code on the filesystem, network or compiled code cache, matching the reference, and then takes several measures to integrate it to your program image in the memory, like relocation. They are all done at runtime. It can be done either manually or by the compiler. There is ability to update with a risk of messing up (namely, DLL hell).

静态链接是在编译时完成的,你告诉编译器所有功能部分在哪里,并指示它集成它们。没有搜索,没有歧义,不能在不重新编译的情况下更新。所有依赖项都与程序映像在物理上是一体的。


我认为这个问题的一个很好的答案应该解释什么是连接。

当你编译一些C代码时(例如),它会被翻译成机器语言。只是一个字节序列,当它运行时,会导致处理器进行加、减、比较、“goto”、读内存、写内存,诸如此类的事情。这些东西存储在object (.o)文件中。

很久以前,计算机科学家发明了这种“子程序”的东西。Execute-this-chunk-of-code-and-return-here。没过多久,他们就意识到最有用的子程序可以存储在一个特殊的地方,供任何需要它们的程序使用。

在早期,程序员必须输入这些子程序所在的内存地址。类似于CALL 0x5A62。如果这些内存地址需要更改,那么这是非常繁琐和有问题的。

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

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

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

程序调用一些静态链接的函数(如printf等标准库函数通常是静态链接的)和其他动态链接的函数是很常见的。当可执行文件运行时,静态的“成为”可执行文件的“一部分”,动态的“加入”。

这两种方法各有优缺点,操作系统之间也存在差异。但既然你没问,我就说到这里吧。


因为上面没有一篇文章真正展示了如何静态链接一些东西,并看到你做得正确,所以我将解决这个问题:

一个简单的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-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, BuildID[sha1]=0xf715572611a8b04f686809d90d1c0d75c6028f0f, not stripped

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

gcc simpleprog.c -static -o simpleprog

运行这个静态链接的二进制文件将显示:

file simpleprog 

现在的结果将是

simpleprog: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.26, BuildID[sha1]=0x8c0b12250801c5a7c7434647b7dc65a644d6132b, not stripped

你可以看到它是愉快的静态链接。然而遗憾的是,并不是所有的库都可以简单地以这种方式进行静态链接,可能需要使用libtool进行扩展,或者手工链接目标代码和C库。

幸运的是,许多嵌入式C库,如must,为几乎所有(如果不是全部的话)库提供静态链接选项。

现在对你创建的二进制文件进行压缩,你可以看到在程序开始之前没有库被访问:

strace ./simpleprog

现在与动态链接程序中的strace输出进行比较,您将看到静态链接版本的strace要短得多!