首先特别感谢网易公司,网易公司提供的云课堂,公开课实在是太好了,有了这些东西我们这些渣渣学校的也可以学习国内外名校的课程了。
由于我在大学学习的网络工程,大学的时候自己也too young,学校没教《编译原理》这门课,自己也没主动接触过,看到许多大牛都很推荐学习《编译原理》这门课堂(程序员的三大浪漫之一:操作系统,编译原理,图形学),所以我也就很关注网易云课堂的这门课,最近这门课终于开了,所以把自己的学习历程记录下来吧,希望课程结束的时候自己能够写出来一个简单的编译器。
课程主页在这里:网易云课堂--中国科学技术大学--编译原理
注:有些图片直接从该课程讲师 @华保健 老师提供的ppt中截取。已经在征取老师的意见了,如果不合适的话,我会重新制作插图。
什么是编译器
编译器是一个程序,它的核心功能是阅读以某种语言(源语言如C、C++)编写的程序,然后把该程序翻译为和它等价的,用目标语言(如二进制机器码)编写的程序。编译器重要的任务之一是报告翻译过程中发现的源程序中的错误。简单来说编译器就是完成源程序到目标程序的转换。
编译器和解释器
编译器和解释器都是处理程序的程序。编译器把源程序翻译为目标程序,而解释器直接执行源程序并计算结果。在把用户输入映射为输出的过程中,由编译器产生的机器语言目标程序通常比解释器快很多,但是解释器的错误诊断比编译器好,因为解释器是逐语句执行源程序。
编译器的结构
实际开发中,我们可以认为编译器就是一个黑盒子,它可以接受源程序翻译为目标程序,打开这个黑盒子,可以认为编译器由两部分组成:
1:分析部分
分析部分也称为编译器的前端,它处理源语言相关的东西。可以粗略认为前端由词法分析,语法分析,语义分析这几个部分组成。
词法分析的输入是一个字符流(可以认为是存储在硬盘上的源代码文件),输出是一组有意义的单词(关键字,变量名,操作符都是一个单词)和符号表。
语法分析处理词法分析的输出单词,然后输出一个语法树,这个语法树表示程序的逻辑结构。
语义分析使用语法树和符号表进行类型检查,类型转换等操作判断是否满足该语言的(编码)规则。
经过前端处理之后,得到源程序的中间表示。
2:综合部分
综合部分也称为编译器的后端,它使用源程序的中间表示和符号表来构造目标程序。通常在前端和后端之间还会有一些与机器无关的优化操作。优化是在中间表示上进行转换,以便后端程序能够更好的生成目标程序。
编译器的优化
编译器的优化指的是生成的目标代码尽可能的高效。编译器的优化必须满足下面的设计目标:
- 优化必须是正确的。也就是说,不能改变被编译程序的意思
- 优化必须能够改善很多程序的性能
- 优化所需要的时间必须在可接受范围内
- 优化所需要的工程方面的工作必须是可管理的
对正确性的强调是无论如何都不过分的,不管编译得到的目标程序运行有多块,只要生成的代码不正确,再快也没有意义。因此设计编译器的最重要的目标就是正确性。性能意味着执行效率和能耗,嵌入式设备和移动设备对性能的要求更高。当然,优化工作需要的时间必须可接受,如果优化需要几天的时间,就算优化后程序执行的再快,也没有人能接受。
编译器的第一个课堂作业
这个课堂作业同样取自华老师的课件,华老师给出了c语言的部分实现,我这里用C++重写。
题目如下:现在有一个叫做sum的语言,目标平台是栈式计算机,支持两条指令PUSH和ADD,模拟2+3+4的编译过程(这里不牵扯后端)。
#include <iostream>
#include <vector>
#include <cassert>
class Expression
{
public:
Expression() { }
virtual ~Expression() { }
enum EXP_TYPE { ET_INT, ET_SUM };
virtual EXP_TYPE type() = 0;
};
class Expression_Int : public Expression
{
public:
Expression_Int(int v) : value_(v) { }
virtual ~Expression_Int() { }
virtual Expression::EXP_TYPE type() override
{ return Expression::ET_INT; }
int value() const { return value_; }
private:
int value_;
};
class Expression_Sum : public Expression
{
public:
Expression_Sum(Expression* left, Expression* right)
: left_(left), right_(right) { }
virtual ~Expression_Sum() { }
virtual Expression::EXP_TYPE type() override
{ return Expression::ET_SUM; }
Expression* left() { return left_; }
Expression* right() { return right_; }
private:
Expression* left_;
Expression* right_;
};
class Compiler
{
struct StackElem
{
enum ELEM_TYPE { ET_PUSH, ET_ADD };
ELEM_TYPE type_;
union
{
int value;
} context;
};
public:
void process(Expression* exp)
{
if (!exp) return;
switch (exp->type())
{
case Expression::ET_INT:
{
Expression_Int* p = dynamic_cast<Expression_Int*>(exp);
assert(p);
StackElem se;
se.type_ = StackElem::ET_PUSH;
se.context.value = p->value();
elemVec.push_back(se);
break;
}
case Expression::ET_SUM:
{
Expression_Sum* p = dynamic_cast<Expression_Sum*>(exp);
assert(p);
process(p->left());
process(p->right());
StackElem se;
se.type_ = StackElem::ET_ADD;
elemVec.push_back(se);
break;
}
default:
break;
}
}
void show()
{
for (auto e : elemVec)
{
switch (e.type_)
{
case StackElem::ET_ADD:
{
std::cout << "ADD" << std::endl;
break;
}
case StackElem::ET_PUSH:
{
std::cout << "PUSH " << e.context.value << std::endl;
break;
}
default:
break;
}
}
}
private:
std::vector<StackElem> elemVec; //stack
};
int main()
{
std::cout << "Compile starting" << std::endl;
// build an expression tree:
// +
// / \
// + 4
// / \
// 2 3
Expression* left = new Expression_Sum(new Expression_Int(2), new Expression_Int(3));
Expression* right = new Expression_Int(4);
Expression* exp = new Expression_Sum(left, right);
Compiler c;
c.process(exp);
c.show();
return 0;
}