📄 main.c
字号:
/******************************************************************************
*******************************************************************************
文件名:main.c 源程序
本文件包含了main、编译处理和管理内存的函数的定义
文件修改时间
============
2005年5月7日: TurboC 版, (C) hfwang
*******************************************************************************
******************************************************************************/
/* 包含本模块所需的头文件,parser.h是由YACC产生的头文件,它有终结符的宏定义和语
义值的数据类型定义。该头文件yacc加选项“-d”产生。 */
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <alloc.h>
#include "vc.h"
#include "parser.h"
/* 以下定义本模块需要的常量,为了提高符号表的工作效率,我们在系统初始化时,预装
一组小整数常数,CONST_MAX是这一组整数的上界,由于库函数中也使用了标号,因此
标号计数器的初值设为LAB_MIN,比LAB_MIN小的标号为库函数所用。 */
#define CONST_MAX 5
#define LAB_MIN 10
/* 以下定义本模块需要引用的外部变量,其定义在词法分析和语法分析模块之中 */
extern char yytext[] ; /* 当前扫描词形 */
extern int yyleng ; /* 当前词形的长度 */
extern int lineno; /* 当前行号 */
extern YYSTYPE finaltac ; /* 最后产生的三地址码指针 */
extern FILE * yyin; /* 文件指针,在lexyy.c中定义,
指向编译器将分析的源程序。*/
/* We define a number of static variables used throughout the compiler. These
have been declared external in the header file, and are defined here. */
/* 以下定义全局变量,其引用定义和说明参见头文件vc.h */
SYMB *symbtab[HASHSIZE] ; /* 符号表 */
TAC *library[LIB_MAX] ; /* 函数库数组 */
int next_tmp ; /* 临时变量计数器 */
int next_label ; /* 标号计数器 */
/* 下述全局变量中,数组const_tab是为了存放预装符号表的小于CONST_MAX的整数;
errors_found是出错标志,当编译出错调用错误处理函数error时,设errors_found为
TRUE(1),此时,不再进行代码生成处理。
为了避免频繁申请内存空间,提高符号表的工作效率,我们通过符号表中的元素提供
的next指针将废弃不用的符号表单元回收形成一个链表,在申请符号表单元时,先查
看是否有废弃的空白单元,如果有使用该单元,并把它从链表中删除;如果没有才调
用申请内存函数,symb_list记录该链表表头。同样的方法处理表达式节点,用
enode_list将废弃的表达式存储单元联成一个链表 */
SYMB *const_tab[CONST_MAX] ; /* 存放小整数的数组 */
int errors_found ; /* 出错标记,出错时标记设为TRUE */
SYMB *symb_list ; /* 空白元素链表的表头指针 */
ENODE *enode_list ;
/* 以下是本模块定义的函数的原形 */
void main(int argc, char ** argv) ;
void init_vc( void ) ;
SYMB *mkconst( int n ) ;
SYMB *mklabel( int l ) ;
SYMB *mktmp( void ) ;
SYMB *get_symb( void ) ;
void free_symb( SYMB *s ) ;
ENODE *get_enode( void ) ;
void free_enode( ENODE *e ) ;
void *safe_malloc( int n ) ;
TAC *mktac( int op,
SYMB *a,
SYMB *b,
SYMB *c ) ;
TAC *join_tac( TAC *c1,
TAC *c2 ) ;
void insert( SYMB *s ) ;
int hash( char *s ) ;
SYMB *lookup( char *s ) ;
void print_instr( TAC *i ) ;
char *ts( SYMB *s,
char *str ) ;
void error( char *str ) ;
int yyparse( void );
void main( int argc, char ** argv)
/* 主函数main首先提取第一个命令参数,该参数是要编译的源程序名,打开该文件,
并将文件指针赋值该全局变量yyin;接着初始化编译,执行语法分析程序yyparse,
如果语法分析正确,执行代码生成程序cg,产生输入文件的汇编代码。*/
{
++argv, --argc;
if( argc >0)
yyin = fopen(argv[0],"r");
else
yyin = stdin;
init_vc() ; /* 系统初始化 */
(void) yyparse() ; /* 语法分析 */
if( !errors_found )
cg( finaltac.tac ) ; /* 代码生成 */
} /* main( void ) 结束 */
void init_vc( void )
/* 系统初始化包括:全局变量的初值的设定,符号表的清零,小于COUNT_MAX - 1的正整
数预装符号表,在函数库中标号L2是函数PRINTN的入口点,标号L4是函数L4的入口点,
因此,在函数库数组中,分别预装标号为L2和L4的标号语句的三地址码指针。当然,
这不是一般的处理库函数的方法,一般的方法应该是:扫描库函数文件,动态生成库函
数的入口点的标号语句的三地址码指针链表,在此用静态的方法是为了简化编译器的处
理。由于标号0到9已被库函数占用,因此标号计数器next_label的初值为LAB_MIN(10)。
*/
{
int i ; /* 循环控制变量 */
symb_list = NULL ; /* 空白元素链表的初值为空 */
enode_list = NULL ;
errors_found = FALSE ; /* 出错标记为假FALSE(0) */
next_tmp = 0 ; /* 临时变量计数器为0 */
next_label = LAB_MIN ; /* 比10小的标号保留给库函数 */
for( i = 0 ; i < HASHSIZE ; i++ ) /* 符号表清零 */
symbtab[i] = NULL ;
for( i = 0 ; i < CONST_MAX ; i++ ) /* 构造小整数的符号表单元 */
{
SYMB *c = get_symb() ; /* 申请一个符号表单元 */
c->type = T_INT ; /* 常数类型 */
c->VAL1 = i ; /* 常数的数值为i */
const_tab[i] = c ; /* 常数数组的第i个元素赋值为申请
的内存单元指针c */
}
library[LIB_PRINTN] = mktac( TAC_LABEL, mklabel( 2 ), NULL, NULL ) ;
library[LIB_PRINTS] = mktac( TAC_LABEL, mklabel( 4 ), NULL, NULL ) ;
/* mktac申请一个标号语句三地址码存储空间,其标号分别是2和4,标号通过创
建标号函数产生,标号2和4分别是打印数字函数PRINTN和打印字符函数PRINTS
在库函数的入口标号。*/
} /* init_vc( void ) 结束 */
/* 由于编译器的存储空间大部分是通过动态申请获得,因此对内存的管理是编译器的关
键,设xxx是某一结构类型,对xxx的管理一般用到三个程序:mkxxx()申请分配一个类型
为xxx的存储空间并设置每个域的值;get_xxx()申请分配一个类型为xxx的存储空间,
但并不对每个域赋值;free_xxx()回收一个类型为xxx的存储空间。*/
SYMB *mkconst( int n )
/* mkconst()首先检查n是否是预装的整数,如果不是上述情况,申请一个新类型为SYMB
内存空间,并设置相应的域。虽然预装的小整数增加了一定的内存开销,但考虑到在
程序中小整数的频繁的使用,该方法减少了mkconst()对内存的申请,从而提高了程序
的效率。*/
{
if((n >= 0) && (n < CONST_MAX))
return const_tab[n] ;
else
{
SYMB *c = get_symb() ; /* 申请内存空间 */
c->type = T_INT ;
c->VAL1 = n ;
return c ;
}
} /* SYMB *mkconst( int n ) 结束 */
SYMB *mklabel( int l )
/* 申请一个新类型为SYMB内存空间,并设置相应的域。其中域type为标号T_LABEL,标
号编码为l */
{
SYMB *t = get_symb() ;
t->type = T_LABEL ;
t->VAL1 = l ;
return t ;
} /* SYMB *mklabel( int l ) */
SYMB * mktmp ( void )
/* 申请一个新类型为SYMB内存空间,并设置相应的域。其中域type为标号T_VAR,变量
名为"T临时变量计数器" */
{
char text[5]; /* 字符数组, 保存临时变量名 */
SYMB *t;
char *s;
int leng;
sprintf(text, "T%d", next_tmp++);
leng = strlen(text);
s = (char *) safe_malloc(leng + 1);
/* 注意由于text是局部变量, 在函数返
回后, 其内存空间将被释放, 因此必
动态申请内存, 将变量名永久保留下
来, safe_malloc()是一个安全申请
分配内存函数, 见下面的定义 */
strncpy(s, text, leng);
s[leng] = EOS;
t = get_symb();
t->type = T_VAR;
t->TEXT1 = s;
insert(t); /* 插入符号表 */
return t;
} /* SYMB *mktmp( void ) 结束 */
SYMB *get_symb( void )
/* 申请一个符号表元素的内存空间, 程序首先选择空白链表头元素, 如果为表头为空, 则
用safe_malloc()申请内存空间,用此方法避免了频繁调用malloc()函数。 */
{
SYMB *t ;
if( symb_list != NULL ) /* 查看空白链表的表头是否为空 */
{
t = symb_list ;
symb_list = symb_list->next ; /* 设置新的表头 */
}
else
t = (SYMB *)safe_malloc( sizeof( SYMB )) ;
/* 空白链为空时,申请内存空间 */
return t ;
} /* SYMB *get_symb( void ) 结束 */
void free_symb( SYMB *s )
/* 本函数是get_symb()的姊妹函数, 回收不再使用的符号表元素,不是简单地将内存空间
释放,而是将之回收,放入空白链中并作为空白链的表头,以供重复使用。*/
{
s->next = symb_list ;
symb_list = s ;
} /* void free_symb( SYMB *s ) 结束 */
ENODE *get_enode( void )
/* 分配一个类型为ENODE的内存空间,该函数和free_enode()函数分别同get_symb()和
free_symb()相类似。 */
{
if( enode_list != NULL )
{
ENODE *expr ;
expr = enode_list ;
enode_list = expr->next ;
return expr ;
}
else
return (ENODE *)safe_malloc( sizeof( ENODE )) ;
} /* ENODE *get_enode( void ) 结束 */
void free_enode( ENODE *expr )
/* 回收一个类型为ENODE的内存空间 */
{
expr->next = enode_list ;
enode_list = expr ;
} /* void free_enode( ENODE *expr ) 结束 */
void *safe_malloc( int n )
/* 为了避免每次调用系统函数malloc()时都要检查是否返回空指针,在此自定义一个申
请内存函数safe_malloc(),改函数调用malloc(),并检查申请内存是否成功,如果成
功返回该内存指针,否则报错并退出程序执行。 */
{
void *t = malloc( n ) ;
/* 检查申请是否成功 */
if( t == NULL )
{
error( "内存枯竭,malloc()失败!!!\n" ) ;
exit( 0 ) ; /* 如果失败,则报错并停止执行 */
}
return t ;
} /* void *safe_malloc( int n ) 结束 */
/* 以下两函数处理三地址码的内存空间的申请和两个三地址码链表的连接。由于三
地址码一旦产生,编译器就要使用到最后代码生成,因此,没有释放对它的释放
函数。*/
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -