彻底理解链接器:一

article/2025/10/13 17:42:58

目录

什么是链接器(Linker)

链接器可操作的元素

链接器是如何工作的

过程一:符号决议

c源文件中都有什么

目标文件里有什么

符号表(Symbol table)

符号表存放在哪里

符号决议的过程

实例说明undefined reference

过程二:库、可执行文件的生成

静态库

静态连接

静态链接下可执行文件的生成

动态库

动态链接

动态链接下可执行文件的生成

动态库vs静态库

过程三:重定位

编译器的工作

链接器的工作

问题:为什么链接器能确定运行时地址

大型项目是如何被构建(build)出来的

目标是如何实现的

make

构建大型项目

源码组织方式

make的执行过程

总结


在介绍本章主题之前,我们先来看几个问题:

问题一

写C/C++的同学应该经常遇到这样的一个Error:

    `undefined reference to ABC`

在遇到这样的问题时你知道这背后到底哪里出问题了吗? 你通常都能顺利解决类似问题吗?

问题二

作为世界上最大的同性交友网站GitHub,里面有很多很棒的项目,一般我们或者直接下载其发布版(release version),或者下载源码自己编译,不管是直接下载发布版还是自己编译,最终都会得到一个(或几个)以.so或者.a为结尾的文件(Windows下为DLL文件或者lib文件),这时你知道该怎么把这些.so或者.a文件引入你自己的项目吗?当然如果你去搜索一下也能得到答案,但是你知道这些答案背后的原理吗?

问题三
你的同学、同事在工作学习中可能不时就会提及到静态链接库动态链接库静态链接动态链接,每次听到这些词汇的时候在你脑海里,A)对此有很清晰的认知;B)一头雾水不知道他们在说些什么,你属于A还是B?

如果你还不能很好的解决上面前两个问题且对于问题三属于B,那么接下来你就要好好看这篇文章啦,解决这几个问题的关键就是这篇文章要介绍的链接器(Linker),虽然现代的集成开发环境IDE比如Visual Studio已经对程序员屏蔽了大部分链接器的工作,但理解链接器将极大提高你对工程的驾驭能力,也许你现在还不是很清楚,读完这篇文章你就能明白啦。

什么是链接器(Linker)


   让我们引用维基百科中对链接器的定义:
   

"a linker or link editor is a computer utility program that takes one or more object files generated by a compiler and combines them into a single executable file, library file, or another 'object' file."

   如果你看不太懂没有关系,我来翻译一下,链接器是一个将编译器产生的目标文件打包成可执行文件或者库文件或者目标文件的程序。这个翻译比较拗口,不太好理解,这句话的意思具体如下:
   首先是链接器的本质,链接器本质上也是一个程序,本质上和我们经常使用的普通程序没什么不同。
   其次是链接器的输入,我们经常使用的程序比如播放器,其输入是一个MP4文件,而链接器的输入是编译器编译好的目标文件(object file,如果你不理解什么是目标文件,请参考之前的文章《不简单的hello world之C标准库》)。
   最后是链接器的输出,链接器在将目标文件打包处理后,生成或者可执行文件,或者库,或者目标文件。
   从这个定义中能够看出,链接器的作用有点类似于我们经常使用的压缩软WinRAR(Linux下是tar),压缩软件将一堆文件打包压缩成一个压缩文件,而链接器和压缩软件的区别在于链接器是将多个目标文件打包成一个文件而不进行压缩。那么链接器到底是如何工作的呢,我们接着往下看。


链接器可操作的元素


链接器可操作的最小元素是一个简单的目标文件,通常我们写的.c源文件编译后就生成了对应的目标文件,我们写的实现文件比如list.c编译后就生成了对应的目标文件list.o(Windows下为list.obj),这个list.o就是链接器可以操作的最小元素。我们见到的所有应用程序,小到自己实现的hello world程序,大到复杂的比如浏览器,网络服务器等,都是链接器将一个个所需要用到的目标文件汇集起来最终形成了非常复杂的应用程序(Windows下是我们常见的EXE文件,Linux下为ELF文件)。

我们可以把最终的应用程序想象成一座房子,构建房子的最基本的原材料就是砖,房子中各个模块像墙面,地面,屋顶等都是由一块块砖构筑成的。而这里的目标文件就好比构建房子时最基本的砖。房子的各个模块就好比我们是用的静态库,动态库。无论多么复杂庞大的应用程序,对于链接器来说最基本的构建材料都是目标文件。链接器可以将目标文件链接器成为各种库以方便使用,然后链接器将目标文件以及程序依赖的各种库再次链接从而形成最终的可执行文件。

接下来我们具体看一下链接器是如何工作的。

链接器是如何工作的


在链接器可操作的元素一节中我们提到,所有的应用程序都是链接器将所需要的一个个简单的目标文件汇集起来形成的。你可以将这个过程想象成拼图游戏,每个拼块就是一个简单的目标文件:
1,拼图游戏当中的每个拼块都依赖于其它拼块提供的拼接口,这就好比我们写的程序模块依赖于其它模块提供的编程接口,比如我们在list.c中实现了一种特定的链表数据结构,其它模块需要使用这种链表,这就是模块间的依赖。而链接器其中一项任务就是要确保提供给链接器进行链接的目标文件集合之间依赖是成立的(也就是说,不会出现在被依赖的模块中链接器找不到需要的接口),这就是后面我们要讲到的符号决议(Symbol Resolution),开篇提到的第一个问题就来自这个过程。

2,我们在拼图游戏当中通常都是将一整幅图按组成部位一部分一部分拼接好,然后将这些比较完整的大的组成部分拼接成最后一整副图。这就好比链接器会首先将程序每个模块当中目标文件集合链接成库,然后再将各个库进行链接最终形成可执行程序。这就是后面我们要讲到的可执行程序的生成(这也是我们在上一篇文章当中留在本章讨论的)。

3,链接器还有一项任务是无法用这个拼图游戏来类比的,但是这项重要的任务对程序员不可见,作为程序员几乎不会在这个过程遇到问题,这项任务就是重定位。

通过拼图这个游戏的类比,我们给出链接器的工作过程:

首先,链接器对给定的目标文件或库的集合进行符号决议以确保模块间的依赖是正确的。

其次,链接器将给定的目标文件集合进行拼接打包成需要的库或最终可执行文件。

最后,链接器对链接好的库或可执行文件进行重定位。

接下来我们详细的讲解下每一个过程。

过程一:符号决议

在这个过程当中,链接器需要做的工作就是确保所有目标文件中的符号引用都有唯一的定义。要想理解这句话我们首先来看看一个典型的c文件里都有些什么。

c源文件中都有什么

如图所示是一个典型的c源文件,该文件中的变量可以划分为两类:

  • 全局变量:比如x_global_uninit,x_global_init,fn_c。只要程序没有结束运行,全局变量都可以随时使用。注意,用static修饰的全局变量比如y_global_uninit,其生命周期也等同于程序的运行周期,只是这种全局变量只能在所被定义的文件当中使用,对其它文件不可见。
  • 局部变量:比如y_local_uninit,y_local_init,局部局部变量的生命周期和全局变量不同,局部变量变量只能在相应的函数内部使用,当函数调用完成后该函数中的局部变量也就无法使用了。因为局部变量只存在于函数运行时的栈帧当中,函数调用完成后相应的栈帧被自动回收(如果你还不能理解这句话是什么意思没有关系,我会在后面的文章当中详细讲解程序运行时的内存模型)。

目标文件里有什么

编译器的任务就是把人类可以理解的代码转换成机器可以执行的机器指令,源文件编译后形成对应的目标文件,这个我们在之前的章节中已经多次提到过了。源文件被编译后生成的目标文件中本质上只有两部分:

  • 代码部分:你可能会想,一个源文件中不都是代码吗,这里的代码指的是计算机可以执行的机器指令,也就是源文件中定义的所有函数。比如上图中定义的函数fn_b以及fn_c。
  • 数据部分:源文件中定义的全局变量。如果是已经初始化后的全局变量,该全局变量的值也存在于数据部分。

到目前为止,你可以把一个目标文件简单的理解为由两部分组成,代码部分中保存的是CPU可以执行的机器指令,这些机器指令来自程序员所定义的函数,编译器将这些定义的函数翻译成机器指令并存放在目标文件的代码部分。数据部分存放的是机器指令所操作的数据。因此目前,你可以简单的将目标文件理解为一个只有两部分的文件,如图所示:、 

你可能会好奇函数中定义的局部变量为什么没有放到目标文件的数据段当中,这是因为局部变量是函数私有的,局部变量只能在该函数内部使用而全局变量时没有这个限制的,所以函数私有的局部变量被放在了代码段中,作为机器指令的操作数。

编译器在编译过程中遇到外部定义的全局变量或函数时,只要编译器能找到相应的变量声明就会在心里默念“all is well, all is well(一切顺利)“,从这里可以看出编译器的要求还是很低的,至于所使用变量的定义编译器是不会费力去四处搜索,而是愉快的继续接下来的编译。注意,这里再次强调一下,编译器在遇到外部定义的全局变量或者函数时只要能在当前文件找到其声明,编译器就认为编译正确。而寻找使用变量定义的这项任务就被留给了链接器。链接器的其中一项任务就是要确定所使用的变量要有其唯一的定义。虽然编译器给链接器留了一项任务,但为了让链接器工作的轻松一点编译器还是多做了一点工作的,这部分工作就是符号表(Symbol table)。

在继续讲解之前推荐一份牛逼的算法刷题资料,除了本文讲到的底层技术,想进BAT、TMD、快手这样的一线大厂算法绝不可忽视,认认真真过上一遍这份资料,这些大厂算法面试一关大部分题目都不在话下Github疯传!阿里P8大佬写的Leetcode刷题笔记,秒杀80%的算法题!

符号表(Symbol table)

我们在上一节中提到,虽然编译器很不厚道的给链接器留了一项任务,但是编译器为了链接器工作的轻松一点还是做了一点事情,这就是符号表。那符号表中保存的是什么呢,符号表中保存的信息有两部分:

  • 该目标文件中引用的全局变量以及函数
  • 该目标文件中定义的全局变量以及函数

以上图中的代码为例,编译器在编译过程中每次遇到一个全局变量或者函数名都会在符号表中添加一项,最终编译器会统计出如下所示的一张符号表:

名字

类型

是否可被外部引用

区域

z_global

引用,未定义

fn_a

引用,未定义

fn_b

定义

代码段

fn_c

定义

代码段

x_global_init

定义

数据段

y_global_uninit

定义

数据段

x_global_uninit

定义

数据段

y_global_init

定义

数据段

z_global以及fn_a是未定义的,因为在当前文件中,这两个变量仅仅是声明,编译器并没有找到其定义。剩余的变量编译器都可以在当前文件中找到其定义。

fn_b以及fn_c为当前文件定义的函数,因为在代码段。

剩余的符号都是全局变量,因此放在了数据段。

有同学可能会问,为什么全局变量y_global_uninit ,y_global_init以及函数fn_b不可被其它目标文件引用,这是因为这些变量用static修饰过了,在C语言中经static修饰过的函数的函数以及变量都是当前文件私有的,对外部不可见,这里一定要注意。所以static这个关键字的用法就是,如果你认为一个变量只应该被当前文件使用而不暴露给外部,那么你就可以使用static关键字修饰一下。

本质上整个符号表只是想表达两件事:

  • 我能提供给其它文件使用的符号
  • 我需要其它文件提供给我使用的符号

有很多同学问,你能写出这样的文章来能不能推荐一些书,书单这东西贵精不贵多,我在这里精心挑选了10本 ,不要贪心,如果你真能把这里推荐的 10 本书读通,可以说你已经能超越 90% 的程序员了。我也把相应的pdf下好了,供大家参考:程序员必看经典书单

这里还有一个问题就是,编译器将统计的这张符号表放在哪里了呢?

接下来的内容我会在该系列的第二篇文章当中介绍,欢迎关注微信公众号“码农的荒岛求生”,并回复“链接器”获取该系列完整文章。

所有文章均已汇总在Github上,https://github.com/xfenglu/everycodershouldknow

彻底理解链接器系列

  1. 彻底理解链接器:一,概念
  2. 彻底理解链接器:二,符号决议
  3. 彻底理解链接器:三,库与可执行文件
  4. 彻底理解链接器:四,重定位

彻底理解操作系统系列

  1. 什么程序?
  2. 进程?程序?傻傻分不清
  3. 程序员应如何理解内存:上篇
  4. 程序员应如何理解内存:下篇
     

符号表存放在哪里

符号决议的过程

实例说明undefined reference

过程二:库、可执行文件的生成

静态库

静态连接

静态链接下可执行文件的生成

动态库

动态链接

动态链接下可执行文件的生成

动态库vs静态库

过程三:重定位

编译器的工作

链接器的工作

问题:为什么链接器能确定运行时地址

大型项目是如何被构建(build)出来的

目标是如何实现的

make

构建大型项目

源码组织方式

make的执行过程

总结


http://chatgpt.dhexx.cn/article/QUYW9JT8.shtml

相关文章

【python】pip指定路径安装文件

在网上下载个tar.gz的安装包,用pip在指定目录安装 pip install --target路径 文件名 pip install --targetE:\work\zicai\pd_code\AutoTest3.7\shujia\venv\Lib\site-packages xlwings-0.18.0.tar.gz 指定下载pyecharts包1.7.0版本到执行路径 pip3 install -i h…

如何查看Ubuntu中Python的安装路径

ubuntu查看python安装路径 查找方法: python import sys pythonpath sys.executable print(pythonpath)Python的安装路径:/usr/bin/python 可提供远程搭建运行服务 不会调试运行的同学,你只需打开远程,会帮你搭建调试好一切&a…

如何找到python 安装路径

我觉得第二个方法更实用,直接按个快捷键F8就行了 第一个:打开python,或者命令行 输入 import sys for i in sys.path: ... print(i) 输出 C:\Users\ASUS\AppData\Local\Programs\Python\Python37\python37.zip C:\Users\ASUS\AppDat…

找Python安装目录,设置环境路径以及在命令行运行python脚本

第一点:找Python安装目录 方法一: 方法二: 输入import sys print(sys.path) 化黑线处 第二点:找到安装目录后就可以开始设置环境变量 这里我的安装目录为C:\Program Files\Python36 再字符串的末尾&…

如何查看Python的安装路径

如何查看Python的安装路径 打开cmd,输入python -m site 发现我的Pyhton安装路径就是:C:\Users\XXXXX\AppData\Local\Programs\Python\Python38

【windows环境下】如何将python模块安装到指定目录下

当你只安装了一个版本的python的时候只需要用 pip install [模块名]就可以安装模块 当存在多个python环境,要将模块添加到某个环境下 可以使用下面的代码 pip install -t [路径]\ [模块名]其中的路径是你选择的环境python所在的安装目录 一般在python下的Lib目录…

下完安装好python后,想查看python的安装位置的几种方法

查看python的路径 基于windows系统,按下winr(也就是命令提示符),输入cmd ,进入 查看当前的python的版本的话输入python -V 1, 查看当前下载的python类型和路径则可以输入 py -0 (加 * 的是你使用python的默…

python安装路径怎么找-怎么查看python安装路径

查看python安装路径的方式:1、在桌面上右击Python软件图标,点击“打开文件所在的位置”,即可直接跳转到安装目录;或是右击之后点击“属性”,查看安装路径。2、在“任务管理器”中找到Python应用,右击选择“打开文件所在的位置”,打开软件的安装位置。 Python是一种脚本语…

cd对Python安装目录操作

1.Python安装路径查询:cmd中使用指令:py -0p 2.cmd操作: 切换当前盘:D: 进入当前盘文件:cd空格位置 返回上一级目录:cd ..

Python安装目录详解

最常用到的就是图中标出的python参考文档与python的标准库。 具体: DLLs:python的动态链接库,跟编译器有关,和python 编程关系不大 Doc:python的参考书,有很多实例 lnclude:python编译器的C语言源码 Lib:…

如何查看python安装路径

在使用python的时候,有时候会需要找到python包的安装位置,来找其他安装的第三方包。下面我们来看看,在不同平台上,怎么找到python的安装路径。 很多运行的系统软件都是建立在python的基础之上,如果python出错了&#…

python的默认安装位置查询

一般你默认安装的话,python的安装路径是在如下的路径 C:\Users\[你的用户名]\AppData\Local\Programs\Python 如图:

Python安装后目录在哪儿_如何查看Python的安装目录

一、Python的安装录 当前安装版本为:python 3.10.4 1.在安装python的时候可以看到安装目录,可以修改安装目录: 2.windows系统下64位安装目录如下: 跟其他软件不太一样,没有安装在PragrameFiles文件夹中 C:\Users\Ad…

状态转移矩阵求解

状态转移矩阵的求解 例: 求状态转移矩阵。 STEP1:写出齐次方程。 ① ② STEP2:求方程在条件下的解:。 STEP3:取线性无关的初始状态,求出基本矩阵。 取时: 取时: 由于两个初始状…

GIS应用技巧之制作土地利用转移矩阵表

一、前言 土地利用数据是反映土地利用系统及土地利用要素的状态、特征、动态变化、分布特点,以及人类对土地的开发利用、治理改造、管理保护和土地利用规划等数据资料。 土地利用作为全球的研究重点,然而对于土地利用编号最直观的体现就是土地利用转移…

利用ArcGIS做土地利用转移矩阵

今天来分享如何在ArcGIS里做土地利用转移矩阵,选用的数据为山西省GLobeland 30的土地利用产品,选用时期为2010年和2020年,原始数据下载地址为http://globeland30.org/。 产品的土地利用类型有耕地、森林、灌木丛、湿地、水体、人造地表和裸地…

Origin利用土地利用转移矩阵制作桑基图步骤

1.获取转移矩阵文件 https://mp.csdn.net/mp_blog/creation/editor/120759354 (arcgis中转移矩阵的做法可以参考) 2.转移矩阵文件的处理 有多期影像数据 也是做成两列,但是一定要标注好分类名称,就像我上面一样 3.桑基图绘制 …

matlab赌徒破产模型转移矩阵,[转载]【转】生成土地利用变化转移矩阵的方法

这里是网上搜到的生成土地利用变化转移矩阵的几种方法,以飨来者: A 栅格数据做转移矩阵 “一般习惯列为早期的数据,行为近期的数据,就如你说的早期的在上,晚期的在下。” B 矢量数据做转移矩阵 根据你的数据类型选用不…