/***********************************
*作者:蔡军生
*出处: http://blog.csdn.net/caimouse/
************************************/
LCC编译器的源程序分析(14)结构类型的声明
以前都是简单类型的识别和语法分析,现在来分析结构的声明,它是比较复杂的一种数据类型,但结构在编写程序中使用是非常多的。由于程序的方程式就是:
数据结构+算法=程序
现在面向对象的方程式是:
数据结构+算法=对象
对象+对象=程序
由上面的公式,就可以看出程序中的数据结构是非常重要的,无论是面向对象的编程,还是面向过程的编程,有什么样的数据结构,就需要有什么样算法。而在C语言里,使用结构类型来描述现实中需要的抽象模型。例子里的结构声明如下:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
这是声明一个文件对象结构。现在就来分析一下LCC编译器是怎么样进行结构的语法分析的。先用调函数decl(dclglobal),然后调用函数specifier,在函数specifier就会处理结构的声明,它的代码如下:
#001 static void decl(Symbol (*dcl)(int, char *, Type, Coordinate *))
#002 {
#003 int sclass;
#004 Type ty, ty1;
…
#074 case STRUCT:
#075 case UNION:
#076 p = &type;
#077 ty = structdcl(t);
#078 break;
上面结构定义和联合的识别,这也是比较复杂的类型,所以也调用structdcl来进一步处理结构体。为了仔细地了解结构的语法分析,就需要分析函数structdcl的代码,如下:
#001 //结构声明处理。
#002 static Type structdcl(int op)
#003 {
#004 char *tag;
#005 Type ty;
#006 Symbol p;
#007 Coordinate pos;
#008
#009 t = gettok();
#010 pos = src;
#011 if (t == ID)
#012 {
#013 tag = token;
#014 t = gettok();
#015 }
#016 else
#017 tag = "";
#018
#019 //结构的定义开始。
#020 if (t == '{')
#021 {
#022 static char stop[] = { IF, ',', 0 };
#023 ty = newstruct(op, tag);
#024 ty->u.sym->src = pos;
#025 ty->u.sym->defined = 1;
#026
#027 t = gettok();
#028 if (istypename(t, tsym))
#029 {
#030 //结构成员定义。
#031 fields(ty);
#032 }
#033 else
#034 {
#035 error("invalid %k field declarations/n", op);
#036 }
#037
#038 test('}', stop);
#039 }
#040 else if (*tag && (p = lookup(tag, types)) != NULL
#041 && p->type->op == op)
#042 {
#043 ty = p->type;
#044 if (t == ';' && p->scope < level)
#045 ty = newstruct(op, tag);
#046 }
#047 else
#048 {
#049 if (*tag == 0)
#050 error("missing %k tag/n", op);
#051 ty = newstruct(op, tag);
#052 }
#053
#054 if (*tag && xref)
#055 use(ty->u.sym, pos);
#056
#057 return ty;
#058 }
处理struct之后,接着就要处理结构的名称,在这个例子里是_iobuf,它是在第9行里获取到这个记号,然后在第11行里保存_iobuf到变量tag里。tag也有可能是空的情况,比如像下面的语法:
typedef struct { int a; } A;
上面的语法就是在第17行里处理这种情况的。
在第20行到39行里,主要处理结构字段定义。像结构_iobuf中的定义,就是处理下面的语句:
{
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
}
在第23行里创建结构的类型ty,然后在第28行里判断语句是否类型开始的记号,如果不是就是出错的定义。如果是类型,比如像例子里的char,就需要调用函数fields(ty)来处理所有的字段定义。
接着下来就需要去分析fields的代码,下一次再带你去分析它吧。
LCC编译器的源程序分析(15)结构类型成员的声明
上次只介绍到开始分析结构类型的定义开始部分,接着就要去分析它的成员类型定义了。它调用函数来处理结构的成员,如下代码:
#001 static void fields(Type ty)
#002 {
#003 {
#004 int n = 0;
#005 while (istypename(t, tsym))
#006 {
#007 static char stop[] = { IF, CHAR, '}', 0 };
#008
#009 Type ty1 = specifier(NULL);
第5行判断是否类型定义,如果是的话就不断地进行字段列表处理。
在第9行里就调用上前介绍过的声明函数specifier来分析,这里也是递归调用的。主要用来分析一行代码的声明处理。
下面第10行的for循环主要是处理声明逗号表达式的。
#010 for (;;)
#011 {
#012 Field p;
#013 char *id = NULL;
#014
#015 Type fty = dclr(ty1, &id, NULL, 0);
#016
#017 p = newfield(id, ty, fty);
#018 if (Aflag >= 1 && !hasproto(p->type))
#019 warning("missing prototype/n");
#020
第15行进行一个声明的处理,也是调用函数dclr来递归处理。
#021 if (t == ':')
#022 {
#023 if (unqual(p->type) != inttype
#024 && unqual(p->type) != unsignedtype)
#025 {
#026 error("`%t' is an illegal bit-field type/n",
#027 p->type);
#028 p->type = inttype;
#029 }
#030
#031 t = gettok();
#032 p->bitsize = intexpr(0, 0);
#033 if (p->bitsize > 8*inttype->size || p->bitsize < 0)
#034 {
#035 error("`%d' is an illegal bit-field size/n",
#036 p->bitsize);
#037 p->bitsize = 8*inttype->size;
#038 }
#039 else if (p->bitsize == 0 && id)
#040 {
#041 warning("extraneous 0-width bit field `%t %s' ignored/n", p-
#042 >type, id);
#043
#044 p->name = stringd(genlabel(1));
#045 }
#046 p->lsb = 1;
#047 }
第21行到第47行是处理结构里按位分配的成员。比如像下面的结构:
struct tag
{
int a:2;
int b:6;
};
第23行到第29行是判断位类型的合法性。
第32行就是处理识别这个位的大小,比如上面的结构里,就是识别2和6的功能。在函数intexpr里计算常量表达的值,比如计算2+5的大小,然后返回给p->bitsize保存起来。
第33行到第45行都是判断这个位大小值是否合法。
#048 else
#049 {
#050 if (id == NULL)
#051 error("field name missing/n");
#052 else if (isfunc(p->type))
#053 error("`%t' is an illegal field type/n", p->type);
#054 else if (p->type->size == 0)
#055 error("undefined size for field `%t %s'/n",
#056 p->type, id);
#057 }
#058
第48行到第58行都是处理结构定义是否出错。
第50行里是判断声明的变量没有出错。
第52行里是判断是否定义函数的类型。
第54行里是判断数据类型的大小错误。
#059 if (isconst(p->type))
#060 ty->u.sym->u.s.cfields = 1;
#061 if (isvolatile(p->type))
#062 ty->u.sym->u.s.vfields = 1;
#063
#064 n++;
#065 if (Aflag >= 2 && n == 128)
#066 warning("more than 127 fields in `%t'/n", ty);
#067 if (t != ',')
#068 break;
#069
#070 t = gettok();
#071 }
#072
第59行是判断类型是否常量类型。
第61行是判断类型是否不可删除的特性。
第67行是判断是否同时声明多个变量,也就是使用逗号表达式的方式。
#073 test(';', stop);
#074 }
#075 }
#076
在第73行里是判断一行代码的声明是否完整。
在第75行就是不断地回到前去分析整个结构的所有声明。
下面的代码主要是处理字段的对齐方式和计算结构的大小。
#077 {
#078 int bits = 0, off = 0, overflow = 0;
#079 Field p, *q = &ty->u.sym->u.s.flist;
#080 ty->align = IR->structmetric.align;
#081
第80行是获取生成代码的对齐方式。
#082 for (p = *q; p; p = p->link)
#083 {
在第82行里,就是遍历整个结构的成员声明。由于在上面调用函数newfield里就生成一个链表来保存所有结构成员类型声明。
#084 int a = p->type->align ? p->type->align : 1;
#085
#086 if (p->lsb)
#087 a = unsignedtype->align;
#088
#089 if (ty->op == UNION)
#090 off = bits = 0;
#091 else if (p->bitsize == 0 || bits == 0
#092 || bits - 1 + p->bitsize > 8*unsignedtype->size)
#093 {
#094 off = add(off, bits2bytes(bits-1));
#095 bits = 0;
#096 chkoverflow(off, a - 1);
#097 off = roundup(off, a);
#098 }
#099
#100 if (a > ty->align)
#101 ty->align = a;
#102
#103 p->offset = off;
#104
#105 if (p->lsb)
#106 {
#107 if (bits == 0)
#108 bits = 1;
#109
#110 if (IR->little_endian)
#111 p->lsb = bits;
#112 else
#113 p->lsb = 8*unsignedtype->size - bits + 1 - p->bitsize + 1;
#114 bits += p->bitsize;
#115 }
#116 else
#117 off = add(off, p->type->size);
#118
#119 if (off + bits2bytes(bits-1) > ty->size)
#120 ty->size = off + bits2bytes(bits-1);
#121
#122 if (p->name == NULL
#123 || !('1' <= *p->name && *p->name <= '9'))
#124 {
#125 *q = p;
#126 q = &p->link;
#127 }
#128 }
#129
#130 *q = NULL;
#131 chkoverflow(ty->size, ty->align - 1);
#132 ty->size = roundup(ty->size, ty->align);
#133 if (overflow)
#134 {
#135 error("size of `%t' exceeds %d bytes/n", ty, inttype->u.sym->u.limits.max.i);
#136 ty->size = inttype->u.sym->u.limits.max.i&(~(ty->align - 1));
#137 }
#138 }
#139 }
上面的代码计算所有成员所占的空间,以及按后端的生成代码的方式来组织对齐方式。并且所有位成员占用的空间。在第131行里要计算结构是否溢出。
分析结构的成员,跟分析其它的声明是大体相同的,主要多了对齐的方式和位结构的方式。由于在汇编里访问结构的成员都是以相对地址来访问一个结构的成员的,因此需要计算所有成员的偏移量,还有不同的CPU对内存的对齐方式也不一样,速度也不一样。比如在IA32里,如果4字节边界对齐的内存变量,访问的速度就比不对齐的快。到这里,就把整个复杂的结构类型声明分析完成了,下一次开始进行函数的声明。
*作者:蔡军生
*出处: http://blog.csdn.net/caimouse/
************************************/
LCC编译器的源程序分析(14)结构类型的声明
以前都是简单类型的识别和语法分析,现在来分析结构的声明,它是比较复杂的一种数据类型,但结构在编写程序中使用是非常多的。由于程序的方程式就是:
数据结构+算法=程序
现在面向对象的方程式是:
数据结构+算法=对象
对象+对象=程序
由上面的公式,就可以看出程序中的数据结构是非常重要的,无论是面向对象的编程,还是面向过程的编程,有什么样的数据结构,就需要有什么样算法。而在C语言里,使用结构类型来描述现实中需要的抽象模型。例子里的结构声明如下:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
这是声明一个文件对象结构。现在就来分析一下LCC编译器是怎么样进行结构的语法分析的。先用调函数decl(dclglobal),然后调用函数specifier,在函数specifier就会处理结构的声明,它的代码如下:
#001 static void decl(Symbol (*dcl)(int, char *, Type, Coordinate *))
#002 {
#003 int sclass;
#004 Type ty, ty1;
…
#074 case STRUCT:
#075 case UNION:
#076 p = &type;
#077 ty = structdcl(t);
#078 break;
上面结构定义和联合的识别,这也是比较复杂的类型,所以也调用structdcl来进一步处理结构体。为了仔细地了解结构的语法分析,就需要分析函数structdcl的代码,如下:
#001 //结构声明处理。
#002 static Type structdcl(int op)
#003 {
#004 char *tag;
#005 Type ty;
#006 Symbol p;
#007 Coordinate pos;
#008
#009 t = gettok();
#010 pos = src;
#011 if (t == ID)
#012 {
#013 tag = token;
#014 t = gettok();
#015 }
#016 else
#017 tag = "";
#018
#019 //结构的定义开始。
#020 if (t == '{')
#021 {
#022 static char stop[] = { IF, ',', 0 };
#023 ty = newstruct(op, tag);
#024 ty->u.sym->src = pos;
#025 ty->u.sym->defined = 1;
#026
#027 t = gettok();
#028 if (istypename(t, tsym))
#029 {
#030 //结构成员定义。
#031 fields(ty);
#032 }
#033 else
#034 {
#035 error("invalid %k field declarations/n", op);
#036 }
#037
#038 test('}', stop);
#039 }
#040 else if (*tag && (p = lookup(tag, types)) != NULL
#041 && p->type->op == op)
#042 {
#043 ty = p->type;
#044 if (t == ';' && p->scope < level)
#045 ty = newstruct(op, tag);
#046 }
#047 else
#048 {
#049 if (*tag == 0)
#050 error("missing %k tag/n", op);
#051 ty = newstruct(op, tag);
#052 }
#053
#054 if (*tag && xref)
#055 use(ty->u.sym, pos);
#056
#057 return ty;
#058 }
处理struct之后,接着就要处理结构的名称,在这个例子里是_iobuf,它是在第9行里获取到这个记号,然后在第11行里保存_iobuf到变量tag里。tag也有可能是空的情况,比如像下面的语法:
typedef struct { int a; } A;
上面的语法就是在第17行里处理这种情况的。
在第20行到39行里,主要处理结构字段定义。像结构_iobuf中的定义,就是处理下面的语句:
{
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
}
在第23行里创建结构的类型ty,然后在第28行里判断语句是否类型开始的记号,如果不是就是出错的定义。如果是类型,比如像例子里的char,就需要调用函数fields(ty)来处理所有的字段定义。
接着下来就需要去分析fields的代码,下一次再带你去分析它吧。
LCC编译器的源程序分析(15)结构类型成员的声明
上次只介绍到开始分析结构类型的定义开始部分,接着就要去分析它的成员类型定义了。它调用函数来处理结构的成员,如下代码:
#001 static void fields(Type ty)
#002 {
#003 {
#004 int n = 0;
#005 while (istypename(t, tsym))
#006 {
#007 static char stop[] = { IF, CHAR, '}', 0 };
#008
#009 Type ty1 = specifier(NULL);
第5行判断是否类型定义,如果是的话就不断地进行字段列表处理。
在第9行里就调用上前介绍过的声明函数specifier来分析,这里也是递归调用的。主要用来分析一行代码的声明处理。
下面第10行的for循环主要是处理声明逗号表达式的。
#010 for (;;)
#011 {
#012 Field p;
#013 char *id = NULL;
#014
#015 Type fty = dclr(ty1, &id, NULL, 0);
#016
#017 p = newfield(id, ty, fty);
#018 if (Aflag >= 1 && !hasproto(p->type))
#019 warning("missing prototype/n");
#020
第15行进行一个声明的处理,也是调用函数dclr来递归处理。
#021 if (t == ':')
#022 {
#023 if (unqual(p->type) != inttype
#024 && unqual(p->type) != unsignedtype)
#025 {
#026 error("`%t' is an illegal bit-field type/n",
#027 p->type);
#028 p->type = inttype;
#029 }
#030
#031 t = gettok();
#032 p->bitsize = intexpr(0, 0);
#033 if (p->bitsize > 8*inttype->size || p->bitsize < 0)
#034 {
#035 error("`%d' is an illegal bit-field size/n",
#036 p->bitsize);
#037 p->bitsize = 8*inttype->size;
#038 }
#039 else if (p->bitsize == 0 && id)
#040 {
#041 warning("extraneous 0-width bit field `%t %s' ignored/n", p-
#042 >type, id);
#043
#044 p->name = stringd(genlabel(1));
#045 }
#046 p->lsb = 1;
#047 }
第21行到第47行是处理结构里按位分配的成员。比如像下面的结构:
struct tag
{
int a:2;
int b:6;
};
第23行到第29行是判断位类型的合法性。
第32行就是处理识别这个位的大小,比如上面的结构里,就是识别2和6的功能。在函数intexpr里计算常量表达的值,比如计算2+5的大小,然后返回给p->bitsize保存起来。
第33行到第45行都是判断这个位大小值是否合法。
#048 else
#049 {
#050 if (id == NULL)
#051 error("field name missing/n");
#052 else if (isfunc(p->type))
#053 error("`%t' is an illegal field type/n", p->type);
#054 else if (p->type->size == 0)
#055 error("undefined size for field `%t %s'/n",
#056 p->type, id);
#057 }
#058
第48行到第58行都是处理结构定义是否出错。
第50行里是判断声明的变量没有出错。
第52行里是判断是否定义函数的类型。
第54行里是判断数据类型的大小错误。
#059 if (isconst(p->type))
#060 ty->u.sym->u.s.cfields = 1;
#061 if (isvolatile(p->type))
#062 ty->u.sym->u.s.vfields = 1;
#063
#064 n++;
#065 if (Aflag >= 2 && n == 128)
#066 warning("more than 127 fields in `%t'/n", ty);
#067 if (t != ',')
#068 break;
#069
#070 t = gettok();
#071 }
#072
第59行是判断类型是否常量类型。
第61行是判断类型是否不可删除的特性。
第67行是判断是否同时声明多个变量,也就是使用逗号表达式的方式。
#073 test(';', stop);
#074 }
#075 }
#076
在第73行里是判断一行代码的声明是否完整。
在第75行就是不断地回到前去分析整个结构的所有声明。
下面的代码主要是处理字段的对齐方式和计算结构的大小。
#077 {
#078 int bits = 0, off = 0, overflow = 0;
#079 Field p, *q = &ty->u.sym->u.s.flist;
#080 ty->align = IR->structmetric.align;
#081
第80行是获取生成代码的对齐方式。
#082 for (p = *q; p; p = p->link)
#083 {
在第82行里,就是遍历整个结构的成员声明。由于在上面调用函数newfield里就生成一个链表来保存所有结构成员类型声明。
#084 int a = p->type->align ? p->type->align : 1;
#085
#086 if (p->lsb)
#087 a = unsignedtype->align;
#088
#089 if (ty->op == UNION)
#090 off = bits = 0;
#091 else if (p->bitsize == 0 || bits == 0
#092 || bits - 1 + p->bitsize > 8*unsignedtype->size)
#093 {
#094 off = add(off, bits2bytes(bits-1));
#095 bits = 0;
#096 chkoverflow(off, a - 1);
#097 off = roundup(off, a);
#098 }
#099
#100 if (a > ty->align)
#101 ty->align = a;
#102
#103 p->offset = off;
#104
#105 if (p->lsb)
#106 {
#107 if (bits == 0)
#108 bits = 1;
#109
#110 if (IR->little_endian)
#111 p->lsb = bits;
#112 else
#113 p->lsb = 8*unsignedtype->size - bits + 1 - p->bitsize + 1;
#114 bits += p->bitsize;
#115 }
#116 else
#117 off = add(off, p->type->size);
#118
#119 if (off + bits2bytes(bits-1) > ty->size)
#120 ty->size = off + bits2bytes(bits-1);
#121
#122 if (p->name == NULL
#123 || !('1' <= *p->name && *p->name <= '9'))
#124 {
#125 *q = p;
#126 q = &p->link;
#127 }
#128 }
#129
#130 *q = NULL;
#131 chkoverflow(ty->size, ty->align - 1);
#132 ty->size = roundup(ty->size, ty->align);
#133 if (overflow)
#134 {
#135 error("size of `%t' exceeds %d bytes/n", ty, inttype->u.sym->u.limits.max.i);
#136 ty->size = inttype->u.sym->u.limits.max.i&(~(ty->align - 1));
#137 }
#138 }
#139 }
上面的代码计算所有成员所占的空间,以及按后端的生成代码的方式来组织对齐方式。并且所有位成员占用的空间。在第131行里要计算结构是否溢出。
分析结构的成员,跟分析其它的声明是大体相同的,主要多了对齐的方式和位结构的方式。由于在汇编里访问结构的成员都是以相对地址来访问一个结构的成员的,因此需要计算所有成员的偏移量,还有不同的CPU对内存的对齐方式也不一样,速度也不一样。比如在IA32里,如果4字节边界对齐的内存变量,访问的速度就比不对齐的快。到这里,就把整个复杂的结构类型声明分析完成了,下一次开始进行函数的声明。