读书笔记_局部变量和栈帧.

局部变量(Local Variable)是指作用域和生命周期都局限在所在函数或过程范围内的变量,它是相对于全局变量(Global variable)而言的。编译器在为局部变量分配空间时通常有两种做法:使用寄存器和使用栈。

寄存器的访问速度快,但数量和空间有限,所以像字符串或数组不适合分配在寄存器中。编译器通常只会把频繁使用的临时变量分配在寄存器中,比如for循环中的循环变量。当编译器的优化选项打开时,编译器会充分利用可用的寄存器来给临时变量使用,以提高程序的性能。对于调试版本,优化选项默认是关闭的,编译器会在栈上分配所有的变量。在C/C++程序中,可以在声明变量时加上register关键字,请求编译器在可能的情况下将该变量分配在寄存器中,但不能保证所描述的变量一定被分配在就存寄存器中。大多数时候,编译器还是根据全局设置和编译器自身的逻辑来决定是否把一个变量分配在寄存器中。

编译器会在编译阶段根据变量的特征和优化选项为每个局部变量选择以上的两种分配方法之一。大多数的局部变量都是分配在栈上的。栈上的变量会随着函数的调用和返回而自动分配和释放,所以栈有时也称为自动内存。

局部变量的分配和释放是由编译器插入的代码通过调整栈指针(Stack Pointer)的位置来完成。编译器在编译时,会计算当前的代码块中所声明的所有局部变量所需要的空间,并将其按照内存对齐要求的最接近整数值。在32位系统中,内存分配时按4字节对齐的,这意味着不满4字节的空间会按4字节来分配。

计算好所需的空间后,编译器会插入适当的指令来调整栈指针ESP,为局部变量分配空间,有两种方式调整ESP的值,一种是直接进行加减运算,另一种是PUSH和POP指令。

当我们看到反汇编代码时,常见到的指令是esp + n这种相对的地址,用esp进行标注的缺点是ESP的值是不稳定的,当ESP的值变化了,引用变量的偏移值也要变化。为了解决以上问题,x86 CPU设计了另一个寄存器,这就是EBP寄存器。EBP的全称是Extended Base Pointer,即扩展的基地址指针。使用EBP寄存器,函数可以把自己将要使用的栈空间的基准地址记录下来,然后使用这个基地址来引用局部变量和参数。在同一函数内,EBP寄存器的值是保持不变的,这样函数的局部变量就有了一个固定的参照物。

通常,一个函数在入口处将当时的EBP值压入堆栈,然后把ESP值(栈顶)赋值给EBP,这样EBP中的地址就是进入本函数时的栈顶地址。因此在EBP地址的上面便是这个函数使用的栈空间,它下面(地址值递增方向)是父函数使用的空间例如EBP+4 指向的是CALL指令压入函数的返回地址。EBP+8是父函数压在栈上的第一个参数,EBP+0xC是第二个参数。依次类推,EBP-n 是第一个局部变量的起始地址(n为变量的长度)。

因为在将栈顶地址(ESP)赋给EBP寄存器之前先把旧的EBP值保存在栈中,所以EBP寄存器所指向的栈单元中保存的是前一个EBP寄存器的值,这通常也就是父函数的EBP值。类似的父函数的EBP所指向的栈单元中保存的是更上一层函数的EBP值,依此类推,直到当前线程的最顶层函数。这也正是栈回溯的基本原理。

栈中的数据是连续存储的,好像所有的数据都混作一团,但事实上,它们是按照函数调用关系依此存放的,而且这种顺序关系非常严格,为了更好地描述和指代栈中的数据,我们把每个函数在栈中所使用的区域称为一个栈帧(stack frame)。

关于栈帧还有以下几点:

1. 在一个栈中,依据函数调用关系,发起调用的函数(caller)的栈帧在下面(高地址方向),被调用的函数的栈帧在上面。

2. 每发生一次函数调用,便产生一个新的栈帧,当一个函数返回时,这个函数所对应的栈帧被清除(eliminated)

3. 线程正在执行的那个函数所对应的栈帧被位于栈的最顶部,它也是栈内仍然有效的最年轻的栈帧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值