您的位置:首页 > 编程语言 > C语言/C++

从汇编语言角度理解C语言栈帧

2016-01-08 13:46 489 查看
在C语言的调用约定中,栈是现实函数的局部变量、参数和返回值地址的关键因素。

函数执行前、执行过程以及执行后程序分别干了什么事情?

在整个过程中唯一不变的就是基址指针寄存器,位于返回地址和函数变量之间,通过+N*4(%ebp)可以访问函数参数和返回地址,通过-N*4(%ebp)可以访问局部变量。

函数执行前:
程序将函数的参数逆序压栈,接着发出一条Call指令,表明程序希望开始执行哪一个函数。Call指令完成两件事情:首先,将下一条指令的地址即返回地址压入栈中;然后,修改指令指针(%eip)以指向函数起始处。
此时的栈:
参数N
...
参数2
参数1
返回地址<--(%esp)
函数开始时:
首先,通过pushl %ebp指令保存当前的基址指针寄存器%ebp,基址指针寄存器,是一个特殊的寄存器,用来访问函数的参数的局部变量。其次,他会用movl %esp, %ebp 将基址指针设为栈指针。你会发现(%ebp)含有旧的%ebp,4(%ebp)含有返回地址,8(%ebp)是函数的第一个参数的位置。
此时的栈:
参数N
...
参数2
参数1 <--8(%ebp)
返回地址 <--4(%ebp)
旧%ebp <--(%ebp)和(%esp)
接下来,函数为其所需的局部变量保存栈空间,只需将栈指针向下移动即可,例如,sub $8, %esp
这样就把变量放在了栈帧上,但函数返回时,栈帧不复存在。这就是局部变变量的原因。

此时的栈:
参数N
...
参数2
参数1 <--8(%ebp)
返回地址 <--4(%ebp)
旧%ebp <--(%ebp)和(%esp)
局部变量1 <-- -4(%ebp)
局部变量2 <-- -8(%ebp)
所以我们通过使用(%ebp)的不同偏移量,通过基址寻址访问这个函数所需的所有数据。
%ebp正是专门为了这一目的设计的,这是它被称为基址指针的原因。

全局变量和静态变量的唯一区别就是静态变量只用于一个函数,而全局变量可有许多函数共同使用。

函数执行完毕后,函数做三件事情:
(1)将返回值-4(%ebp)存入%eax(返回值始终存储在%eax中),linux要求退出码保存在%ebx中,所以我们可以在主函数中movl %eax, %ebx,
(2)将栈恢复到调用函数时的状态 movl %ebp, %esp pop1 %ebp ret
(3)将控制权返回给调用它的程序。通过ret指令将栈顶的值弹出,并将指令指针寄存器%eip设置为该弹出器。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: