UCC 编译器内部用英文单词 generate 来表示中间代码的生成,而用 emit 来表
示汇编代码的生成,这里我们统一翻译为“生成”。
第 30 至 34 行的代码用于保存寄存器的值,第 35 行用于在栈空间中预留内存空
间,用来存放局部变量和临时变量,这部分工作被称为“Prologue 序言”,即在函数开始执
行时要处理的工作。而图 6.1.1 第 53 至 57 行被称为“Epilogue 尾声”,用于恢复原先保存的
寄存器值,第 58 行的汇编指令 ret 用于从栈中取出返回地址并返回。而函数 f 的返回值在第
50 至 51 行进行计算,并保存于寄存器 eax。
/**
* Emit assembly code for the translation unit
*/
void EmitTranslationUnit(AstTranslationUnit transUnit)
{
if(ASMFileName){
ASMFile = fopen(ASMFileName, "w");
ASMFileName = NULL;
}else{
ASMFile = CreateOutput(Input.filename, ExtName);
}
SwitchTableNum = 1;
// "# Code auto-generated by UCC\n\n"
BeginProgram(); //就是做一些准备工作,把寄存器这些准备好
// ".data\n\n"
Segment(DATA); //数据区开始了
/**************************************************
.str0: .string "%d \012"
.str1: .string "a + b + c + d = %d.\012"
**************************************************/
EmitStrings(); //字符串,浮点数常量,全局变量,都会被UCC给收集起来
EmitFloatConstants();
EmitGlobals();
// ".text\n\n"
Segment(CODE); //代码区开始了
ImportFunctions();
/************************************
The key function is
void EmitFunction(FunctionSymbol fsym)
in x86.c
************************************/
EmitFunctions(transUnit);
EndProgram();
fclose(ASMFile);
}
对应的汇编代码如下所示,还是挺有意思的:
按 C 标准的规定,当“函数的返回值是结构体对象,且该对象的大小
不落在{1,2,4,8}”时(因为这些大小是基本类型),C 编译器会隐式地为该函数添加一个参数,该参数的类型是指向结构体对象的指针。例如,以下结构体 struct Data 的对象要占 32 字节,C 编译器会为函数GetData 隐式地添加一个 struct Data * recvaddr 参数,图 6.1.2 第 36 至 47 行的 if 语句用于对此进行处理。
struct Data {int dt[8];};
// C 程序员设定的函数接口
struct Data GetData(int num);
//被 C 编译器隐式地改为
void GetData(struct Data * recvaddr, int num);