当前位置:嗨网首页>书籍在线阅读

06-堆栈

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

5.4.1 堆栈

无论何时调用函数或方法,堆栈都用于为函数内部创建的值分配空间。函数中所有let绑定都存储在堆栈中,它既可以是值本身,也可以是指向堆上内存地址的指针。这些值构成了活动函数的堆栈帧。堆栈帧是堆栈中存储器的逻辑块,用于存储函数调用的上下文。此上下文可能包括函数参数、局部变量、返回地址,以及从函数返回后需要恢复的任何已保存的寄存器值。随着越来越多的函数被调用,它们对应的堆栈帧会被压入堆栈。一旦函数返回,与之相关的堆栈帧,以及其中声明的所有值都会一起被清理释放。

这些值会根据它们声明的相反顺序删除,并且遵循后进先出(Last In First Out,LIFO)规则。

堆栈上内存分配速度很快,因为分配和释放内存只需一条CPU指令:递增/递减堆栈帧指针。堆栈帧指针(esp)是一个CPU寄存器,它始终指向堆栈的最顶部。堆栈帧指针在函数被调用或返回时实时更新。当函数返回时,通过将堆栈帧指针恢复到进入函数之前的位置来丢弃该堆栈帧。使用堆栈是一种临时性内存分配策略,但由于其简单性,它在释放已使用内存方面是可靠的。但是,堆栈的相同属性不适用于需要超出当前堆栈帧生命周期的情况。

以下代码用于简要说明在函数调用期间,如何在程序中更新堆栈:

// stack_basics.rs
fn double_of(b: i32) -> i32 {
    let x = 2 * b;
    x
}
fn main() {
    let a = 12;
    let result = double_of(a);
}

我们将用空数组[]表示该程序的堆栈状态。让我们无负载执行一次该程序来查看堆栈的内容。我们将使用[]表示父堆栈中的堆栈帧。运行此程序时,将产生以下这一系列操作。

1.当调用main函数时,它会创建堆栈帧,其中会保存a和result(会被初始化为零)变量。此时的堆栈是[[a=12, result=0]]。

2.接下来,double_of函数会被调用,并将新的堆栈帧推入堆栈存放其本地数据。堆栈的内容现在是[[a=12, result=0], [b=12, temp_double=2x, x=0]]。temp_double是一个由编译器创建的临时变量,用于存储2x的结果,然后将其分配给在double_of函数中声明的变量x,再将此x返回给调用者,即main函数。

3.double_of函数返回后,它的堆栈帧将从堆栈中弹出,堆栈中的内容现在是[[a=12, result=24]]。

4.随后,main函数执行完毕,其堆栈帧也会被弹出,堆栈中是空的:[]。

与之有关的细节还有很多,我们只是对函数调用及其与堆栈内存的交互进行了高度概括的描述。现在,如果我们只有在函数调用生命周期内有效的本地值,那么它们的作用将是非常有限的。虽然堆栈简洁、高效,但实际上程序还需要生命周期更长的变量,因此我们需要使用堆。