静态链接——编译和链接(一)


本文主要是《程序员的自我修养》一书的内容摘要和梳理,如有需要并且没有被本文涵盖的内容,建议读者自行观看原书。

相关文章:
静态链接——编译和链接(一)
静态链接——目标文件(二)
静态链接——静态链接(三)
装载与动态链接——装载与进程(一)
装载与动态链接——动态链接(二)

编译与链接

本部分主要是介绍关于程序源代码是如何到可执行文件的,及这其中涉及到的主要步骤(例如本书的主题——编译)的相关基本概念。

源代码到可执行文件主要是分为4个步骤:预处理(Preprocess),编译(Compilation),汇编(Assembly) 和链接(Linking)。

预处理

预处理部分主要是处理源代码以 “#” 开头的预编译指令:

  • 展开所有的宏定义 ,“#define”。
  • 处理所有条件预编译指令,”#if“,"#ifdef" 等。
  • 处理 ”#include“ 预编译指令,递归地将包含的文件内容插入到指定位置。
  • 删除所有的注释
  • 添加行号和文件名标识,便于后面调试,或者产生错误/警示信息能显示具体位置。
  • 保留所有的 ”#pragma“ 编译器指令,编译器需要。

预处理过程可以看到大部分都是文本的替换工作,所以我们无法在预处理阶段就判断出宏定义是否正确或者是头文件包含正确,唯一可能的方法就是肉眼检查预处理之后的文件内容 😃。

编译命令:

gcc -E hello.c -o hello.i

编译

编译部分要把预处理部分的内容进行一系列的操作:词法分析语法分析语义分析相关优化,最后生成汇编代码文件。
下面以书中的一行源码来进行上述步骤的说明:

array[index] = (index + 4) * (2 + 6) 
词法分析

首先词法分析是运行一种类似有限状态机的算法,把源代码的字符序列分割成一系列的记号(Token),一般可以分为:关键字,标识符,字面量和特殊符号。
上述可以得到:

  • 标识符:array index
  • 字面量:2 4 6
  • 特殊符号:[ ] ( )+ = ×
    (上面只是一个大概示例)

将对应的类别放入对应的符号表/文件表中以备后续使用。

语法分析

语法分析顾名思义是对词法分析的结果进行语法分析,生成上下文无关语法树,其是以表达式(Expression)为节点的树。上述语句就是由赋值表达式、加法表达式、乘法表达式、数组表达式、括号表达式组成的复杂语句,经过语法分析以后可以得到下述语法树:
在这里插入图片描述

注意,一般符号和数字是最小的表达式,所以它们一般是叶节点,然后在语法分析的同时,很对符号的优先级和含义就被确定了下来,例如 × 比 + 优先级高,()比 × 优先级高,这里 * 符号代表乘法而不是取指针内容等。如果表达式不合法,括号不匹配,表达式缺少操作符等,都是在这个阶段编译器就会报相关错误了。

语义分析

语法分析并不了解这个语句是否真正有意义,例如语法上将两个指针相乘是合法的,但是在语义上肯定是非法的,因此编译器需要进一步进行语义分析。编译器可以做的是静态语义:值在编译器就可以确定的语义,与之对应的动态语义则是只有运行期才可以确定的语义,比如将 0 作为除数了。

静态语义通常包括声明和类型匹配,类型转换。

比如运算需要将 float 转换成 int,或者将 float 数复制给一个指针,语义分析发现类型不匹配,编译器就要报错。
经过语义分析以后整个语法树就会被表示上类型,如果需要做隐式转换,语义分析程序会在语法书中插入相应转换节点。经过语义分析的语法树为:
在这里插入图片描述

中间语言生成

在语义分析完成以后,以及进行相关优化之前,还有一步中间步骤,中间语言生成,其目的主要还是对语法树进行初步优化,方便后面的进一步优化编译,而直接在语法树上进行优化比较困难,所以需要中间代码,这个代码与机器和运行环境是无关的。以上面的语法树为例,转化成中间语言的一种——三地址码后是:

t1 = 2 + 6
t2 = index + 4
t3 = t2 * t1
array[index] = t3

这里在编译期 t1 的值就可以直接确定为 8 ,所以可以直接将 t1 变量替换为 8,且变量可以重复使用,优化后为:

t2 = index + 4
t2 = t2 * 8
array[index] = t2

中间代码的存在使编译器可以分为前端和后端,前端只要是负责产生与机器无关的中间代码,后端负责将中间代码转换成目标机器的代码,这样对于一些跨平台的编译器,它们可以针对不同平台使用同一个前端 + 针对不同机器的多个后端。

相关优化

前面提到编译器前端主要是负责生成与机器无关的代码,后端细化主要是包括代码生成器和目标代码优化器。
代码生成器:将中间代码转化成目标机器代码。
目标代码优化器:选择合适的寻址,使用位移代替乘法,删除多余指令等。

最终经由词法分析、语法分析、语义分析、源代码优化、中间语言生成、目标代码优化之后,源代码编程了目标代码(汇编代码),交由后面的汇编器来编译成真正能在机器上执行的指令。

gcc -S hello.c -o hello.s

这里针对不同的程序,gcc 后台会调用不同的预编译和编译的程序,例如 C 语言需要调用 cc1,C++ 语言需要调用 cc1 plus。

汇编

汇编部分就是将汇编代码转换成机器可以执行的指令,基本只要根据对照表一一翻译就可以了。翻译之后输出的就是目标文件(Object File)。

gcc -c hello.c -o hello.o

链接

在上述目标文件中,可能会包含对外部符号的引用,在经过汇编以后这些外部的符号都是未定义的一个状态,需要链接器来最终计算确定并把这些库中未定义的外部符号的地址换成正确的绝对地址,所有依赖库中的地址都确定了,最后将目标文件都链接起来之后才会生成可执行文件,这是一个很复杂的过程,也是 《程序员的自我修养》这本书的重点所在。这个大话题会贯穿整个系列文章,这里先简单介绍。
链接的主要内容就是把各个模块之间的内容都处理好,是各个模块之间可以正常衔接。主要过程包括了 地址和空间分配、符号决议和重定位等。

小结

本文主要是介绍编译器的编译阶段对源代码所做的内容,如标题顺序,对源代码以此经过:词法分析,语法分析,语义分析,中间代码生成,相关优化,最后汇编器翻译成机器语言,再交给链接器来进行变量/函数地址的重新计算和修正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值