《程序员的自我修养》第二章 编译和链接

#include <stdio.h>
int main()
{
    printf(‘hello word');
    return 0;
}

 hello word是c语言经典的经典,在GCC编译这个c语言的时候,将代码编译为可执行的程序之间可以分为四个步骤,预处理编译汇编链接,如图所示

 

 1 预编译:

 首先是源代码文件helloc.c和相关的头文件,比如stdio.h等预编译器cpp预编译成一个.i文件,预编译主要处理哪些源代码文件中以’#‘开始的预编译指令,比如’#include‘ '#define'等, 主要的规则如下:

   将所有的”#define"删除,并且展开所有的宏定义.

   处理所有的条件预编译指令,比如“#if” “#ifdef" "#elif" "#elde" "#endif"

   处理”#include” 预编译指令,将被包含的文件插入到改预编译的指令的位置。注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其它文件’

   删除所有的注释”//“ 和”/**/"

   添加行号和文件名标识,比如#2“hello.c” 2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号

   保留所有的#pragma编译器指令,因为编译器需要使用它们

 经过预编译后的.i文件不包含任何宏定义,因为所有的宏已经被展开,并且包含的文件也被插入到.i文件中,所以当我们无法判断宏定义是否正确或头文件包含是否正确,也可以查看预编译后的文件来确定问题

2 编译:

  编译过程就是把预处理的文件进行一系列的词法分析,语法分析,词义分析以及优化后产生相应的汇编代码文件,而这些过程看起来简单实际上确是最复杂的部分之一:

    首先源代码被输入到扫描器进行词法分析,运行一种类似于有限状态机的算法将代码字符串序列分割成一系列的记号,然后语法分析器将对由扫描器产生的记号进行语法分析,从而产生语法树,(由分析器产生的语法树就是以表达式为节点的树如下图:

 

 

整个语法被看作是一个赋值表达式:赋值表达式左边是一个数组表达式,他的右边是一个乘法表达式,数组表达式又由俩个符号表达式组成,符号和数字都是最小的表达式,它们不是由其它的表达式来组成的,所以它们通常作为整个语法树的叶节点,在词法分析的同    时,很多运算符号的优先级和含义也被确定下来了)整个分析采用了上下文无关语法的分析手段,接下来就是语义分析,由语义分析器来完成,语义分析仪仅仅是完成了对表达式的语法层面的分析,但是它并不了解这个语句是否真正有意义,编译器能分析的语义是静态语义(在编译期可以确定的语义),与之对应的是动态语义,就是在运行期才能确定的语义,然后这个这些步骤才能产生相应的汇编代码文件

 

  在现代的编译器中,有着很多层次的优化,往往在源代码级别会有一个优化过程。我们这里所描述的源码级优化器在不同的编译器中可能会有不同的定义或者一些其他的差异,源码级优化器会在源代码级别上进行优化,中间代码使得编译器可以被分为前端和后端,编译器前端负责产生机器无关的中间代码,编译器后端将中间代码转换为目标机器代码,对于一些可以跨平台的编译器而言,它们可以针对不同的平台使用同一个前端针对不同机器平台的数个后端,源代码级优化器产生中间代码标志着下面的过程都属于编辑器后断,编译器后端主要包括代码生成器目标代码优化器,首先代码生成器将中间代码转换成目标机器代码,这个过程十分依赖于目标机器,因为不同的机器有着不同的字长,寄存器,整数数据类型和浮点数数据类型等,例如下面这个例子(假设index的类型为int,array的类型为int型数组)

 

 

 最后目标代码优化器对上出的目标代码进行优化,比如选择合适的寻址方式,使用唯一来代替惩罚运算,删除多余的指令,

3链接:

 当一个系统十分复杂的时候,我们将一个复杂的系统逐步分割成小的系统以达到各个突破的目的,软件也是如此,人们把每个源代码独立的编译,然后按照顺序组装起来,这个组装过程就被称为链接,链接的过程包括了地址和空间分配符号决议重定位等这些步骤

(中间具体的步骤还是推荐去书里面看,我只不过是将题目里面的一些关键定义进行汇总)

 

posted @ 2021-12-22 21:00  庄周恋蝶蝶恋花  阅读(49)  评论(0)    收藏  举报