在语法分析里,最主要的组成部份是声明分析,并且这是
C
语言编译器最复杂的组成部分。由于任何变量都需要声明,那么怎么样知道这个变量声明是合法的呢?现在带着这个问题去分下面的代码。
为了理解代码的工作,先来看前面的例子里的第一行有效代码:
typedef unsigned int size_t;
在这句语句里,使用类型定义关键字来声明了一个无符号整数的类型
size_t
,为了识别这句语句的语法,那么最开始的是类型关键字,它相当于存储类型。接着是无符号、整型,最后才是标识
ID
。其实上面这句语句也可能有这种形式,如下:
typedef int size_t;
那么上面这句就上面那句少了一个无符号的说明。
要分析这种声明,那么就需要一个函数来把这些属性整理出来,并断它的合法性。
LCC
里的声明分析是在函数
decl
里调用函数
specifier
来实现的:
#001 //
说明
#002 static Type specifier(int *sclass)
#003 {
#004 int cls, cons, sign, size, type, vol;
#005 Type ty = NULL;
#006
#007 cls = vol = cons = sign = size = type = 0;
#008 if (sclass == NULL)
#009 {
#010 cls = AUTO;
#011 }
#012
第
2
行里输入了获取声明的存储类型参数
sclass
。
一个声明变量有可能出现的说明符就有以下几种:
存储类型、常量说明、符号说明、类型大小、类型、是否删除。
比如像这些语句:
auto int a;
register int iLoop;
auto unsigned int nRet;
static unsigned int g_nRet;
const unsigned int cnstRet;
第
4
行里就是定义上面几种说明的类型变量保存。
第
5
行里定义了返回类型保存变量。
第
7
行是把所有类型说明初始化为
0
值,表示没有出现这种说明。
第
8
行到第
11
行是把存储类型设置为自动类型。由于在
C
语言里,所有变量声明如果没有明确地指明存储类型时,缺省就是自动类型
AUTO
。
下面就通过循环来分析出
6
种说明:
#013 for (;;)
#014 {
#015 int *p, tt = t;
#016 switch (t)
#017 {
#018 case AUTO:
#019 case REGISTER:
#020 if (level <= GLOBAL && cls == 0)
#021 error("invalid use of `%k'/n", t);
#022
#023 p = &cls;
#024 t = gettok();
#025 break;
#026
第
16
行使用
switch
语句来处理不同的记号类型。
第
18
行和第
19
行就是分析存储类型
AUTO
和
REGISTER
。
第
20
行是当全局变量声明而又声明为寄存器类型,就出会提示出错。
第
23
行保存当前识别出来的类型说明。
第
24
行获取下一个记号。
#027 case STATIC:
#028 case EXTERN:
#029 case TYPEDEF:
#030 p = &cls;
#031 t = gettok();
#032 break;
#033
上面识别
static
,
extern
,
typedef
的存储类型说明。
#034 case CONST:
#035 p = &cons;
#036 t = gettok();
#037 break;
#038
上面识别常量说明。
#039 case VOLATILE:
#040 p = &vol;
#041 t = gettok();
#042 break;
上面识别优化限制说明,当指定这个属性时,表示这个变量不能优化掉的。
#043 case SIGNED:
#044 case UNSIGNED:
#045 p = &sign;
#046 t = gettok();
#047 break;
上面无符号和有符号的识别。
#048 case LONG:
#049 if (size == LONG)
#050 {
#051 size = 0;
#052 tt = LONG+LONG;
#053 }
#054 p = &size;
#055 t = gettok();
#056 break;
上面识别
long
类型和
long long
的
64
位的类型。
#057 case SHORT:
#058 p = &size;
#059 t = gettok();
#060 break;
#061 case VOID:
#062 case CHAR:
#063 case INT:
#064 case FLOAT:
#065 case DOUBLE:
#066 p = &type;
#067 ty = tsym->type;
#068 t = gettok();
#069 break;
上面这些都是简单类型的识别。
#070 case ENUM:
#071 p = &type;
#072 ty = enumdcl();
#073 break;
上面是枚举类型识别,调用函数
enumdcl
进行类型的分配。后面再仔细地讨论怎么样处理枚举类型的成品。
#074 case STRUCT:
#075 case UNION:
#076 p = &type;
#077 ty = structdcl(t);
#078 break;
上面结构定义和联合的识别,这也是比较复杂的类型,所以也调用
structdcl
来进一步处理结构体。
#079 case ID:
#080 if (istypename(t, tsym) && type == 0
#081 && sign == 0 && size == 0)
#082 {
#083 use(tsym, src);
#084 ty = tsym->type;
#085
#086 if (isqual(ty) && ty->size != ty->type->size)
#087 {
#088 ty = unqual(ty);
#089
#090 if (isconst(tsym->type))
#091 ty = qual(CONST, ty);
#092
#093 if (isvolatile(tsym->type))
#094 ty = qual(VOLATILE, ty);
#095
#096 tsym->type = ty;
#097 }
#098
#099 p = &type;
#100 t = gettok();
#101 }
#102 else
#103 {
#104 p = NULL;
#105 }
#106 break;
当把所有的说明符分析完成后,最后肯定是剩下一个
ID
,如果不是就有可能出错的。
在第
80
行到第
101
行里处理
ID
是自己定义的类型处理,比如用
typedef
定义的
ID
类型,就需要在那里识别出类型的属性。
如果这个
ID
是变量,就会在第
104
行设置为空,并且在后面的第
111
行到第
114
行里跳出来
for
循环。
#107 default:
#108 p = NULL;
#109 }
#110
#111 if (p == NULL)
#112 {
#113 break;
#114 }
#115
#116 if (*p)
#117 {
#118 error("invalid use of `%k'/n", tt);
#119 }
#120
#121 *p = tt;
#122 }
上面在第
113
行里跳出了
for
循环,意味着整声明已经分析完成,接着就是把所有分析出来的说明符组成属性结构,保存了到相应的符号表里。
#123
#124 if (sclass)
#125 *sclass = cls;
#126
#127 if (type == 0)
#128 {
#129 type = INT;
#130 ty = inttype;
#131 }
#132
第
124
行到第
125
行保存存储类型返回给调用函数。
第
127
行到第
131
行是设置类型为缺省值。
#133 if (size == SHORT && type != INT
#134 || size == LONG+LONG && type != INT
#135 || size == LONG && type != INT && type != DOUBLE
#136 || sign && type != INT && type != CHAR)
#137 {
#138 error("invalid type specification/n");
#139 }
#140
第
133
行到第
136
行里都判断声明组合是否合法,如果不合法的组合,就需要提示出错。
#141 if (type == CHAR && sign)
#142 {
#143 ty = sign == UNSIGNED ? unsignedchar : signedchar;
#144 }
#145 else if (size == SHORT)
#146 {
#147 ty = sign == UNSIGNED ? unsignedshort : shorttype;
#148 }
#149 else if (size == LONG && type == DOUBLE)
#150 {
#151 ty = longdouble;
#152 }
#153 else if (size == LONG+LONG)
#154 {
#155 ty = sign == UNSIGNED ? unsignedlonglong : longlong;
#156 if (Aflag >= 1)
#157 warning("`%t' is a non-ANSI type/n", ty);
#158 }
#159 else if (size == LONG)
#160 {
#161 ty = sign == UNSIGNED ? unsignedlong : longtype;
#162 }
#163 else if (sign == UNSIGNED && type == INT)
#164 {
#165 ty = unsignedtype;
#166 }
#167
第
141
行到第
166
行就是根据符号和类型来判断声明的类型,由于类型的大小不同,符号位不同,而组成不同的类型。这些类型都是
C
编译器预先定义好的。不知道你还记起最开始的类型初始化没有?如果不记起,就回过头去看看。
#168 if (cons == CONST)
#169 ty = qual(CONST, ty);
#170
#171 if (vol == VOLATILE)
#172 ty = qual(VOLATILE, ty);
#173
#174 return ty;
#175 }
第
168
行把类型添加常量属性。
第
171
行是类型添加不可删除属性。
第
174
行就把声明的类型返回给调用函数。
通过上面的代码,就可以把
C
里的声明分析出来,如果不合法的声明就会出错。如果合法的声明,就返回相应的类型属性。只要有了声明的类型属性,那么一个变量的定义就完全了解了,也就是说它的语义已经确定下来。只剩下了一个问题没有解决?这个问题是什么呢?下一次再告诉你吧,这一次分析的代码够长的了。