不负韶华

来源:互联网 发布:python 图像分割提取 编辑:程序博客网 时间:2024/04/27 16:39
        每 一次函数调用都是一个过程,该过程称为函数的该过程称为函数的调用过程。该过程需要为函数开辟空间,用于本次函数的调用中临时变量的保存、现场保护,开辟的空间就称为函数栈帧。接下来我就简要介绍一下函数调用过程中,栈帧的创建与销毁。栈帧的创建需要用到两个寄存器:ebp和esp,在函数调用过程中这两个寄存器存放了维护这个栈的栈底和栈顶指针。ebp存放了指向函数栈帧栈底的地址,称为栈底寄存器或者基址寄存器;esp存放了指向函数栈帧栈顶的指针,称为栈顶寄存器。
        当我们要详细研究函数调用过程,必须得对应汇编代码。以下面这个程序为例,简要介绍一下函数调用过程栈帧的创建与销毁。
#include <stdio.h>int Add(int x,int y){int z=x+y; return z;}int main(){int a=0xAAAAAAAA;int b=0xBBBBBBBB;int ret=Add(a,b);printf("you should run here!\n");printf("result:%d\n",ret);return 0;}
1、从main函数开始,要展开main 函数的调用就得为main函数创建栈帧,首先我们来看main函数的栈结构。在查看中打开调试窗口的汇编,查看对应的汇编代码。

图1
地址空间主要由code、init、uninit、heap及stack构成,heap和stack相对而生。如图1所示,首先为main函数开辟栈空间,ebp 指向栈底,esp指向栈顶;将a和b依次放入栈中a先入栈(ebp-4),b后入栈(ebp-8)。eip为程序计数器,用于存放CPU当前正在执行指令的下一条指令。





图2
将b放入eax寄存器中,将a放入ecx寄存器,b先出栈,a后出栈。执行call命令,将eip写为00401093,也就是main函数中call指令的下一条指令,并将其压入栈中(main ret)。执行jmp指令,将eip写为00401020,进入Add函数。将ebp压入栈中,将esp赋给ebp,如图2所示。(call指令的功能:将当前正在执行指令的下一条指令的地址压入栈中,随即就会跳转至指定函数)



图3
创建Add函数的栈帧,先将a放入eax寄存器,再将a+b放入eax寄存器,随后将eax放入Add的栈中,最后再将栈底的值z放入eax寄存器中,如图3所示。




图4
执行mov指令,Add函数的栈帧被销毁,接着执行pop指令,出栈,将栈的内容保存到ebp,回到main函数的栈帧。ret指令,弹出栈顶,将栈顶的值放入eip。回到main函数,将返回值以eax寄存器的形式返回,至此Add函数调用完成。
总结:
           1:形参实例化,存放于调用函数与被调函数的栈帧之间;
           2:形参实例化的顺序为从右向左;
           3:临时变量在自己的栈帧中,随栈帧存在,所以具有临时性;
           4:函数通过操控esp和ebp形成自己的栈帧,且需要自己释放;
           5:常规情况下,函数返回值会以寄存器的形式返回。