编译原理入门学习总结

前言:近期正在学b站上哈工大的编译原理课,但是越学越崩溃。所以干脆停止看视频,慢下来汇总、理清知识。我搜索了几篇关于编译原理的文章,读完后提取出有用的知识。当做入门吧,争取搞清why。[若侵权,请联系我删除~]

  1. “语言是怎么诞生的?为什么不能用一个语言解决所有问题?”
  2. 追寻程序设计语言的本质
  3. 从现实的方面来说,编译原理学过之后的益处(不考虑最后都没有入门的情况)包括:
    1、可以更加容易的理解在一个语言中哪些写法是等价的,哪些是有差异的
    2、可以更加客观的比较不同语言的差异
    3、更不容易被某个特定语言的宣扬者忽悠
    4、学习新的语言是效率也会更高
    5、其实从语言a转换到语言b是一个通用的需求,学好编译原理处理此类需求时会更加游刃有余

一、翻译程序:编译器、解释器(将高级语言或汇编语言的代码转换成机器认识的代码)

机器不能理解我们用高级语言编写的代码,所以要在程序执行前将高级语言“翻译”为机器语言。这是一个将源语言程序转化为目标语言程序的过程,它依靠翻译程序来完成。

  1. 翻译程序包括:

编译器:将编译型语言(C++,Go)翻译为机器语言
解释器:将解释型语言(JavaScript、Python)翻译为机器语言

这两个概念其实是对目前流行的语言进行的一个分类

  1. 像编译器最典型的语言就是c/c++,在执行时不需要编译器的帮助,因为在编译器第一次编译的时候就已经将代码变成当前平台上的可执行文件了(比如windows上的exe文件),直接在支持目标代码的平台上运行,不需要第二次编译。
  2. 而解释器对应的就是解释性语言,比如python,php,javascript等等,这类语言的特点是用它写的代码一般不必担心平台操作系统的局限性,在linux上能运行的就一定能在windows运行,这是因为解释性语言在运行时并不会将整个代码进行翻译,而是一行一行进行解释,解释完了也就运行完了,在每次运行代码的时候都需要编译器的帮助,我们常说c语言比python效率高的原因,这也算一个吧。
  1. 编译与解释的异同:
    相同之处:编译和解释都可以将高级语言翻译为机器语言
    不同之处:
    在这里插入图片描述
  1. 编译是将源代码经过分析后生成语法树,再优化生成中间代码,最后生成机器码。编译的结果是生成一个可执行的二进制文件
  2. 而解释也是将源代码经过分析后生成语法树,只不过此后它是基于语法树生成字节码,再根据字节码去执行程序。它并不会生成目标文件,更多的是一个结果。
  3. PS:JavaScript 本身是解释型语言,但是在“翻译”过程中同时有解释器和编译器(JIT)的参与。
  1. 总结(翻译、编译、解释)

翻译:把一种语言翻译成另一种语言。
编译:把源文件转换成目标文件(中间文件),然后输出结果。(高级语言到低级语言)
解释:一边翻译一边执行,不产生目标文件。
三者之间的关系?翻译>编译;编译产生目标代码,而解释不产生目标代码。
翻译程序是一个整体的概念,类似于将整篇文章翻译;而解释程序是一个局部概念,类似于将 文章的每个词每句话逐个解释。

编译程序和解释程序的区别:编译程序是翻译程序的一种
主要区别:解释程序不产生目标程序
(1)编译程序会产生目标程序;而解释程序不产生目标程序;
(2)编译程序实现起来比较复杂;而解释程序本身实现起来比较简单;
(3)编译程序效率比较高;而解释程序运行效率比较低,需对语法、词法、语义等进行检测;

  1. 宿主机和目标机

宿主机:运行编译的机器
目标机:运行目标的机器
两者的区别在于是否安装了编译程序。其实是一个机器
eg:我的电脑可以运行我自己编写的一段代码。(我安装了编译环境)
因为安装了编译程序,所以我的可以称为宿主机。
可以运行我编译完成的目标文件的机器称之为目标机。


二、编译器的演进

  1. 二阶段编译器(单盒模型)
    在这里插入图片描述
    早期的二阶段编译器,任务主要有两个,一是理解输入的源程序,二是将其功能映射到目标机上
    据此将编译器内部划分为前端和后端两个模块 —— 前端负责理解,后端负责映射。
    前端对源代码的理解反映在 **IR (intermediate representation,中间表示)**这一结构中,IR 再传递给后端进行处理。

  2. 三阶段编译器
    在这里插入图片描述
    后面出现了三阶段编译器,也就是在前后端之间增加了一个用以改进 IR 的优化器。注意:这个优化器本身是一个源到源的编译器。自此,三者的分工变为:

  1. 前端:理解源程序,并将理解的结果映射到 IR 中。与编译的源语言有关
  2. 优化器:改进 IR 的形式
  3. 后端:将改进后的 IR 映射到目标机的有限资源上。与编译的目标语言运行环境有关

目标代码的形成:
绝对地址的机器指令代码:这种代码可以立即执行
汇编语言形式的目标程序:这种代码还需要汇编程序汇编之后才能运行
可重定位的指令代码(模块结构的机器指令):运行前需借助一个连接装配程序把各个目标模块连接在一起,装入内存中,使之成为一个可以运行的绝对地址的机器指令代码程序


三、编译步骤

在这里插入图片描述

3.1 前端

词法分析(Lexical Analysis):词法分析器(扫描器)以字符流为输入,对其进行扫描和分解,产生多个 token(单词、符号),这个过程叫做 tokenize(分词化)。接着,这些 token 被归入对应的词类,最后再输出由已归类单词构成的流(形如(typeA,“str1”),(typeB,“str2”),(typeA,“str3”),(typeC,“str4”)…)

一段源代码进入编译器,被词法分析器接收,词法分析器将源代码分割成单个字符,根据字符表组合关键字,比如:它知道一个符号序列’i’、’f’是一个关键词”if”,而一个符号序列’1’、’2’、’3’、’4’是一个常量”1234”等等。但是,它不能知道他们之间的关系,例如:”int”、”x”、”=”、”1”、”;”,词法分析器不知道它是一个语句;它不能检测出它的语法错误。
编译器在扫描代码里的句子的时候,会过滤掉空格以及注释,将其中出现的字符一个个分割开来,并且将其分类,以便在后面构建语法树的时候能够对号入座

语法分析(Syntactic Analysis,or Parsing):语法分析器(分析器)在词法分析的基础上,根据事先约定的语法规则,对已归类单词构成的流进行匹配,以语法单位的形式进行重组,构造并输出一棵语法树(syntax tree | | parsing tree | | derivation tree)。

接下来字符流被语法分析器接收,语法分析器根据语法规则确定一个语法结构,检测语法错误,生成一颗抽象语法树(AST)

这两步的目的是检查代码里是否有出现比较明显的语法错误。比如说我们在初学c语言时最常出现的语法错误就是忘记在语句后面加上”;”符号,这一类错误将会在构建语法树的时候被检查出来
在经过词法分析之后,源文件的字符流变成了一个个可以被计算机识别的token,接下来就是用这些token构建出语法树,根据语法树和语法规则的对比,判断是否出现语法错误,并且一门语言的抽象程度,很大程度就是根据语法树的好坏和token的划分决定的。

语义分析(Semantic Analysis)与中间代码生成:语义分析与中间代码生成器基于语义规则,对语法树进行语义分析(变量是否定义,类型是否正确)和中间代码生成(三元式、四元式等)。

为什么不直接翻译成机器码呢,何必要多此一举先生存中间代码呢?
答案就是为了提高编译器的可移植性,因为不同的cpu的指令集是不一样的,假如直接翻译成机器代码,那么当你换了一块cpu之后,可能你的编译器就完全不能运行了,所以为了鲁棒性,将代码先翻译成中间代码,然后在能在多个不同的cpu上做出相应的改变并且运行了。并且在中间代码到目标代码的过程中,许多数据科学家发明了很多可以提高代码运行效率的算法,这个就是编译原理中重要的代码优化部分。

在这里插入图片描述
在这里插入图片描述

3.2 优化器

中间代码优化:包括分析和转换(数据流分析、相关性分析)两个过程

3.3 后端

指令选择:将每个 IR 操作映射为一个或多个实际的目标机操作
寄存器分配:将指令选择阶段使用的虚拟寄存器映射到实际的目标机寄存器,最小化寄存器的使用
指令调度:重排代码中的各个操作,最小化等待操作数所浪费的周期数

词法分析:把源代码分割成一个一个的词法记号
语法分析:识别程序结构,生成语法树
语义分析:给语法树添加信息,用于生成正确的目标代码
生成中间码:节省工作量,解放生产力,增加灵活性
优化:让程序跑得更快
在这里插入图片描述


发布了581 篇原创文章 · 获赞 97 · 访问量 29万+

猜你喜欢

转载自blog.csdn.net/gx17864373822/article/details/104953862