编译原理与技术(一)——编译器的整体结构

一、为什么需要编译器

使用编辑器创建C语言源文件,并保存为以c为扩展名结尾的源文件。

比如下面的C程序保存为A.c

#include <stdio.h>
int main()
{
  printf("hello, world!\n");
}

创建该文件后,该文件中的程序代码以字节序列的形式存储在文件中。每个字节都有一个对应于某个字符的值。比如,第一个字节的值为35,对应于字符“#”;第二个字节值为105,对应于字符“i”,以此类推。

但这些字节仅仅表示一个个字符,机器不能读懂这些信息,所以我们需要一些程序来将这些信息翻译成机器能读懂的信息,而这些程序就组成了编译系统。在编译系统中,最主要的就是编译器

我们只需要把C语言源文件作为输入,让其经过编译系统的处理,便可得到可执行文件,机器就通过读可执行文件来执行相应的功能。

二、Linux中编译和执行C程序的步骤

 编译系统可划分为四个程序——预处理器、编译器、汇编器与链接器。

下图展示了一个C语言源文件是如何被编译系统处理的。

 接下来我将依次介绍编译系统处理C语言源文件的四个阶段。

(一)预处理(Preprocessing)

预处理由预处理器完成,而预处理器由编译器自动调用。

编译C程序从编译预处理指令(例如#include)开始。

A.c为例,预处理指令(#include<stdio.h>)告诉预处理器读取系统头文件stdio.h的内容,并将该文件内容直接插入源文件里面,这称为预处理指令被展开

源文件被预处理后生成以i为扩展名的文件,该文件通常不会存储在磁盘上。

但可以通过单独执行以下命令来得到存储在磁盘上的以i为扩展名的文件。

cpp A.c > A.i

生成A.i后,我们可以查看该文件里面的内容。内容已展示在附录A。

我们会发现,原本5行的源文件,经过预处理器处理后,生成的文件有743行,这些多出来的内容就是所有宏的源代码。

(二)编译(Compilation)

编译由编译器完成。

现代编译可分为6个阶段——词法分析、语法分析、语义分析、中间代码生成、机器无关代码优化与目标代码生成。

通过执行下面命令便可得到汇编语言源程序文件A.s

gcc -S A.i

A.s会被存储在磁盘上,其内容已展示在附录B中。

(三)汇编(Assembly)

汇编由汇编器完成。

汇编器会将汇编语言翻译为机器语言指令,生成以o为扩展名的目标文件A.o

我们可以通过下面命令来得到A.o

as A.s -o A.o

A.o被存储在磁盘上,该文件是二进制文件,我们现在没必要去查看其内容。

(四)链接(Linking)

链接由链接器完成。

为什么需要链接呢?不是已经得到机器语言指令了吗?

这是因为一个可执行文件可能需要许多外部资源(系统函数、C运行时库等)。具体到A.c,就是需要打印(printf)函数,该函数被包含在一个单独的预编译目标文件printf.o中。所以,我们需要链接这些文件。最终,我们可以得到一个可执行文件A

执行链接的命令十分复杂,有兴趣的读者可以去了解。

从上面可以发现,编译系统处理一个C语言源文件需要执行较多的命令,尤其是链接命令十分复杂。为了提高效率,可以通过一个命令直接将C语言源文件翻译成可执行文件,如下。

gcc A.c -o A

三、现代编译器的构造/阶段

现代编译器的整体结构如下图。

或者说按如下方式划分。

 而本博客所介绍的内容如下:

  1. 词法分析
  2. 语法分析
  3. 语义分析
  4. 中间代码生成
  5. 机器无关代码优化
  6. 目标代码生成

(一)词法分析

功能:将程序字符流分解为记号流(Tokens)

(二)语法分析(Parsing)

功能:由记号流创建语法树

(三)语义分析 

检查程序中的不一致。

(四)中间代码生成 

生成与文法和目标机器都无关的中间语言。

(五)机器无关代码优化 

(六)目标代码生成 

生成最终的机器语言指令。

参考资料:

 [1]gcc Compilation Process and Steps of C Program in Linux

 [2]

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值