我们再分析条件表达式的翻译,以下给出了一段 C 程序及其对应的中间代码,
我们生成临时一个临时变量 t0 用来存放 b+2 或 b+3 的值,然后再把 t0 赋值给变量 c。
/************************************************************************
 Syntax
           conditional-expression:
                   logical-OR-expression
                   logical-OR-expression ?  expression :  conditional-expression
 *************************************************************************/
static Symbol TranslateConditionalExpression(AstExpression expr)
{   //翻译c = a > 0? b+2: b+3;这样的语句,语法树为:  ? a>0 (: b+2 b+3)
    Symbol t, t1, t2;
    BBlock trueBB, falseBB, nextBB;
    t = NULL;
    /**************************************************
    int a ,b,c;
    c = a > 0? b+2: b+3;
//对应的中间代码
    if (a <= 0) goto BB7;
BB6: // trueBB
    t1 : b + 2;
    t0 = t1;
    goto BB8;
BB7: // falseBB
    t2 : b + 3;
    t0 = t2;
BB8: // nextBB
    c = t0;
     ***************************************************/
    if (expr->ty->categ != VOID)    //这个类型是表达式的类型,语法分析时候记录的是整个表达式结果值的类型
    {
        t = CreateTemp(expr->ty);   //创建临时变量t
    }
    trueBB = CreateBBlock();        //创建3个块
    falseBB = CreateBBlock();
    nextBB = CreateBBlock();
    // to be consistent with if(){}else{}   这儿又是套路TranslateBranchExpression()函数一样
    TranslateBranch(Not(expr->kids[0]), falseBB,trueBB);//产生if (a <= 0) goto BB7;指令。GenerateBranch(ty, trueBB, JZ, src1, NULL); “jz=jump if zero,即零标志为1就跳转,最终还是落实到了汇编自己判断这儿来了,因此中间代码翻译的目的就是把汇编期待的一切都给汇编准备好,汇编就直接生成即可
StartBBlock(trueBB);    
    t1 = TranslateExpression(expr->kids[1]->kids[0]); //翻译“b+2
    if (t1 != NULL)
        GenerateMove(expr->ty, t, t1);              //产生 MOV 指令,“t0 = t1;”,
    GenerateJump(nextBB);                           //产生无条件跳转指令“goto BB8;
    StartBBlock(falseBB);
    t2 = TranslateExpression(expr->kids[1]->kids[1]);
    if (t2 != NULL)
        GenerateMove(expr->ty, t, t2);
    StartBBlock(nextBB);
    return t;
}由于局部变量的存储空间是运行时在栈中动态分配的,UCC 编译器需要在编译时产生
中间代码来实现对局部变量的初始化,而在对应的汇编代码中,由于无法预知相应的存储单
元,我们只能采用“ebp 寄存器+常量偏移”的模式来对其寻址。程序运行时,我们会在栈
中为被调用的函数分配一块内存,用于存放其形参、局部变量、函数返回地址等信息,这块
内存通常被称为“活动记录 activation record”或者“帧 frame”。大部分的 C 编译器,会使
用 x86 的 ebp 寄存器来指向当前函数的活动记录;而“常量偏移”则可由 C 编译器在编译
时,根据局部变量声明在函数中出现的先后顺序和所占内存大小来确定。(如果要生成调试信息给调试器用,那这些信息就很重要,调试时候能定位出变量的地址,从而查看变量地址)
对于以下局部数组 arr 的初始化,我们可以先把 arr 所占的栈空间清 0,然后再根据“初
值及其偏移”来产生初始化相应数组元素的指令。
int arr[8] = {10,20,30};
我们在语义检查时介绍过以下结构体 struct initData,其中的 offset 即可用于描述偏移,
而 expr 对应的表达式为初值,而 next 用于构造链表。
struct initData{
 int offset; //偏移
 AstExpression expr; //初值对应的表达式
 InitData next;
 };
 typedef struct initData * InitData;上述初值{10,20,30}经语义检查后,我们可得到以下链表,在中间代码生成阶段,我们
可由该链表产生“对相应数组元素进行初始化”的中间代码。
(表达式 10,偏移 0) --> (表达式 20,偏移 4) --> (表达式 30,偏移 8)
///对应的中间代码
 arr : 40; //把 arr 所占 40 字节栈内存清 0
 arr[0] = 10;
 arr[4] = 20;
 arr[8] = 30;
 而到了汇编层次,局部变量的名字不再可用,我们改用“ebp 寄存器+常量偏移”的方
 式来表示局部变量。对应的汇编代码如下所示:
 pushl $40        //这个是把形参1入栈,需要传给memset函数
 pushl $0          //这个是把形参2入栈,需要传给memset函数
 leal -40(%ebp), %eax
 pushl %eax    //这个是把arr的基址拿到,形参3,需要传给memset函数
call memset //对数组 arr 清 0,相当于调用 memset(&arr[0], 0, 40);
 addl $12, %esp
 movl $10, -40(%ebp) //arr[0] = 10;
 movl $20, -36(%ebp)
 movl $30, -32(%ebp)对于以下局部数组 num 来说,由于初值{1,2,3,4}覆盖了数组 num 所占的所有内存空间,
我们并不需要额外产生对数组 num 进行清 0 的指令。
int num[4] = {1,2,3,4};








