【发布时间】:2010-09-23 15:16:46
【问题描述】:
【问题讨论】:
标签: c# c++ linker static-linking dynamic-linking
【问题讨论】:
标签: c# c++ linker static-linking dynamic-linking
因为上述帖子实际上都没有显示如何静态链接某些东西并看到你做的正确,所以我会解决这个问题:
一个简单的 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要短得多!
【讨论】:
我认为这个问题的一个很好的答案应该解释什么是链接。
当您编译一些 C 代码(例如)时,它会被翻译成机器语言。只是一个字节序列,在运行时会导致处理器加、减、比较、“转到”、读取内存、写入内存等等。这些东西存储在对象 (.o) 文件中。
现在,很久以前,计算机科学家发明了这个“子程序”的东西。在此处执行此代码块并返回。不久之后,他们意识到最有用的子程序可以存储在一个特殊的地方,供任何需要它们的程序使用。
现在,在早期,程序员必须输入这些子程序所在的内存地址。类似于CALL 0x5A62。如果需要更改这些内存地址,这将是乏味且有问题的。
因此,该过程是自动化的。你写了一个调用printf()的程序,编译器不知道printf的内存地址。所以编译器只写CALL 0x0000,并在目标文件中添加一个注释,说“必须用printf的内存位置替换这个0x0000”。
静态链接是指链接器程序(GNU 称为ld)将printf 的机器代码直接添加到您的可执行文件中,并将0x0000 更改为printf 的地址。创建可执行文件时会发生这种情况。
动态链接意味着上述步骤不会发生。可执行文件 still 有一个注释,上面写着“必须用 printf 的内存位置替换 0x000”。操作系统的加载器需要找到 printf 代码,将其加载到内存中,并修正 CALL 地址,程序每次运行。
程序通常会调用一些静态链接的函数(标准库函数如printf 通常是静态链接的)和其他动态链接的函数。静态的“成为”可执行文件的一部分,而动态的“加入”可执行文件。
这两种方法各有利弊,操作系统之间也存在差异。不过既然你没问,那我就到此结束吧。
【讨论】:
ld 文档。
(在大多数情况下,不考虑解释代码)从源代码(您编写的内容)到可执行代码(您运行的内容)的两个阶段。
首先是将源代码转换为目标模块的编译。
第二个,链接,是将对象模块组合在一起形成可执行文件。
区别在于,除其他外,允许第三方库包含在您的可执行文件中,而您不会看到它们的源代码(例如用于数据库访问、网络通信和图形用户界面的库),或者用于在不同的语言(例如 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 以提供更新或错误修复。如前所述,这既有优点也有缺点。
【讨论】:
.dll 或 .so 扩展名) - 将答案视为解释 concepts 而不是作为一个准确的描述。而且,根据文本,这是一个仅显示 C 运行时文件的静态和动态链接的示例,所以,是的,这就是 `crt 在所有文件中所指示的内容。
(我不懂 C#,但有一个 VM 语言的静态链接概念很有趣)
动态链接涉及知道如何找到您只有在程序中引用的所需功能。您的语言运行时或操作系统在文件系统、网络或编译代码缓存上搜索一段代码,匹配引用,然后采取多种措施将其集成到内存中的程序映像中,例如重定位。它们都是在运行时完成的。它可以手动完成,也可以由编译器完成。有更新的能力有搞砸的风险(即,DLL 地狱)。
静态链接在编译时完成,您告诉编译器所有功能部分在哪里并指示它集成它们。没有搜索,没有歧义,没有重新编译就无法更新。您的所有依赖项都与您的程序映像在物理上是一体的。
【讨论】:
静态链接库在编译时被链接。动态链接库在运行时加载。静态链接将库位烘焙到您的可执行文件中。动态链接仅包含对库的引用;动态库的位存在于其他地方,以后可以换掉。
【讨论】: