可变目标C编译器学习笔记(一)

可变目标C编译器学习笔记(一)

第一章 引论

要编写一个编译器,首先要理解一个编译器是什么,有什么作用。
编译器就是将高级语言翻译成低级语言的程序。

以下引用自百度百科:
一个现代编译器的主要工作流程:源代码 (source code) → 预处理器 (preprocessor) → 编译器 (compiler) → 目标代码 (object code) → 链接器 (Linker) → 可执行程序 (executables)。

编译器可以将源代码翻译成目标机器上的汇编代码或者目标代码。而可变目标编译器能够针对不同的机器(MIPS,X86,SPARC)生成代码。

本书主要介绍了lcc编译器,与gcc不同,lcc的代码量很少,易于学习,而且速度很快。

1.1 文本程序

代码片段:包括源代码和对其他代码片段的引用。
例:
<a fragment label 1>≡
sum = 0;
for(i = 0, i < 10, i++)<increment sum 1>

<increment sum 1> ≡
sum += x[i]
以上为两个代码段,而第一个代码段中引用了第二个代码段。这两段代码的功能是计算数组x中所有元素之和。

1.3 概述(因为理解不全面,部分可能有误)

一段代码的编译分为几个阶段。
第一个阶段是由C的预处理程序进行宏扩展、引入头文件、选择条件编译代码等工作。
第二个阶段,编译器开始工作,首先由词法分析器(lexical analyzer)扫描器(scanner)将输入的源程序分解成单词(token)
词法分析器输出单词和单词定义点位置,并处理#指令。
第三个阶段,根据C语言的语法规则对单词串进行分析(parse),同时分析 程序语义(semantic)正确性。经过分析后,源程序会生成抽象语法树(abstract syntax tree),树中每一个节点代表一个基本运算。
然后代码生成器将树转为有向无环图(dag)。
第四个阶段,lcc的与目标机器无关的前端将程序的表示结构传递给后端,由后端把这些结构翻译成汇编代码。

1.4 设计

对于lcc来说,并没有一个独立的设计阶段。lcc刚开始只是针对C语言的一个子集的编译器,所以其最初的设计目标非常有限,仅定位于针对一般的编译器实现、特别是代码生成的教学的需要。即使后来lcc演化成了适于实用的C编译器,这一设计目标仍然没有大的改变。
  lcc比大多数其他ANSI C编译器更小、更快。尽管有时在编译器设计时会忽视编译的速度,但快速仍是广为欣赏的特点,用户经常把速度作为他们使用lcc的原因之一。快速编译本身并不是设计的目标,它是追求简单、特别是注意编译器中严重影响速度的组成部分的实现的自然结果。lcc的词法分析和指令选择都特别快,对整个系统的速度贡献最大。
  lcc能够生成相当高效的目标代码,它以产生好的本地代码为设计目标,而其他优化编译器所具有的全局优化,不在lcc的设计目标之中。大多数现代编译器,特别是CPU供应商开发的编译器,必须采取尽可能的优化措施,以期在性能测试中突出他们的机器。这些编译器很复杂,一般需要数十人组成的小组进行开发。尽管这些高度优化的C编译器在启用优化选项时,产生的代码比lcc产生的代码更高效,但数百名使用lcc作为日常主要C编译器的开发者发现,对大多数应用来说,lcc产生的代码已经足够快了,同时,由于lcc的本身速速非常快,也帮助他们节约了许多时间。另外,系统开发者如果需要对lcc进行修改,则会发现lcc很容易理解。
  lcc的前端大约有9000行代码,每种与目标相关的代码生成器有700行代码,还有约1000行与目标无关的后端代码被所有的代码生成器共享。
  除个别例外,lcc的前端一般使用成熟的编译技术。lcc的词法分析器和递归下降分析器都是通过手工编写的(注:词法分析器另一个方法是通过LEX工具自动生成)。后端是lcc最令人感兴趣的模块,原因之一就是体现了它旨在提高可变目标能力的设计选择。第一,后端使用了代码生成器lburg,该产生器可以根据紧缩规范产生代码生成程序。第二,只要可能,一些明显依赖于目标机器的函数尽可能被移到前端实现。

1.5 公共声明

在此节开始之前,读者需要掌握C语言宏定义相关的知识。这个Blog写的很详细。

在《C Primer Plus》中也有关于宏定义的知识(大一用的谭浩强的书,而且没翻完,对于我来说宏定义了解甚少),下面用一定篇幅学习宏定义。

明示常量 #define

用法:

#include <stdio.h>
#define TWO 2/*分为三个部分 define为第一部分叫做预处理指令 第二部分是TWO叫做
宏,第三部分是 2 叫做替换体*/
#define OW "Zhangyi is so \
handsome."/*反斜杠把该定义延续到下一行*/

#define FOUR  TWO*TWO
#define PX printf("X is %d.\n", x)
#define FMT "X is %d.\n"

int main(void)
{
	int x = TWO;
	PX;
	x = FOUR;
	printf(FMT, x);
	printf("%s\n", OW);
	printf(OW); 
	printf("\nTWO: OW\n");

	return 0;
}

运行结果为:
运行结果
代码中大写字母部分叫做,编译预处理时,预处理器首先在程序中找到宏的实例,然后用替换体代替该宏。从宏变成最终替换文本的过程称为宏展开

带参数的#define

示例代码:

#include <stdio.h>
#define SQUARE(X) X*X
#define PR(X) printf("The result is %d.\n", X)

int main(void)
{
	int x = 5;
	int z;
	
	printf("x = %d\n", x);
	z = SQUARE(x);
	PR(z);
	z = SQUARE(2);
	PR(z);
	PR(SQUARE(x + 2));
	PR(100 / SQUARE(2));
	printf("x = %d\n", x);
	PR(SQUARE(++x));
	printf("x = %d\n", x);
	
	return 0;
}

结果:
在这里插入图片描述
其中第四行输出比较奇怪,其实是编译器把SQUARE(x+2)替换成了x+2*x+2

根据运算法则得到17,解决办法是在宏定义中加括号,即#define SQUARE(x) (x)*(x);

第五行的输出为100,有歧义,其实是编译器把100/SQUARE(2)替换成了100/2*2,根据运算法则得100。

解决办法是#define SQUARE(x) ((x)*(x))

第六行,编译器把SQUARE(++x)替换成了++x * ++x

有些编译器会变成7乘6,有些编译器会变成7乘7。解决办法是避免用++x为宏参数。

用宏参数创建字符串

下面是示例代码:

#include <stdio.h>
#define PSQR(x) printf("The square of " #x " is %d.\n",((x)*(x)))

int main(void)
{
	int y=5;
	
	PSQR(y);
	PSQR(2+4);
	
	return 0;
 } 

运行结果:
在这里插入图片描述
编译器在编译时,把 #x 作为一个字符串替换,就是传入的参数转化成的字符串。

预处理黏合 ##运算符

##运算符可以把两个记号合成为一个记号。例如
x ## n,若n为1,则表达式被替换为x1。
下面是示例代码:

#include <stdio.h>
#define XNAME(n) x ## n
#define PRINT_XN(n) printf("x" #n " = %d.\n", x ## n)

int main(void)
{
	int XNAME(1) = 14;// int x1 = 14
	int XNAME(2) = 20;//int x2 = 20
	int x3 = 30;
	
	PRINT_XN(1);//printf("x1 = %d.\n", x1)
	PRINT_XN(2);//printf("x2 = %d.\n", x2)
	PRINT_XN(3);//printf("x3 = %d.\n", x3)
	
	return 0;
}

下面是运行结果:

在这里插入图片描述
PRINT_XN()宏用#运算符组合字符串, ##运算符把记号组合为一个新的标识符。

变参宏: …和__VA_ARGS__

示例代码:

include <stdio.h>

include <math.h>

#define PR(X, …) printf(“Message” #X " : " VA_ARGS)

int main(void)
{
double x = 48;
double y;

y = sqrt(x);
PR(1,"x = %g\n", x);
PR(2,"x = %.2f, y = %.4f\n", x,y);

return 0;

}

替换时,__VA_ARGS__被替换为"x = %g\n", x和"x = %.2f, y = %.4f\n", x,y。

宏和函数的选择

相对来说,调用多次宏和函数时,宏更占存储空间,函数花费的时间更多。当使用宏可以明显减少程序运行时间时,可以考虑用宏定义。
C99添加了内联函数,可以替换宏的作用。

其他指令

条件编译

示例:

#ifdef MAVIS
	#include "horse.h" //如果已经用define定义了MAVIS则执行以下语句
	#include STABLES 5
#else
	#include "cow.h"//若没有定义,则执行以下语句
	#define STABLES 15
#endif
预定义宏

示例代码:

#include <stdio.h>

void why_me();

int main()
{
	printf("the file is %s.\n", __FILE__);
	printf("date:%s \n", __DATE__);
	printf("time:%s \n", __TIME__);
	printf("version:%ld \n", __VERSION__);
	printf("This is line : %d \n",__LINE__);
	printf("This function is : %s \n", __func__);
	why_me();
	
	return 0;
 } 
 
 void why_me()
 {
 		printf("This is line : %d \n",__LINE__);
	printf("This function is : %s \n", __func__);
 }

结果:

the file is C:\Users\10625\Desktop\大三\预定义宏.cpp.
date:Oct 22 2019
time:16:10:02
version:4210787
This is line : 11
This function is : main
This is line : 20
This function is : why_me

#line 和#error

#line 100 "replace.c" //把当前行号重置为100,文件名重置为replace.c
#define X 10
#if X!=10
#error X is wrong//若X不等于10,编译器就会显示当前指令中的文本。

宏定义学习到此结束。

1.6语法规则

文法规则,即产生式,定义了语言中的语法。产生式中,[…]中为可出现或者不用出现的串,可重复多次的串用{…}括起来,圆括号用来分组。
例如:
bob = ID [ ’ ( ’ Amy { , Amy } ’ ) ’ ] | ‘(’ Amy’)’
该表达式说明,bob可以是ID,也可以是以ID开头,后面跟着用圆括号括起来的一个或多个Amy组成的列表,也可以是用括号括起来的Amy。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页