我经常听到术语“静态链接”和“动态链接”,通常是指用C,C++或C#编写的代码。它们是什么,它们到底在谈论什么,它们在联系什么?
w80xi6nr1#
从源代码(您编写的代码)到可执行代码(您运行的代码)有两个阶段(在大多数情况下,不包括解释代码)。第一种是编译,它将源代码转换为目标模块。第二种是链接,它将目标模块组合在一起以形成可执行文件。区别在于,允许第三方库包含在您的可执行文件中,而无需查看其源代码(例如用于数据库访问,网络通信和图形用户界面的库),或者用于编译不同语言的代码(例如C和汇编代码),然后将它们链接在一起。当您将文件静态链接到可执行文件时,该文件的内容将在链接时包含在内。换句话说,文件的内容被物理地插入到您将要运行的可执行文件中。当你链接 * 动态 *,一个指向被链接的文件的指针(文件的文件名,例如)被包含在可执行文件中,并且所述文件的内容在链接时不被包含。只有当您稍后运行可执行文件时,这些动态链接的文件才会被引入,并且它们只被引入可执行文件的内存副本中,而不是磁盘上的副本中。它基本上是一种延迟链接的方法。还有一个更延迟的方法(在某些系统上称为延迟绑定),它不会引入动态链接的文件,直到您实际尝试调用其中的函数。静态链接的文件在链接时被“锁定”到可执行文件,因此它们永远不会更改。可执行文件引用的动态链接文件可以通过替换磁盘上的文件来更改。这允许在不必重新链接代码的情况下更新功能;加载程序每次运行时都会重新链接。这有好有坏-一方面,它允许更容易的更新和错误修复,另一方面,如果更新不兼容,它可能导致程序停止工作-这有时是一些人提到的可怕的“DLL地狱”的原因,即如果您将动态链接库替换为不兼容的库,应用程序可能会崩溃(顺便说一句,这样做的开发人员应该会被追捕并受到严厉的惩罚)。作为一个 * 示例 *,让我们看一下用户为静态和动态链接编译main.c文件的情况。
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来提供更新或错误修复。如前所述,这既有优点也有缺点。
whlutmcx2#
我认为这个问题的一个好答案应该解释什么是链接。例如,当你编译一些C代码时,它会被翻译成机器语言。只是一个字节序列,当运行时,会导致处理器进行加,减,比较,“后藤”,读内存,写内存,诸如此类的事情。这些内容存储在对象(.o)文件中。很久以前,计算机科学家发明了“子程序”这个东西。执行此代码块并返回此处。没过多久,他们就意识到最有用的子程序可以存储在一个特殊的地方,允许任何需要它们的程序使用。在早期,程序员必须输入这些子程序所在的内存地址。类似于CALL 0x5A62。如果这些存储器地址需要更改,这将是乏味和有问题的。所以,这个过程是自动化的。你写了一个调用printf()的程序,编译器不知道printf的内存地址。所以编译器只是写CALL 0x0000,并在对象文件中添加一个注解,说“必须用printf的内存位置替换这个0x0000”。静态链接意味着链接器程序(GNU的链接器程序称为ld)将printf的机器码直接添加到您的可执行文件中,并将0x0000更改为printf的地址。这发生在创建可执行文件时。动态链接意味着上述步骤不会发生。可执行文件 still 有一个注解,说“必须用printf的内存位置替换0x000”。操作系统的加载器需要找到printf代码,将其加载到内存中,并在每次运行程序时纠正CALL地址。程序通常会调用一些静态链接的函数(像printf这样的标准库函数通常是静态链接的)和其他动态链接的函数。当可执行文件运行时,静态的“成为”可执行文件的一部分,而动态的“加入”。这两种方法都有优点和缺点,并且在操作系统之间存在差异。但既然你没问我就到此为止吧。
CALL 0x5A62
printf()
printf
CALL 0x0000
qmelpv7a3#
静态链接库在编译时被链接。动态链接库在运行时加载。静态链接将库位烘焙到可执行文件中。动态链接只在对库的引用中进行烘焙;动态库的位存在于别处,并且可以在以后被换出。
s8vozzvw4#
因为上面的帖子 * 实际上都没有展示如何 * 静态链接一些东西,看看你做得对不对,所以我将解决这个问题:一个简单的C程序
#include <stdio.h> int main(void) { printf("This is a string\n"); return 0; }
动态链接C程序
gcc simpleprog.c -o simpleprog
然后在二进制文件上运行file:
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
在此静态链接的二进制文件上运行文件将显示:
现在的结果将是
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库(如musl)为几乎所有的库提供了静态链接选项。现在strace你已经创建的二进制文件,你可以看到在程序开始之前没有访问过库:
libtool
musl
strace
strace ./simpleprog
现在与strace在动态链接程序上的输出进行比较,您将看到静态链接版本的strace要短得多!
gk7wooem5#
(我不懂C#,但有一个VM语言的静态链接概念很有趣)动态链接涉及到知道如何找到所需的功能,你只有一个从你的程序引用。您的语言运行时或操作系统在文件系统、网络或编译代码缓存中搜索一段代码,匹配引用,然后采取几种措施将其集成到内存中的程序映像中,例如重新定位。它们都在运行时完成。它可以手动完成,也可以由编译器完成。有能力更新与混乱的风险(即DLL地狱)。静态链接是在编译时完成的,你告诉编译器所有的功能部分在哪里,并指示它集成它们。没有搜索,没有歧义,没有能力更新而不重新编译。你的所有依赖项都与你的程序映像物理上是一个。
5条答案
按热度按时间w80xi6nr1#
从源代码(您编写的代码)到可执行代码(您运行的代码)有两个阶段(在大多数情况下,不包括解释代码)。
第一种是编译,它将源代码转换为目标模块。
第二种是链接,它将目标模块组合在一起以形成可执行文件。
区别在于,允许第三方库包含在您的可执行文件中,而无需查看其源代码(例如用于数据库访问,网络通信和图形用户界面的库),或者用于编译不同语言的代码(例如C和汇编代码),然后将它们链接在一起。
当您将文件静态链接到可执行文件时,该文件的内容将在链接时包含在内。换句话说,文件的内容被物理地插入到您将要运行的可执行文件中。
当你链接 * 动态 *,一个指向被链接的文件的指针(文件的文件名,例如)被包含在可执行文件中,并且所述文件的内容在链接时不被包含。只有当您稍后运行可执行文件时,这些动态链接的文件才会被引入,并且它们只被引入可执行文件的内存副本中,而不是磁盘上的副本中。
它基本上是一种延迟链接的方法。还有一个更延迟的方法(在某些系统上称为延迟绑定),它不会引入动态链接的文件,直到您实际尝试调用其中的函数。
静态链接的文件在链接时被“锁定”到可执行文件,因此它们永远不会更改。可执行文件引用的动态链接文件可以通过替换磁盘上的文件来更改。
这允许在不必重新链接代码的情况下更新功能;加载程序每次运行时都会重新链接。
这有好有坏-一方面,它允许更容易的更新和错误修复,另一方面,如果更新不兼容,它可能导致程序停止工作-这有时是一些人提到的可怕的“DLL地狱”的原因,即如果您将动态链接库替换为不兼容的库,应用程序可能会崩溃(顺便说一句,这样做的开发人员应该会被追捕并受到严厉的惩罚)。
作为一个 * 示例 *,让我们看一下用户为静态和动态链接编译
main.c
文件的情况。在静态情况下,可以看到主程序和C运行时库在链接时(由开发人员)链接在一起。由于用户通常无法重新链接可执行文件,因此他们只能使用库的行为。
在动态的情况下,主程序与C运行时导入库(声明动态库中的内容,但实际上并不 * 定义 * 它)链接。这允许链接器即使在缺少实际代码的情况下也进行链接。
然后,在运行时,操作系统加载器进行主程序与C运行时DLL(动态链接库或共享库或其他命名法)的后期链接。
C运行时的所有者可以随时添加新的DLL来提供更新或错误修复。如前所述,这既有优点也有缺点。
whlutmcx2#
我认为这个问题的一个好答案应该解释什么是链接。
例如,当你编译一些C代码时,它会被翻译成机器语言。只是一个字节序列,当运行时,会导致处理器进行加,减,比较,“后藤”,读内存,写内存,诸如此类的事情。这些内容存储在对象(.o)文件中。
很久以前,计算机科学家发明了“子程序”这个东西。执行此代码块并返回此处。没过多久,他们就意识到最有用的子程序可以存储在一个特殊的地方,允许任何需要它们的程序使用。
在早期,程序员必须输入这些子程序所在的内存地址。类似于
CALL 0x5A62
。如果这些存储器地址需要更改,这将是乏味和有问题的。所以,这个过程是自动化的。你写了一个调用
printf()
的程序,编译器不知道printf
的内存地址。所以编译器只是写CALL 0x0000
,并在对象文件中添加一个注解,说“必须用printf的内存位置替换这个0x0000”。静态链接意味着链接器程序(GNU的链接器程序称为ld)将
printf
的机器码直接添加到您的可执行文件中,并将0x0000更改为printf
的地址。这发生在创建可执行文件时。动态链接意味着上述步骤不会发生。可执行文件 still 有一个注解,说“必须用printf的内存位置替换0x000”。操作系统的加载器需要找到printf代码,将其加载到内存中,并在每次运行程序时纠正CALL地址。
程序通常会调用一些静态链接的函数(像
printf
这样的标准库函数通常是静态链接的)和其他动态链接的函数。当可执行文件运行时,静态的“成为”可执行文件的一部分,而动态的“加入”。这两种方法都有优点和缺点,并且在操作系统之间存在差异。但既然你没问我就到此为止吧。
qmelpv7a3#
静态链接库在编译时被链接。动态链接库在运行时加载。静态链接将库位烘焙到可执行文件中。动态链接只在对库的引用中进行烘焙;动态库的位存在于别处,并且可以在以后被换出。
s8vozzvw4#
因为上面的帖子 * 实际上都没有展示如何 * 静态链接一些东西,看看你做得对不对,所以我将解决这个问题:
一个简单的C程序
动态链接C程序
然后在二进制文件上运行
file
:这将显示它是动态链接的,沿着以下路线:
这一次,让我们静态地链接程序:
在此静态链接的二进制文件上运行文件将显示:
现在的结果将是
你可以看到它是静态链接的。然而不幸的是,并不是所有的库都可以简单地以这种方式静态链接,并且可能需要使用
libtool
或手动链接目标代码和C库来进行扩展。幸运的是,许多嵌入式C库(如
musl
)为几乎所有的库提供了静态链接选项。现在
strace
你已经创建的二进制文件,你可以看到在程序开始之前没有访问过库:现在与
strace
在动态链接程序上的输出进行比较,您将看到静态链接版本的strace要短得多!gk7wooem5#
(我不懂C#,但有一个VM语言的静态链接概念很有趣)
动态链接涉及到知道如何找到所需的功能,你只有一个从你的程序引用。您的语言运行时或操作系统在文件系统、网络或编译代码缓存中搜索一段代码,匹配引用,然后采取几种措施将其集成到内存中的程序映像中,例如重新定位。它们都在运行时完成。它可以手动完成,也可以由编译器完成。有能力更新与混乱的风险(即DLL地狱)。
静态链接是在编译时完成的,你告诉编译器所有的功能部分在哪里,并指示它集成它们。没有搜索,没有歧义,没有能力更新而不重新编译。你的所有依赖项都与你的程序映像物理上是一个。