在第一节介绍的例子里,就需要使用到函数声明。比如下面的语句:
printf("nTest3 = %d/r/n",nTest3);
如果没有预先的声明,编译器是找不到它的类型定义,所以编译不通过。因此就需要了解什么是函数的声明,在
LCC
里又是怎么样处理函数的声明的。在
hello.i
文件里,有下面一句的函数声明:
int printf(const char *, ...);
要分析上面的声明,就需要先识别类型
int
,然后再识别
ID
字符串
printf
,最后处理括号和参数列表。在
LCC
的源程序里,先在函数
specifier
函数里就已经可以把类型和
ID
识别出来,并判断语法的合法性,主要还剩下括号和参数列表的分析。下面的代码就是处理它们的,如下:
#001 static Type dclr1(char **id, Symbol **params, int abstract)
#002 {
#003 Type ty = NULL;
#004
#005 switch (t)
…
#067 while (t == '(' || t == '[')
#068 {
#069 switch (t)
#070 {
#071 case '(': //
函数声明。
#072 t = gettok();
#073 {
#074 Symbol *args;
#075 ty = tnode(FUNCTION, ty);
#076
#077 enterscope();
#078 if (level > PARAM)
#079 enterscope();
#080
#081 //
分析参数列表。
#082 args = parameters(ty);
#083
#084 if (params && *params == NULL)
#085 *params = args;
#086 else
#087 exitparams(args);
#088 }
#089 break;
在函数
decl
里调用函数
dclr1
来处理函数的括号和参数列表,在第
67
行那里,就判断是否这个
ID
是否函数的声明开始,如果是左括号的话,就肯定是函数的声明开始了。接着就跳到第
71
行处理,获取下一个记号,就是函数的类型。在第
75
行那里创建一个类型节点,它是函数声明的类型。在第
77
行里增加作用域的计算,也就是参数列表的作用域是只限于参数内部。第
78
行是判断是否在函数声明里再声明一个函数,如是就需要再增加它的作用域。
第
82
行开始调用函数
parameters
来分析参数的列表,比如上面
const char*
等等参数。
在第
84
行是保存分析后的参数列表返回,以便后面构造函数声明的类型。
这样就可以分析完成一个函数的声明,在编写
C
程序过程中需要把函数的声明放到使用函数之前,这样就可以让编译器提前计算好它的类型大小,以及参数的传送过程。
接着下来,就是分析参数列表,看看
LCC
是怎么样来分析参数列表的。
#001 //
函数参数列表分析。
#002 static Symbol *parameters(Type fty)
#003 {
#004 List list = NULL;
#005 Symbol *params;
#006
#007 if (kind[t] == STATIC || istypename(t, tsym))
#008 {
#009 int n = 0;
#010 Type ty1 = NULL;
第
7
行是判断参数类型是否合法,主要是存储类型或者类型声明。
下面的
for
循环就是一个一个地分析逗号分隔的参数。
#011 for (;;)
#012 {
#013 Type ty;
#014 int sclass = 0;
#015 char *id = NULL;
#016 if (ty1 && t == ELLIPSIS)
#017 {
#018 static struct symbol sentinel;
#019 if (sentinel.type == NULL)
#020 {
#021 sentinel.type = voidtype;
#022 sentinel.defined = 1;
#023 }
#024
#025 if (ty1 == voidtype)
#026 error("illegal formal parameter types/n");
#027
#028 list = append(&sentinel, list);
#029 t = gettok();
#030 break;
#031 }
#032
在第
16
行到第
31
行就是处理参数里可变参数(
…
),
C
编译器为了处理可变参数需要从右向左地压参数入栈,以便计算有多少个参数。
ty1
的判断是表示在可变参数之前,一定需要有一个参数,如果只有一个可变参数是非法的。处理可变参数很简单,就是把它类型
sentinel
添加到参数列表
list
。
#033 if (!istypename(t, tsym) && t != REGISTER)
#034 error("missing parameter type/n");
#035
#036 n++;
#037
第
33
行里是判断参数是否有声明类型,如果没有就出错。
第
36
行是增加参数的计算,也可以说参数的名称,由于
LCC
里把所有没有写名称的参数都变成数字化编名,比如第一个参数就叫
1
,第二个参数就叫
2
,依次类推。由于
C
语言里可以声明这样的函数:
int printf(int *,int *,…);
像上面的函数就没有明确指名参数的名称,这样就需要自动地添加参数的名称。
#038 //
分析参数类型声明。
#039 ty = dclr(specifier(&sclass), &id, NULL, 1);
#040
在第
39
行里就是递归调用类型声明函数
dclr
来分析这个参数定义是否合法,而在函数
dclr
调用之前,就需要先递归调用说明符处理函数
specifier
来处理。
#041 if ( ty == voidtype && (ty1 || id) ||
#042 ty1 == voidtype)
#043 error("illegal formal parameter types/n");
#044
#045 if (id == NULL)
#046 id = stringd(n);
#047
#048 if (ty != voidtype)
#049 list = append(dclparam(sclass, id, ty, &src), list);
#050
第
41
行和第
42
行是处理声明为
void
类型语法错误处理。
第
45
行是处理声明参数变量是空时处理,采用自定义的数据作为名称。
第
48
行是判断声明的类型不为空类型的话,就说明已经定义了一个参数变量,需要检查这个参数变量是否已经在前面的局部变量或者全局变量里声明了吗?要解决定这个疑问就需要调用参数变量声明函数
dclparam
来处理。
#051 if (Aflag >= 1 && !hasproto(ty))
#052 warning("missing prototype/n");
#053
#054 if (ty1 == NULL)
#055 ty1 = ty;
#056
#057 if (t != ',')
#058 break;
#059
#060 t = gettok();
#061 }
#062
在第
57
行是判断是否还有参数变量声明,如果没有就跳出
for
循环,处理参数完毕。
#063 fty->u.f.proto = newarray(length(list) + 1,
#064 sizeof (Type *), PERM);
#065 params = ltov(&list, FUNC);
#066 for (n = 0; params[n]; n++)
#067 fty->u.f.proto[n] = params[n]->type;
#068 fty->u.f.proto[n] = NULL;
#069 fty->u.f.oldstyle = 0;
#070 }
第
63
行是创建参数类型原型列表,然后在第
66
行到第
67
行里保存所有参数变量的类型,在第
69
行里指定这个函数声明是新类型的函数声明。
#071 else
#072 {
#073 if (t == ID)
#074 for (;;)
#075 {
#076 Symbol p;
#077 if (t != ID)
#078 {
#079 error("expecting an identifier/n");
#080 break;
#081 }
#082
#083 p = dclparam(0, token, inttype, &src);
#084 p->defined = 0;
#085 list = append(p, list);
#086 t = gettok();
#087 if (t != ',')
#088 break;
#089 t = gettok();
#090 }
#091 params = ltov(&list, FUNC);
#092 fty->u.f.proto = NULL;
#093 fty->u.f.oldstyle = 1;
#094 }
#095
上面这段是处理旧参数列表的声明。比如像下面的声明:
int max(x,y)
int x,int y;
{
return x>y ? x:y;
}
这种方式的声明,现在越来越少了,就不再去分析这种函数的处理代码。
#096 if (t != ')')
#097 {
#098 static char stop[] = { CHAR, STATIC, IF, ')', 0 };
#099 expect(')');
#100 skipto('{', stop);
#101 }
#102
#103 if (t == ')')
#104 t = gettok();
#105 return params;
#106 }
#107
在第
96
行里就是判断是否函数声明结束,如果不是行就进入错误恢复的处理,直找到上面定义的类型字符为止。
通过上面的分析已经了解函数声明的处理过程,已经可以把声明语法分析完成。不过其中还有一个参数变量声明函数
dclparam
没有分析,下一次再分析它的实现。