# 函数的活动记录

#### 例：简单函数调用过程中，活动记录的产生和回收 <a href="#li-jian-dan-han-shu-tiao-yong-guo-cheng-zhong-huo-dong-ji-lu-de-chan-sheng-he-hui-shou" id="li-jian-dan-han-shu-tiao-yong-guo-cheng-zhong-huo-dong-ji-lu-de-chan-sheng-he-hui-shou"></a>

```c
void foo(int bar, int *baz) {
    char snink[4];
    short *why;

    why = (short *) (snink + 2);
    *why = 50;
}
```

当函数 foo 在内存中运行时，我们可以大胆猜测 **bar**、**baz、snink、why** 应该会被存储在一起，实际上也确实如此：这就是 foo 函数的活动记录，它有以下几个特点：

![](https://zhenghe-md.gitbooks.io/zhenghe-lecture-notes-digest/content/assets/Screen%20Shot%202018-02-01%20at%209.09.24%20PM.jpg)

1. 函数的参数从右往左，依次从高内存地址往低内存地址方向摞起来 --- 第一个参数在最低地址处
2. 函数的局部变量从上往下，依次从高内存地址往低内存地址方向摞起来 --- 第一个参数在最高地址处
3. 在函数的参数和局部变量之间留有一段空间 (4 字节或 8 字节)，用来存储 foo 函数执行完的返回地址，也称为 saved pc.

接下来再看看 foo 函数被其它函数调用时，函数的活动记录是如何变化的：

```c
int main(int argc, char **argv) {
    int i = 4;
    foo(i, &i);
    return 0;
}
```

main 函数执行过程中，函数栈里 main 和 foo 的活动记录如下图所示：**①：** main 函数执行时，有两个参数 argc, argv 被放在了 main 的活动记录中，并在 saved pc 中保留了 main 函数执行结束后 pc 的位置

![](https://zhenghe-md.gitbooks.io/zhenghe-lecture-notes-digest/content/assets/Screen%20Shot%202018-02-01%20at%209.49.28%20PM.jpg)

②：执行到 int i = 4; 时，main 函数需要为局部变量 i 分配空间，即 SP = SP - 4，并将 i 的值初始化，即 M\[SP] = 4，

③：这时候 main 函数遇到了调用 foo 函数的指令，但在此之前，需要为 foo 函数生成一半活动记录，上面记录有 foo 函数的两个参数 bar 和 baz。在执行完 foo 之后，通过 SP = SP + 8 来回收分配给这两个参数的空间。因此，foo 函数的 saved pc 里面存的就是指令 SP = SP + 8；的地址。初始化了 foo 函数的 saved pc 后，就可以正式把控制权转交给 foo 函数

④：foo 函数先为它的两个局部变量分配空间，即 SP = SP - 8，然后再为两个局部变量 bar 和 baz 赋值。这里由于受到指令集及硬件的限制，无法实现 M\[SP] = M\[SP + 8] 以及 M\[SP + 4] = SP + 8 这种指令，因此需要先将变量的值放入通用寄存器 R1, R2 中，再将值存入内存中，接着执行后两条语句，这里略过。

⑤：foo 函数体执行完毕后，执行 SP = SP + 8 回收局部变量空间，这时栈指针 SP 指向 foo 函数的 saved pc

⑥：执行 RET 会将 saved pc 的值存到 PC 中，foo 函数就将控制权转交回给了 main 函数

⑦：main 函数执行 SP = SP + 8，回收为 foo 函数参数分配的空间，然后将返回值 0 存入专用寄存器 RV 中，等待 main 函数返回

#### 活动记录的一般模式 <a href="#huo-dong-ji-lu-de-yi-ban-mo-shi" id="huo-dong-ji-lu-de-yi-ban-mo-shi"></a>

从上面的例子中可以抽象出活动记录的一般模式：每个活动记录包含 **参数**、**返回地址** 和 **局部变量**，其中 **参数** 由函数调用方负责分配空间以及初始化；**局部变量** 则由函数执行方负责分配空间及初始化。原因如下：

![](https://zhenghe-md.gitbooks.io/zhenghe-lecture-notes-digest/content/assets/Screen%20Shot%202018-02-01%20at%2010.29.33%20PM.jpg)

1. 只有函数调用方知道参数的个数及取值
2. 只有函数的执行方知道局部变量的个数及取值

综上所述，活动记录由函数的调用方和执行方共同生成，当执行方完成工作后，会回收自己负责分配和初始化的局部变量，然后将控制权转交回给函数的调用方，由函数的调用方负责回收函数的参数，并最终完成整个活动记录的回收。

#### 例：递归函数的执行过程中，活动记录的产生与回收 <a href="#li-di-gui-han-shu-de-zhi-hang-guo-cheng-zhong-huo-dong-ji-lu-de-chan-sheng-yu-hui-shou" id="li-di-gui-han-shu-de-zhi-hang-guo-cheng-zhong-huo-dong-ji-lu-de-chan-sheng-yu-hui-shou"></a>

```
int fact(int n) {
    if (n == 0) return 1;
    return n * fact(n - 1);
}
```

假设某函数在某处调用了 fact(3)，则从调用开始之后的活动记录变化过程如下图所示

![](https://zhenghe-md.gitbooks.io/zhenghe-lecture-notes-digest/content/assets/Screen%20Shot%202018-02-01%20at%2011.00.35%20PM.jpg)

①：某函数在某处调用 fact，并将参数 n 的空间分配好且初始化为 3，将返回地址放入 saved pc 中后控制权转交 fact 函数

②③：fact 函数执行，读入参数 n 并利用 BNE 指令来判断是返回 1 还是继续递归执行 n \* fact(n-1)，值得注意的是，当递归的 fact 函数执行结束时，外层 fact 函数相信里层 fact 函数已经把它的返回值放入特殊寄存器 RV 中，因此它直接将参数 n 与 RV 相乘的值存入 RV 中，执行 RET 语句将控制权交给外层调用者。

④：fact 函数执行，读入参数 n，值为 0，因此 BNE 指令的跳转判断为否，继续执行之后的指令，把 1 放入 RV 中并将控制权转交外层 fact 函数

⑤⑥⑦：fact 函数继续执行 CALL\<fact> 之后的指令，将参数 n 与 RV 中的返回结果相乘，并存回 RV 中，继续将控制权转交给外层函数。

#### 参考资料 <a href="#can-kao-zi-liao" id="can-kao-zi-liao"></a>

* [Stanford CS107: lecture 10](https://www.youtube.com/watch?v=FvpxXmEG1F8\&index=10\&t=3s\&list=PL08D9FA018A965057)
* [Github: ZhengHe-MD - lecture codes](https://github.com/ZhengHe-MD/cs107-lecture-codes)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://zhenghe.gitbook.io/open-courses/stanford-cs107/di-shi-ke-han-shu-de-ji-lu-huo-dong.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
