LCC编译器的源程序分析(16)(17)

/***********************************
 *作者:蔡军生
 *出处:http://blog.csdn.net/caimouse/
 ************************************/
  LCC编译器的源程序分析(16)函数的声明
在第一节介绍的例子里,就需要使用到函数声明。比如下面的语句:
       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没有分析,下一次再分析它的实现。

 LCC编译器的源程序分析(17)参数变量的声明
 
函数里的参数变量个数不固定,因此也需要检查这些参数的名称是否相同,还需要检查类型的合法性。现在就来分析上次提到的函数dclparam,它的代码如下:
#001 //参数类型声明处理
#002 static Symbol dclparam(int sclass, char *id, Type ty, Coordinate *pos)
#003 {
#004  Symbol p;
#005
#006  if (isfunc(ty))
#007         ty = ptr(ty);
#008  else if (isarray(ty))
#009         ty = atop(ty);
#010
第6行判断这个参数变量是否声明为函数类型,如果是就需要创建新类型。
第8行判断这个参数变量是否声明为数据类型。
 
#011  if (sclass == 0)
#012         sclass = AUTO;
#013  else if (sclass != REGISTER)
#014  {
#015         error("invalid storage class `%k' for `%t%s/n",
#016               sclass, ty, stringf(id ? " %s'" : "' parameter", id));
#017         sclass = AUTO;
#018  }
#019  else if (isvolatile(ty) || isstruct(ty))
#020  {
#021         warning("register declaration ignored for `%t%s/n",
#022               ty, stringf(id ? " %s'" : "' parameter", id));
#023         sclass = AUTO;
#024  }
#025
第11行到第24行都是判断存储类型。如果没有声明存储类型,缺省为AUTO类型。其它几个识别为寄存器类型REGISTER、不可删除属性、结构类型。
 
#026  p = lookup(id, identifiers);
#027  if (p && p->scope == level)
#028         error("duplicate declaration for `%s' previously declared at %w/n", id, &p->src);
#029  else
#030         p = install(id, &identifiers, level, FUNC);
#031
第26行是查找这个参数变量是否已经在其它地方声明过,如果声明过就从lookup返回给p,如果作用域一样的声明肯定就是重复声明,这是出错的,在第28行里提示出错信息。如果没有这个参数变量是合法的,就把它保存到声明表格identifiers里。
 
 
#032  p->sclass = sclass;
#033  p->src = *pos;
#034  p->type = ty;
#035  p->defined = 1;
#036 
上面几行是保存参数的属性到p符号里。
 
#037  if (t == '=')
#038  {
#039         error("illegal initialization for parameter `%s'/n", id);
#040         t = gettok();
#041         (void)expr1(0);
#042  }
#043
由于在标准C里是不允许参数变量初始化的,所以在第37行就处理这样的出错,如果在C++里应是允许的。
 
#044  return p;
#045 }
在第44行里就是返回分析出来的一个参数变量返回添加到参数列表里。
 
到这里就把所有的函数声明分析完成了,把hello.i文件里的声明已经分析大部份,自定义类型、指针类型、结构类型、函数声明等部分,下面就开始进入到main函数的分析了,这次分析到这里,下次再会 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值