到这里,我们已经研究了词法分析中表达式和语句的描述和实现。这一部分,我们接着剖析声明这一块内容。
3.3 声明(declaration)
3.3.1 基本数据类型
还是先从最简单的基本数据类型开始。对于如下的C语言代码:
int a;
char* b;
int** c;
int add(int x, int y);
int printf(char * format, ...)
由于C语言是强类型语言,声明必然是以类型符开始的。归纳上面几条声明的特点,就得到了关于类型符的描述:
type_specifier : INT
| CHAR
类型符后面的则称之为变量。第一条声明,变量就是一个单独的标志符;紧接着后面的两条声明由于有指针的存在,需要用到前面递归的描述方法,直到遇到标志符为止。于是,声明的变量的描述就可以表述为:
declarator : ID
| * declarator
对于最后两条函数声明,括号是主要的特征。同时,又有含参数和不含参数的声明形式,可以扩展标志符的表示范围,得到下面的描述:
declarator : direct_declarator
| * declarator
direct_declarator : ID
| direct_declarator ( parameter_type_list )
| direct_declarator ( )
最终,上面的5条声明,包括变量或者函数,可以统一描述为:
variable_declaration : type_specifier declarator
和前面的表达式的描述方法非常类似,关键是要知道如何归纳抽象。需要说明的是,parameter_type_list就是对函数参数的表示方法,本质上也是变量的声明,只是可以用逗号隔开,进行多次声明:
parameter_type_list : variable_declaration
| parameter_type_list, variable_declaration
这样,我们便得到了声明的最终表达形式:
declaration : variable_declaration ;
对于C语言中的基本数据类型的分析就到此为止。接着,我们继续分析C语言中另外三种非常重要并且也非常常用的数据类型:数组,结构体和枚举。
3.3.2 数组(Array)
这里,我们只讨论最简单的一维数组。在C语言中,我们这样声明数组:
int arr[3];
int arr[] = {1, 2};
先不考虑赋值操作(后面我们统一分析),有没有发现,数组的声明和函数的声明结构很类似,唯一的区别是圆括号和方括号。因此,只需要补充上面的描述:
direct_declarator : ID
| direct_declarator ( parameter_type_list )
| direct_declarator ( )
| direct_declarator [ const_expression ]
| direct_declarator [ ]
这里,我们引入了const_expression,顾名思义,就是值为常数的表达式,可以简单描述为:
const_expression : primary_expression
因为数组定义必须首先分配固定大小的内存,我们会在生成汇编代码的时候详细地研究这一块内容。
3.3.3 结构体(Structure)
结构体也是一种特殊的数据类型,主要的声明方式为:
struct Point {
int x;
int y;
};
struct Point point;
可以发现,结构体的声明和基本数据结构的声明类似,如果对应起来,就是这里是以struct 结构体名字开头,然后是变量名。也就是说,我们只需要再单独定义一种针对结构体的类型符即可,得到:
type_specifier : INT
| CHAR
| struct_specifier
仔细分析结构体大括号的内部结构,其实就是由普通的数据类型的声明或者可能存在的嵌套的结构体声明组成的集合。因此,可以描述为:
struct_specifier : struct STRUCT_ID
| struct STRUCT_ID { struct_declaration_list }
struct_declaration_list : struct_declaration
| struct_declaration_list struct_declaration
struct_declaration : type_specifier declarator ;
为了和普通变量名的区别开来,这里引入了STRUCT_ID来单独表示结构体的名字,它和变量名是有区别的。
3.3.4 枚举(Enumerate)
熟悉了结构体的描述,枚举也就大同小异了,我们常用的声明方式如下:
enum COLOR { RED, BLUE = 2, YELLOW };
enum { Mon, Wed, Fri } week;
enum COLOR color;
仿照结构体的描述,更新后的类型符为:
type_specifier : INT
| CHAR
| struct_specifier
| enum_specifier
对于枚举声明中大括号内部的结构,再结构体的描述基础上,稍微变通一下,就可以得到下面的描述:
enum_specifier : enum ENUM_ID
| enum ENUM_ID { enum_declarator_list }
enum_declarator_list : ENUM_MEMBER_ID
| enum_declarator_list, ENUM_MEMBER_ID
同样的,引入ENUM_ID来单独表示枚举名。由于枚举成员变量需要是一个常值,再引入ENUM_MEMBER_ID进行表示。
至此,我们将表达式、语句和声明用我们自己规定的结构进行了抽象描述,完成了语法分析第一步的工作。再进入第二步内容的时候,我们看一看有没有什么遗漏的地方。当时存在,比如说在声明变量的同时进行初始化,也就是常说的: 定义。
3.3.5 变量定义(Definition)
在C语言中,允许我们采用如下的方式进行变量的声明:
int a = 1, *b;
char str[] = {'a', 'b', 'c'};
struct Point point = {0, 0};
即声明的同时进行变量的定义,也就是需要增加对变量进行赋值的描述。同时可以采用逗号分隔,声明多个变量,这种情况前面我们已经处理过了,只需要转换成_list形式即可。于是,补充之后对描述为:
declaration : type_specifier init_declarator_list ;
init_declarator_list : init_declarator
| init_declarator_list, init_declarator
init_declarator : declarator
| declarator = initializer
initializer : expression
| { initializer_list }
initializer_list : initializer
| initializer_list, initializer
看上去还是很好理解的,那么函数呢?C语言中,函数的定义为:
int printf(char * format, ...)
{
...
}
熟能生巧,一步到位:
function_definition : type_specifier declarator compound_statement
至此,我们将会处理到的C语言结构已经全部用抽象的结构描述完毕。但是,对于一个完整的C编译器来说,还有很多内容没有涉及到。本质上来说,这些忽略的内容也是很好用抽象结构表示的,只是越往后,它们涉及的工作量将会非常多。因此,这里将其省略,只保留了相对来说易于理解和处理的部分,便于抓住对编译器的宏观认识和理解。
这些描述目前都还只是零散的表示。为方便后续处理,需要将这些内容全部联系起来,我们将在下一部分详细研究。