前一次已经分析了声明的函数,但还有一个声明函数没有分析的,它就是
dclr
函数,这个函数是大内总管,分别调用前面两个声明函数来处理所有的声明语句,接着又会保存声明的
ID
和属性到符号表,当然它需要调用处理函数定义的函数,接着在那里把函数生成汇编代码并写到输出文件里。
现在就来看代码:
#001 static void decl(Symbol (*dcl)(int, char *, Type, Coordinate *))
#002 {
#003 int sclass;
#004 Type ty, ty1;
#005 static char stop[] = { CHAR, STATIC, ID, 0 };
#006
#007 //
#008 ty = specifier(&sclass);
#009
第
3
行定义保存存储类型的局部变量。
第
5
行是定义语法出错时停止的条件。
第
8
行是调用函数
specifier
进行语法的声明处理,前面已经分析这个函数的实现,如果还搞不懂,就需要回过头去看它分析。
#010 if (t == ID || t == '*' || t == '(' || t == '[')
#011 {
#012 char *id;
#013 Coordinate pos;
#014 id = NULL;
#015 pos = src;
#016
#017 if (level == GLOBAL)
#018 {
#019 Symbol *params = NULL;
#020 ty1 = dclr(ty, &id, ¶ms, 0);
#021
在声明分析之后,也就是识别了说明符之后,记号开始一定是
ID
、
'*'
、
'('
、
'['
等几种类型了。如果不是这样的记号,说明语法出错。
第
10
行就是处理这种情况。
第
15
行保存源程序分析的位置。
第
17
行根据当前作用域来分别处理,由于局部声明的作用域是没有参数处理的。
第
20
行是调声明处理函数
dclr
来分析全局变量和函数的声明。
#022 //
判断是否函数定义开始。
#023 if (params && id && isfunc(ty1) &&
#024 (t == '{' || istypename(t, tsym) ||
#025 (kind[t] == STATIC && t != TYPEDEF)))
#026 {
#027 if (sclass == TYPEDEF)
#028 {
#029 error("invalid use of `typedef'/n");
#030 sclass = EXTERN;
#031 }
#032
#033 if (ty1->u.f.oldstyle)
#034 {
#035 exitscope();
#036 }
#037
#038 //
函数定义
,
开始生成代码。
#039 funcdefn(sclass, id, ty1, params, pos);
#040
#041 return;
第
23
行判断是否函数的声明,如果是函数的声明,就再进一步判断是否函数定义的复合语句。如果有函数定义,在第
39
行开始处理函数定义,并生成汇编代码。
#042 }
#043 else if (params)
#044 {
#045 exitparams(params);
#046 }
#047 }
#048 else
#049 {
#050 ty1 = dclr(ty, &id, NULL, 0);
#051 }
#052
第
43
行到第
46
行处理参数列表,退出参数的作用域。
#053
#054 for (;;)
#055 {
#056 if (Aflag >= 1 && !hasproto(ty1))
#057 warning("missing prototype/n");
#058
#059 if (id == NULL)
#060 {
#061 error("missing identifier/n");
#062 }
#063 else if (sclass == TYPEDEF)
#064 {
#065 Symbol p = lookup(id, identifiers);
#066 if (p && p->scope == level)
#067 error("redeclaration of `%s'/n", id);
#068
#069 p = install(id, &identifiers, level,
#070 level < LOCAL ? PERM : FUNC);
#071
#072 p->type = ty1;
#073 p->sclass = TYPEDEF;
#074 p->src = pos;
#075 }
#076 else
#077 {
#078 (void)(*dcl)(sclass, id, ty1, &pos);
#079 }
#080
#081 if (t != ',')
#082 break;
#083
#084 t = gettok();
#085 id = NULL;
#086 pos = src;
#087 ty1 = dclr(ty, &id, NULL, 0);
#088 }
第
54
行到第
88
行是处理声明变量。
第
54
行的
for
循环是用来处理声明的变量是逗号表达式时并列处理。
第
59
行是处理声明没有
ID
的出错情况。
第
63
行到第
75
行是处理
typedef
定义的类型声明。
第
65
行从符号表
identifiers
里查找这个
ID
是否已经声明了,如果有声明过并且作用域一样,就表示重复声明了同一个
ID
。它是在第
66
行到第
67
行里处理的。
第
69
行是保存这个
ID
到符号表
identifiers
,同时在第
72
行保存声明的类型,在第
73
行保存存储的类型,在第
74
行保存源程序位置。
第
78
行是调用全局函数
dclglobal
,或者局部函数
dcllocal
,或者参数函数
dclparam
来处理变量
ID
。
#089 }
#090 else if (ty == NULL ||
#091 !(isenum(ty) ||
#092 isstruct(ty) && (*unqual(ty)->u.sym->name < '1' ||
#093 *unqual(ty)->u.sym->name > '9')))
#094 {
#095 error("empty declaration/n");
#096 }
#097
#098 test(';', stop);
#099 }
第
90
行到第
96
行都是处理出错的情况。
第
98
行是测试当前的记号是否语句结束符,如果不是,就会跳过错误直到下一句语句再进行分析处理。
通过上面函数的分析,像语句(
typedef unsigned int size_t;
)已经处理完成,并保存到
identifiers
符号表里,以便后面使用
size_t
类型来定义新变量时查找类型的属性。所有再遇到像
typedef
的声明,都是这样处理的,通过这个例子来分析
C
编译器的源程序,就会简单很多。有实际的例子并同步跟踪源程序运行的路径,其实就是流程图的表现。像这种简单语法分析,是没有太多递归调用和复杂的处理,后面会跟着例子里其它语句再深入体会
C
编译器是怎么处理它们的,同时明确地理解源程序的作用。