lcc是一款小巧的工业级编译器,代码精简,代码开源,相比gcc更适合编译器初学者阅读。
你可以在这里搞到代码:https://github.com/drh/lcc
但是,怎么说呢,这个代码防盗性较强,几乎没有注释,
我在阅读源码中参考了其他前辈关于lcc的文章以及官方推荐书籍《a Retargetable C Compiler---Design and Implementation》
同时为了为了给其他想学习编译器相关知识的同学提供些帮助,所以我在阅读源代码中用中文对源代码做了较为详细的注释。
中间有理解不对的地方,欢迎指正:jinn.yette@gmail.com
本文解析编译器中的一个很重要的模块,它贯穿整个编译过程,贯穿前端后端。
符号的结构定义在c.h中,如下
struct symbol {
char *name; //符号的名称,大多数情况是源程序的符号
int scope; //符号作用域,常量CONSTANT,标号LABEL,全局GLOBAL,参数PARAM还是局部变量LOCAL,在第i层生成的local变量,其scope等于LOCAL+i
Coordinate src; //符号定义处的位置:文件名,行号和列号
/*up字段比较重要,它将符号表中所有符号链接成一个链表,最后进入符号表的那个符号为首
从后向前遍历该链表可以访问当前作用域内的所有符号,包括被内嵌符号隐藏的符号
这就提供了除hash方式外另外一个符号表查询方式。*/
Symbol up;
List uses;//如果uses保存一个Coordiante链表,则可表明一个符号的所有使用信息,也可置null
int sclass; //符号的扩展存储类型,AUTO/REGISTER/STATIC/EXTERN/TYPEDEF/ENUM,常量和标号不使用该域
unsigned structarg:1;//结构参数标志
unsigned addressed:1;//地址访问的变量
unsigned computed:1; //地址树的标志.addrtree函数处理
unsigned temporary:1;//生成的临时变量标志
unsigned generated:1;//生成的符号标志
unsigned defined:1; //符号被定义了,避免声明多次
Type type;//变量或者常量的类型
float ref; //标号或变量的引用计数
/*以上各项对于所有符号表的所有符号通用,常量和标号函数需要使用下面union中的一些域*/
union {
//保存标号
struct {
int label; //全局分配唯一的标号,这时name保存标号字符串
Symbol equatedto;
} l;
struct {
unsigned cfields:1;
unsigned vfields:1;
Table ftab; /* omit */
Field flist;
} s;
int value;
Symbol *idlist;
struct {
Value min, max;
} limits;
//保存常量的结构
struct {
Value v;//保存实际的常量值
Symbol loc; //指向符号表的入口
} c;
struct {
Coordinate pt;
int label;
int ncalls;
Symbol *callee;
} f;
int seg; //全局变量或静态变量给出定义的段
Symbol alias;
struct {
Node cse; //前端生成多次引用公共表达式的临时变量的DAG节点
int replace;
Symbol next;
} t;
} u;
Xsymbol x;//后端使用的符号扩展,为变量分配的寄存器,调试信息数据等
};
代码主要在sym.c文件中,如下
#include "c.h"
#include <stdio.h>
static char rcsid[] = "$Id: sym.c,v 1.1 2002/08/28 23:12:47 drh Exp $";
#define equalp(x) v.x == p->sym.u.c.v.x
struct table {
int level;//符号表作用域
Table previous;//指向外层(上一层)作用域对应的table
/**/
struct entry {
struct symbol sym