编译链接过程

我们应该知道,一个.c文件经过编译链接过程会变成.exe文件(windows下)和.out文件(Linux下)的可执行文件。
我认为掌握编译链接原理对程序员来说很是重要。
首先,这样一个过程可以大致分为两个过程,即编译和链接过程。
对于编译过程又可以细分为预编译,编译,汇编三个过程。


一.预编译
预编译过程主要处理那些源代码文件中以“#”开头的预编译指令,比如:头文件“#include”,宏定义“define”。。
主要处理规则如下:

  1. 将所有的“#define”删除,并且展开所有的宏定义。
  2. 处理所有条件编译指令,比如:“#if”,“ifdef”。。
  3. 处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置。
  4. 把所有注释用空格替换。
  5. 添加行号和文件名标识。
  6. 保留所有的#pragma编译器指令。

在预编译完成之后我们可以得到的是一个.i文件。
在linux中,预编译.i文件的获取可以用 gcc -E main.c -o main.i 命令获得。
这里写图片描述
这时候如果你打开了main.i文件,你会发现前面好几百行我们并不认识的乱码,而我们的程序只在最后出现占一点点。


二.编译
编译过程就是把预处理完的文件进行一系列的语法分析、语义分析、词法分析及代码的优化。还有一个最重要的就是符号的汇总
这里写图片描述

在这个过程中,源代码会被放入到一个扫描器中,扫描器会利用一种叫有限状态机的算法将里面的代码划分成单个记号,这些记号一般就是关键字,标识符类等。然后会进行语法分析,语法分析的过程中,会将扫描器中的记号用树状结构连接起来。然后进行一些运算符的优先级判断,以及表达式的错误判断,然后进行语义分析,语义分析就是对树状结构的记号加上类型,从而让编译器去执行一些隐示类型转换,以及类型的错误判断。接下来编译器会将源代码优化成我们的汇编语言。
编译结束会得到一个汇编文件main.s。这个.s文件在linux中我们可以利用
gcc -S main.c -o main.s 命令得到。
这里写图片描述
这时如果你打开了main.s文件,你会发现里边存放的都是汇编指令。


三.汇编
汇编器试讲汇编代码转变成机器可以执行的指令,每一句汇编语句几乎都对应了一条机器指令,所有汇编器的汇编过程相当于编译器来讲只是根据汇编指令和机器指令的对照表一一翻译就可以啦。
经过汇编步骤我们得到的就是我们的可重定位的目标文件

在汇编阶段通过汇编器将汇编语言转换成我们的计算机可以执行的二进制可重定位文件。任何一个源文件在进行编译阶段的时候会去产生符号表,符号表中存放的就是程序所产生的符号(例如:函数名,变量名等),我们的编译阶段是不会去给符号分配正确的地址,因此当我们用
obidump -t main.o 命令查看.o文件时,会有如下结果:
这里写图片描述
这个其实就是符号表,这个表里有text段,data段….等。
我们会发现符号的地址都是0x00000000,所以计算机根本无法通过地址找到它正确的指令,所以obj文件没有链接根本执行不了。
我们来写个简单的代码,模拟一下代码中的符号会放在符号表的那个位置?
这里写图片描述


四.链接
链接过程可以分为两步:
第一步是合并所有的.o文件的段,进行符号解析,给符号表分配真正的虚拟地址。
这里第一个重要的点就是合并符号表,因为在汇编阶段结束后,我们每个.o文件生成的符号表都是独立的,比如上面再main.o生成的文件表中sum和gdata4都是UND也就是没有找到的符号,所以链接第一步将符号表合并,这些没有找的的符号就会找到其定义的地方,如果还没有,就是发生链接错误。
第二个重要的点就是给符号分配真正的虚拟地址,前面说过,当汇编阶段结束后,我们用obidump -t main.o命令查看.o文件时,会发现符号的地址都是0x00000000,所以也就是说不经过链接,obj文件根本执行不了的根本原因就是链接阶段才会给所有的变量这些符号分配真正的虚拟地址。
第二步是符号重定位,符号重定位就是给之前是UND的那些符号分配虚拟地址,修改这些符号的地址,每一个修改的地方都是一个重定位入口。
链接过程需要所有的.o文件和所有的库文件参与。

五.虚拟地址映射
链接完成之后每个程序的虚拟地址空间也就生成了。关于虚拟地址空间的知识可以点击这里

我们来画一个简单的图理解一下地址映射的过程。。。
这里写图片描述
地址映射其实就是如图所示,通过cpu中一个叫MMU的内存管理单元来映射。
第一次地址映射肯定是失败的,会产生一个缺页异常,因为第一次我们并没有分配无力内存,处理完缺页异常后会重启页面映射,这样就可以映射成功啦!
这里提出一个问题:
为什么操作系统会经过虚拟地址空间来映射分配物理地址?
因为如果说我们链接之后不运行,长时间占用物理内存但是不使用,就会被分到swap交换分区磁盘里,也许一段时间后就会有其他进程占用了这块内存,但是,如果是经过虚拟内存来分配物理内存,直到运行时在分配,就会很方便,所以我们的操作系统可以实现多个进程协同运行。

关于编译链接也可以看https://blog.csdn.net/darkfaker/article/details/79370796

扫描二维码关注公众号,回复: 2437286 查看本文章

猜你喜欢

转载自blog.csdn.net/qq_39110766/article/details/79516624