step12 实验指导

本实验指导使用的例子为:

int func(int param[]){
    param[0] = 1;
    return 0;
}

int main() {
    int arr[4] = {1,2};
    func(arr);
    return arr[0] + arr[1] + arr[2];
}

词法语法分析

我们需要增加一个数组的初始化列表,可以直接修改上一节数组的AST结点增加一个数组用于记录初始化元素。

函数的参数列表需要加上数组类型。

语义分析

由于 step 12 里额外引入了数组传参和数组初始化,所以你需要修改语义分析,以支持数组传参。传参出现了一种特殊情况,即:函数参数数组的第一维可以为空。

int fun(int a[][12]){
    a[0][1] = 1;
    return 0;
}

中间代码生成

在C语言中,对于全局数组,如果没有初始化,那么其值全为0,而对于局部数组来说,如果没有初始化,其值是未定义的。

而初始化后数组的元素值是确定的,如果初始化时指定的的元素个数比数组大小少,剩下的元素都回被初始化为 0。例如:

int arr[3]={1,2};
// 等价于
int arr[3]={1,2,0};

当数组长度较长时,如果对每个位置产生一条赋值语句可能会让生成的汇编代码非常冗长。因此你可能需要内置一个 memset 这样的函数来实现数组数组的清零,我们的运行时库中也提供了 fill_n 函数用来给数组批量赋值,你可以按照 RISC-V 标准调用约定来使用这个函数,因此数组清零不是一件难事。你可以查看 runtime.hruntime.c 来了解 fill_n 函数的实现,其使用方式和功能同 C++ 标准库中的 std::fill_n 相似。

// fill_n 函数原型,三个参数分别是目标内存地址,设置的内容,长度(以数组元素个数为单位)
int fill_n(int *dst, int res, int cnt);

实际上上述初始化可以等价地转化为:

int arr[3];
fill_n(arr, 0, 3);
a[0] = 1;
a[1] = 2;

目标代码生成

数组传参相对于初始化是简单的,回想函数一节的传参方式,自行实现

需要注意的是:在C++框架中,在生成函数时,编译器会自动给函数名前面加上下划线,来防止名字冲突,所以如果出现链接错误时,请先检查生成的汇编代码的函数名称和所要调用的函数名称是否一致。

思考题

  1. 作为函数参数的数组类型第一维可以为空。事实上,在 C/C++ 中即使标明了第一维的大小,类型检查依然会当作第一维是空的情况处理。如何理解这一设计?

results matching ""

    No results matching ""

    results matching ""

      No results matching ""