本章将对本书制作的编译器及其实现的概要进行说明。 2.1 CЬ语言的概要 本书制作的编译器可将C Ь 这种语言编译为机器语言。本节首先对C Ь 语言的概要进行简单的说明。 C Ь的Hello, World ! C Ь 是C 语言的简化版,省略了C 语言中琐碎的部分以及难以实现、容易混淆的功能,实现起来条理更加清晰。虽然如此,C Ь 仍保留了包括指针等在内的C 语言的重要部分。因此,理解了C Ь 的编译过程,也就相当于理解了C 程序的编译过程。 让我们再来看一下用C Ь 语言编写的Hello,World! 程序,如代码清单2.1 所示。 代码清单2.1 用C Ь语言编写的Hello,World! 程序 import stdio; int main(int argc, char **argv) { printf("Hello, World!n"); return 0; } 可见该程序和C 语言几乎没有差别,不同之处只是用import 替代了#include,仅此而已。本书的目的是让读者理解“在现有的OS 上,现有的程序是如何编译及运行的”。那些有着诸多不切实际的限制,仅能作为书中示例的“玩具”语言,对其进行编译丝毫没有意义。从这个角度来说,C 语言作为编程语言是非常具有现实意义的,而C Ь 则十分接近于C 语言。因此,理解了C Ь,对于现实的程序就会有更深刻的认识。 C Ь中删减的功能 为了使编译器的处理简明扼要,下面这些C 语言的功能不会出现在C Ь 中。 ●●预处理器 ●●K&R 语法 ●●浮点数 ●●enum ●●结构体(struct)的位域(bit field) ●●结构体和联合体(union)的赋值 ●●结构体和联合体的返回值 ●●逗号表达式 ●● const ●● volatile ●● auto ●● register 简单地说一下删除上述功能的原因。 首先,C Ь 同C 语言最大的差异在于C Ь 没有预处理器。认真地制作C 语言的预处理器会花费过多的时间和精力,进而无法专注于本书的主题——编译器。 但是,因为省略了预处理器,所以C Ь 无法使用#define 和#include。特别是不能使用#include,将无法导入类型定义和函数原型,这是有问题的。为了解决该问题,C Ь 使用了与Java 类似的import 关键字。import 关键字的用法将稍后说明。 数据类型方面也做了一些变化。 首先,删除了和浮点数相关的所有功能。浮点数的计算是比较重要的功能,笔者也想对此进行实现,但由于本书页数的限制,最后也只能放弃。 其次,由于C 语言的enum 和生成名称连续的int 型变量的功能本质上无太大区别,因此为了降低编译器实现的复杂度,这里将其删除。至于结构体和联合体,主要也是考虑到编译器的复杂度,才删除了类似的使用频率不高或非核心的功能。 volatile 和const 还是比较常用的,但因为cbc 几乎不进行优化,所以volatile 本身并没有太大意义。const 可以有条件地用数字字面量和字符串字面量来实现。 最后,auto 和register 不仅使用频率低,而且并非必要,所以将其也删除了。 import 关键字 下面对C Ь 中新增的import 关键字进行说明。 C Ь 在语法上和C 语言稍有差异,而且没有预处理器,所以不能直接使用C 语言的头文件。为了能够从外部程序库导入定义,C Ь 提供了import 关键字。import 的语法如下所示。 import 导入文件ID; 下面是具体的示例。 import stdio; import sys.params; 导入文件类似于C 语言中的头文件,记载了其他程序库中的函数、变量以及类型的定义。cbc 中有stdio.hb、stdlib.hb、sys/params.hb 等导入文件,当然也可以自己编写导入文件。 导入文件的ID 是去掉文件名后的“.hb”,并用“.”取代路径标识中的“”后得到的。例如导入文件stdio.hb 的ID 为stdio,导入文件sys/params.hb 的ID 为sys.params。 导入文件的规范 下面让我们看一个导入文件的例子,cbc 中的stdio.hb 的内容如代码清单2.2 所示。 代码清单2.2 导入文件stdio.hb // stdio.hb import stddef; // for NULL and size_t import stdarg; typedef unsigned long FILE; // dummy extern FILE* stdin; extern FILE* stdout; extern FILE* stderr; extern FILE* fopen(char* path, char* mode); extern FILE* fdopen(int fd, char* mode); extern FILE* freopen(char* path, char* mode, FILE* stream); extern int fclose(FILE* stream); : : 只有下面这些声明能够记述在导入文件中。 ●●函数声明 ●●变量声明(不可包含初始值的定义) ●●常量定义(这里必须有初始值) ●●结构体定义 ●●联合体定义 ●● typedef 函数及变量的声明必须添加关键字extern。并且在C Ь 中,函数返回值的类型、参数的类型、参数名均不能省略。