一、前言
大家都知道,一段程序的编译全过程,也就是程序运行的四个阶段,分为预处理、编译、汇编、链接,不清楚的可以看我之前写的文章,C/C++编译过程-优快云博客。
我在这里简单讲一下链接的概念,参考黑皮书《深入理解计算机系统》(csapp),现在有一个简单的hello程序,这个程序经过预处理、编译、汇编之后生成可重定位目标文件(hello.o),也是一个二进制文件,链接阶段,假如hello程序调用了printf函数,它是每个C编译器都提供的标准C库中的一个函数。printf函数存在于一个名为printf.o的单独的预编译好了的目标文件中,而这个文件必须以某种方式合并到我们的hello.0程序中。链接器(ld)就负责处理这种合并。结果就得到hello文件,它是一个可执行目标文件(或者简称为可执行文件),可以被加载到内存中,由系统执行。
用简单的话概述链接就是链接器将汇编生成的目标文件以及可能的库文件链接在一起,生成最终的可执行文件。链接分为静态链接和动态链接,下面我介绍一下关于这两个链接的一些知识以及面试常问的问题。
二、概念及优缺点
静态链接:静态链接是指在程序编译时,将所有需要的库文件和代码直接整合到可执行文件中的过程。这意味着当一个程序被编译时,所有需要的代码和资源都被包含在最终的可执行文件里。
例子:在静态链接中,链接器会将hello
程序的目标文件(假设为hello.o
)和printf.o等
文件合并,生成一个包含所有代码和数据的单一可执行文件hello
。
优点:
因为所有需要的代码都包含在内,所以不需要额外的库文件就可以运行,这使得部署和分发变得简单。
缺点:
1. 浪费内存空间:当多个进程静态链接同一个静态库时,会复制多个副本,每个程序链接静态库的时候都会链接一个副本到目标文件里,链接的进程越多,副本也就越多,在磁盘上占用的空间就会变大,程序运行的时候,也会占用内。
2. 程序的开发与发布流程受模块制约:只要有一个模块更新,那么就需要重新编译打包整个代码,这可能会导致更新和维护上的不便。
动态链接:动态链接是指程序在运行时才加载所需的库文件的过程。这意味着可执行文件在编译时并不包含所有的库代码,而是在运行时根据需要动态地加载这些库。
例子:在动态链接中,hello
程序的可执行文件在编译时并不包含printf
函数的实际代码。相反,它只包含对printf
函数的引用。当hello
程序运行时,动态链接器(如ld.so
或dyld
)负责在需要时加载printf
函数的代码。
优点:
库的更新不需要重新编译程序,只需要替换或更新库文件即可,这使得程序的维护和更新更加方便,解决了静态链接的缺陷,更适应现代的大规模的软件开发。
缺点:
1. 程序运行时需要依赖特定的库文件,如果这些文件不可用,程序可能无法运行。
2. 由于是运行时加载,可能会影响程序的前期执行性能。
三、面试常问的问题
1.为什么会出现动态链接?
动态链接出现的原因就是为了解决静态链接中提到的两个问题,一方面是空间浪费,另外一方面是更新困难。动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。
2.动态链接地址是如何重定位的呢?
虽然动态链接把链接过程推迟到了程序运行时,但是在形成可执行文件时(注意形成可执行文件和执行程序是两个概念),还是需要用到动态链接库。比如我们在形成可执行程序时,发现引用了一个外部的函数,此时会检查动态链接库,发现这个函数名是一个动态链接符号,此时可执行程序就不对这个符号进行重定位,而把这个过程留到装载时再进行。
3.静态链接的过程(底层)?
-
符号解析:链接器需要解析程序中所有符号(变量和函数)的引用。这包括识别每个符号的定义(即符号的值或内存地址)以及每个符号的引用(即在代码中对符号的使用)。链接器会将所有目标文件中的符号表合并到一个全局符号表中,并解决符号之间的依赖关系。
-
重定位:在这一步中,链接器会调整代码和数据中的地址引用。因为目标文件是独立的,它们中的地址引用需要被更新以反映它们在最终可执行文件中的实际位置。这个过程涉及到更新代码中的跳转指令和数据引用,确保它们指向正确的内存地址。
-
合并段:链接器会将所有目标文件中的相同类型的段(如代码段、数据段、BSS段等)合并到输出文件的相应段中。例如,所有目标文件的“.text”段会被合并到输出文件的“.text”段中。
-
生成可执行文件:完成上述步骤后,链接器会生成一个包含所有必需代码和数据的单一可执行文件。这个文件可以直接被操作系统加载和执行,因为所有的依赖关系都已经在文件中解析和定位好了。
4.动态库和静态库有什么区别?
静态库是一组预先编译好的代码集合,可以在编译时被链接到程序中,它们通常以.a
(在Unix-like系统中)或.lib
(在Windows系统中)为文件后缀。动态库是设计为在程序运行时被加载的库,在Unix-like系统中,它们通常以.so
为后缀;在Windows系统中,以.dll
为后缀。
5.VS中同一个项目,为什么生成静态库比生成动态库要大很多?
静态库由于在编译时完整复制代码、包含所有可能的函数、包含编译单元的中间文件信息以及不共享代码等特点,导致同一个项目其体积比动态库要大很多。
四、感悟
之所以写这篇文章是因为面试被拷打麻了,本来我一直以为我对这一块的内容知识掌握很好的,这个系列的问题,字节的面试官拷打了我将近10分钟,后面还问了很多关于编译器优化的问题,被拷打麻木了,今天就好好总结重新学一遍。
最后,如果觉得我的文章对你有帮,请点赞收藏加关注,谢谢大家。
参考链接
1.【计算机基础】静态链接与动态链接_静态链接和动态链接-优快云博客
3.《深入理解计算机系统》(CSAPP),也推荐大家没事可以多看看这本书,豆瓣评分9.7,我每次看都有不一样的收获。