龙书第一章 - WinChua/blog GitHub Wiki

1. 编译器的主要作用:

  • 发现并报告用户程序中的错误;
  • 产生符号表;
  • 产生编译输出.

2. 编译器的结构:

  • 分析部分(前端): 词法分析,语法分析,语义分析, 产生中间表示;
  • 综合部分(后端): 根据中间表示进行优化, 产生目标机器的输出;
  • 除了上面这种划分外, 编译器可以看成是一系列步骤的组合: 词法、语法、语义分析、中间码生成、目标代码生成、符号表管理等等;
  • 这些步骤都是程序的某一种表示形式到另外一种表示形式之间的转换;

2.1 词法分析:

  • 将文本的字符流转化成为有意义的lexeme(词素)的序列;
  • 每一个词素形如: <token_name, attribute_value> 这里的token_name指的是token的类型

2.2 语法分析:

  • token_name实际上是token的类型,比如identifier,op等等; 可以用来创建一种用于分析词素序列产生的语法结构的树形中间表示,通常成为parse tree

2.3 语义分析:

  • 将语法树以及符号表作为输入, 对parse tree 进行检查, 判断是否符合语言的定义;
  • 收集符号的类型信息, 把这些类型信息写到符号表里面;
  • 类型检查: 判断每一个运算符号的运算分量是否匹配;

3. 程序语言的基础:

记录一些术语以及术语之间的区别.

  1. 静态和动态的区别.
编译器的任务: 对一份源程序代码做出关于某个问题的判定, 类似于下面的函数:

def judge(what, when):
    return answer

e.g:
    * judge(type of a variable, compile time):  表示编译器能够在编译的时候对一个变量的类型做出回答;
        这种语言通常称为  静态类型语言
    * judge(type of a variable, runtime): 表示编译器只能在运行时对一个变量的类型做出回答.
        这种通常称为 动态类型语言
  1. 环境与状态
  • 环境: 代码中某一个名字为x的变量与代码运行时在内存中的位置的映射构成环境;
  • 状态: 代码运行时,每一个内存位置与该位置上的值的映射构成了状态;
环境与状态中涉及了三种对象的绑定:
1. 名字与内存位置的绑定;
   * 动态绑定: 大多数情况下,名字到内存位置的绑定都是动态的, 比如: 函数g中的局部变量x 在不同的调用中指向的内存位置都不一样;
   * 静态绑定: 静态全局变量, static int i;
2. 位置到值的绑定:
   * 一般都是动态绑定的;
   * 静态绑定: 声明常量
  1. 名字、标识符、变量
  • 所有的标识符都是名字, 但是不是所有的名字都是标识符: x.y 是名字但是不是标志符
  • 变量指向了存储中某个位置。
  1. 声明与定义
  • 一个标识符有两个属性: 类型, 值
  • 声明指定了某个标识符的类型;
  • 定义指定了该标识符的值;
  1. 静态作用域与动态作用域
  • 动态作用域: 对一个名字x的使用,指向了最近被调用但是还没有被终止且声明了x的过程中关于x的声明; 这种策略只能够在运行时决定;
  • 静态作用域: 对于一个名字x的使用,指向的是最近在代码中被声明的位置;
  • 两者的关系: 都是最近使用, 动态作用域需要的是时间上最近被声明的名字; 静态作用域需要的是位置上最近被声明的名字.
  1. 参数传递方式:
  • 值调用: f(x,y) ==> 1. 先计算x, y的表达式的值; 2. 将结果作用函数f的参数; 3. 调用f
  • 引用调用: f(x, y) ==> 1. 先获取x, y的地址; 2. 将参数的地址作为f中相关名字的地址; 3. 调用f
  • 名调用: f(x, y) ==> 1. f函数中对x, y的使用都看做是关于实参的宏定义; 名调用如果使用表达式作为实参的话, 可能会在f函数体中多次求值, 如果求值表达式是非函数式的, 那么可能会造成一些与直觉相悖的现象;