Answers:
从源代码(您编写的内容)到可执行代码(您运行的内容)的获取过程(在大多数情况下,对解释代码进行打折)有两个阶段。
首先是编译,它将源代码转换为目标模块。
第二个链接是将对象模块组合在一起以形成可执行文件的链接。
区别在于:允许第三方库包含在您的可执行文件中,而您看不到它们的源代码(例如,用于数据库访问,网络通信和图形用户界面的库),或者用于编译不同语言的代码( 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
扩展名)-将答案视为解释概念而不是确切的描述。而且,根据本文,这是一个仅显示C运行时文件的静态和动态链接的示例,是的,这就是`crt在所有文件中所指示的内容。
我认为一个好的答案,这个问题应该解释链接什么是。
例如,当您编译某些C代码时,会将其翻译为机器语言。只是一个字节序列,在运行时会导致处理器加,减,比较“ goto”,读取内存,写入内存之类的事情。这些东西存储在对象(.o)文件中。
现在,很久以前,计算机科学家发明了这种“子程序”。在此处执行此代码块并返回。不久之后,他们意识到最有用的子例程可以存储在一个特殊的位置,并可由需要它们的任何程序使用。
现在,在早期,程序员必须输入这些子例程所在的内存地址。有点像CALL 0x5A62
。如果需要更改这些内存地址,这将很繁琐且成问题。
因此,该过程是自动化的。您编写了一个调用的程序printf()
,而编译器不知道的内存地址。printf
。因此,编译器只写CALL 0x0000
,并在目标文件中添加一条注释,说“必须将0x0000替换为printf的存储位置”。
静态链接意味着链接器程序(GNU一个称为ld)将其printf
机器代码直接添加到您的可执行文件中,并将0x0000更改为地址printf
。创建可执行文件时会发生这种情况。
动态链接意味着上述步骤不会发生。可执行文件仍然有一条注释,指出“必须用printf的存储位置替换0x000”。每次运行程序时,操作系统的加载器都需要找到printf代码,将其加载到内存中,并更正CALL地址。
程序通常会调用某些将被静态链接的函数(标准库函数printf
通常是静态链接的)和其他被动态链接的函数。运行可执行文件时,静态文件“成为可执行文件的一部分”,而动态文件“成为”一部分。
两种方法都有优点和缺点,并且操作系统之间也存在差异。但是,既然您不问,我就在这里结束。
ld
文档。
由于以上所有帖子均未实际显示如何静态链接某些内容,并确保您正确地进行了链接,因此我将解决此问题:
一个简单的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短得多!