1、简述
LLVM前端通过对源程序的预处理,构成源程序的字符流扫描与分解,将单词序列提取为各类语法短语,生成抽象语法树,最终转换为中间代码。编译器前端包含的这几个过程如下:
(1)预编译
(2)词法分析
(3)语法分析
(4)语义分析
2、预编译
预编译所完成的基本上是对程序源代码的替代和整理工作,经过此类替代整理,生成一个没有宏定义、条件编译指令、特殊符号、用户注释的输出文件。
预编译的主要处理有:
(1)文件包含
这一步主要处理头文件包含问题,也就是#include语句。例如处理#include<stdio.h>时,预编译器会在系统标准路径下搜索stdio.h,并将stdio.h的内容复制到当前文件。文件包含的过程时递归进行的。
(2)宏展开
这一步是主要处理#define定义的宏,用宏定义的字符串替换掉对应的宏名。
(3)条件编译
这一步主要处理#if和#ifdef等条件编译指令,将源代码中的某部分符合条件的代码包含进来,剔除掉不符合条件的代码段。
(4)删除注释
这一步主要处理注释,删除掉注释行(//和/* */)
3、词法分析
词法分析是对源代码从左到右逐个将字符读入编译器,并扫描和分解字符流,从而识别出一个个单词,并确定单词的类型,将识别出的单词转换为统一的语法单元形式,也就是token。包括如下类型:
(1)关键字
关键字是被C语言或者C++本身使用,在程序中有特定含义,不能作为其他用途使用的单词。如int、float、if、for等。
(2)标识符
用来表示各种名字,如变量、数组名、函数名等。
(3)运算符
包括算术运算符+、-、*、\等,逻辑运算符&&、||、!等,以及关系运算符==、>、<等。
(4)分界符
包括,、:、;等符号
(5)常数
分为整型、浮点型、字符型等,如1、5.67、Helllo等
4、语法分析
语法分析的任务是将词法分析生成的单词组合成语法短语,同时分析这些短语是否符合高级程序设计语言中的语法规则。有下面的规则来定义表达式:
(1)标识符是表达式;
(2)常数是表达式;
(3)若表达式1和表达式2都是表达式,那么表达式1+表达式2以及表达式1*表达式2也都是表达式
有下面的规则来定义赋值语句:
(1) <表达式> = n
(2) <表达式> = <表达式>”+“<表达式>
(3) <表达式> = <表达式>”*“<表达式>
(4) <赋值语句> = <标识符>”“<表达式>
根据这个规则,可以将表达式:c=a+b*7 转成下图的语法树:
5、语义分析
语义分析阶段的任务是审查源代码有无语义错误,源代码中有些语法成分,按照语法规则去判断是正确的,但不符合语义规则,比如使用了没有声明的变量。
语义分析主要的任务可归结为以下四类:
(1)完成静态语义审查和处理;
(2)上下文相关性的审查;
(3)类型匹配审查;
(4)类型转换;
比如
int b,c;
c = a * b + 1;
就会审查出a变量没有定义。
再比如
float a,b,c;
c = a + b * 5;
运算符*的两个运算对象分别是b和3,如果b是float变量,3是整型常数,语义分析阶段执行类型审查之后,会自动将整型量转化成float,以完成同类型的数据运算,体现在语法分析所得到的语法树上,即增加一个运算符结点。
6、举例说明
hello.c内容如下:
#include <stdio.h>
#define N 1024
int main()
{
int a,b,c,d[N];
a = 2;
b = 4;
c = a + b * 3;
printf("Hello!");
return c;
}
进行预编译,可以看到结果中对头文件、宏定义和注释按照预编译规则进行相应的处理。
使用
clang -E hello.c -o hello.i
在进行词法分析后会读入源程序的字符流,将其转换成对应的单词序列token。
词法分析指令:
clang -fmodules -fsyntax-only -Xclang -dump-tokens hello.c
结果:
在进行语法分析后会依据语法规则把源代码的单词序列组成语法短语构成的语法树。
clang -fmodules -fsyntax-only -Xclang -ast-dump hello.c
输出就是语法分析的过程和结果:
在进行语义分析后可以检查源代码有无语义错误,若无语义错误,则可以进入编译器中端了。