函数调用过程栈帧变化详解

http://www.cnblogs.com/fxplove/articles/2574451.html

数调用另一个词语表示叫作 过程。一个过程调用包括将数据和控制从代码的一部分传递到另一部分。另外,它还必须在进入时为过程的局部变量分配空间,并在推出时释放这些空间。而数据传递,局部变量的分配和释放通过操纵程序栈来实现。在了解本文章之前,您需要先对程序的进程空间有所了解,即对进程如何使用内存?如果你知道这些,下面的内容将是很easy的事情了。为了您的回顾还是将简单的分布图贴出来,便于您的回顾。

我们先来了解一个概念,栈帧(stack frame),机器用栈来传递过程参数,存储返回信息,保存寄存器用于以后恢复,以及本地存储。为单个过程(函数调用)分配的那部分栈称为栈帧。栈帧其实是两个指针寄存器,寄存器%ebp为帧指针,而寄存器%esp为栈指针,当程序运行时,栈指针可以移动(大多数的信息的访问都是通过帧指针的)。总之简单一句话,栈帧的主要作用是用来控制和保存一个过程的所有信息的。栈帧结构如下所示:

  

如果你已经对这个图已经非常了解了,那么就没有必要再看下去了。因为下面的内容都是对这幅图的讲解。

  假设过程P(调用者)调用过程Q(被调用者),则Q的参数放在P的栈帧中。另外,当P调用Q时,P中的返回地址被压入栈中,形成P的栈帧的末尾(返回地址就是当程序从Q返回时应该继续执行的地方)。Q的栈帧从保存的帧指针的值开始,后面到新的栈指针之间就是该过程的部分了。

  过程实例讲解:

下面以这个程序为例进行简要说明函数调用的基本过程。

int swap_add(int* xp,int* yp) {
    int x = *xp;
    int y = *yp;
    *xp = y;
    *yp = x;
    return x+y;
}
int caller(){
    int arg1 = 534;
    int arg2 = 1057;
    int sum = swap_add(&arg1,&arg2);
    int diff = arg1 - arg2;

    return sum * diff;
}

经过汇编之后caller部分的代码如下:

caller:
    pushl %ebp   //保存%ebp
    movl %esp,%ebp    //设置新的帧指针为旧的栈指针
    subl $24,%esp  //分配24子节的栈空间
    movl $534,-4(%ebp) //设置arg1=534
    movl $1057,-8(%ebp) //设置arg2=1057
    leal -8(%ebp),%eax //计算&arg2
    movl %eax,4(%esp) //将&arg2存入栈中
    leal -4(%ebp),%eax //计算&arg1
    movl %eax,(%esp) //将&arg1存入栈中
    call swap_add //调用swap_add

这段代码先保存了%ebp的一个副本,将新的过程(该函数的ebp)的ebp设置为栈帧的开始位置。然后将栈指针减去24,从而在栈上分配了24字节的空间(你应该思考一下为什么是24字节),然后是初始化两个局部变量,计算两个局部变量的地址并存入栈中,形成了函数swap_add的参数。将这些参数存储到相对于栈指针偏移量为0和+4的地方,留待稍后的swap_add调用访问。然后调用swap_add.

接下的代码是swap_add的函数部分:

swap_add:

    pushl %ebp

    movl %esp,%ebp

    pushl %ebx

    

    movl 8(%ebp),%edx

    movl 12(%ebp),%ecx

    movl (%edx),%ebx

    movl (%ecx),%eax

    movl %eax,(%edx)

    movl %ebx,(%ecx)

    addl %ebx,%eax

    

    popl %ebx

    popl %ebp

    ret

   

代码分为3部分 建立部分:初始化栈帧;主体部分:执行过程的实体计算;结束部分:回复栈帧的状态,以及过程返回。这一部分的代码比较简单,就不在一一介绍,根据以上的3部分,划分的已经很清晰了。(说明一点程序在执行到swap_add的代码之前,也就是在执行call语句已经把返回地址压入栈中)值得注意的是最后一部分的popl %ebx   popl %ebp。它的作用是恢复了之前存储的栈帧指针的值,也就是调用程序的原始栈帧指针。从而程序就可以得到返回(有些细心的人会问那返回值咋么办?呵呵,返回值是存入了%eax中,在接下来的调用程序caller中直接访问该寄存器就可以了)。

下面就是返回之后继续执行的部分代码了:

movl -4(%ebp),%edx

subl -8(%ebp),%edx

imull %edx,%eax

leave

ret

为了计算diff,从栈中取出arg1,和arg2的值,并将寄存器%eax当做swap_add的返回值。

整个过程的栈变化如下所示:

推荐一篇对栈帧的讲解不错的文章:http://blog.csdn.net/yxysdcl/article/details/5569351

参考文献:《深入理解计算机系统》

时间: 2024-12-24 12:52:08

函数调用过程栈帧变化详解的相关文章

C语言函数调用及栈帧结构

source:http://blog.csdn.net/qq_29403077/article/details/53205010 一.地址空间与物理内存 (1)地址空间与物理内存是两个完全不同的概念,真正的代码及数据都存在物理内存中. 物理储存器是指实际存在的具体储存器芯片,CPU在操纵物理储存器的时候都把他们当做内存来对待,把他们看成由若干个储存单元组成的逻辑储存器,这个逻辑储存器就是我们所说的地址空间. 地址空间大小与逻辑储存器大小不一定相等. (2)进程的地址空间分布 进程的地址空间包括:

函数调用时的帧变化

[原文] 函数调用另一个词语表示叫作过程.一个函数调用包括将数据和控制从代码的一部分传递到另一部分.另外,它还必须在进入时为函数的局部变量分配空间,并在退出时释放这些空间.而数据传递,局部变量的分配和释放是通过操纵程序栈来实现的. 我们先来了解一个概念,栈帧.机器用栈来传递过程参数,存储返回信息等.为单个过程(函数调用)分配的那部分栈成为栈帧.栈帧其实是两个指针寄存器:寄存器%ebp为帧指针,寄存器%esp为栈指针.当程序运行时,栈指针可以移动,大多数的下线访问都是通过帧指针进行的.总之简单一句

C/C++堆、栈及静态数据区详解

转自:https://www.cnblogs.com/hanyonglu/archive/2011/04/12/2014212.html  做略微修改 本文介绍C/C++中堆,栈及静态数据区. 五大内存分区 在C++中,内存分成5个区,他们分别是堆.栈.自由存储区.全局/静态存储区和常量存储区.下面分别来介绍: 栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区.里面的变量通常是局部变量.函数参数等. 堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用

ELK技术栈之-Logstash详解

ELK技术栈之-Logstash详解 前言 在第九章节中,我们已经安装好Logstash组件了,并且启动实例测试它的数据输入和输出,但是用的是最简单的控制台标准输入和标准输出,那这节我们就来深入的学习Logstash的详细使用. 常用启动参数 我们在上一节中演示了启动Logstash的实例,其中我们启动的时候给Logstash脚本传入了-e的参数,但实际上,Logstash的启动参数有很多,我们来看一下各个启动参数的作用: -e #立即启动实例,例如:./logstash -e "input {

IOS应用的执行过程以及生命周期详解

iOS的应用程序的生命周期,还有程序是运行在前台还是后台,应用程序各个状态的变换,这些对于开发者来说都是很重要的. iOS系统的资源是有限的,应用程序在前台和在后台的状态是不一样的.在后台时,程序会受到系统的很多限制,这样可以提高电池的使用和用户体验. //开发app,我们要遵循apple公司的一些指导原则,原则如下: 1.应用程序的状态 状态如下: Not running  未运行  程序没启动 Inactive          未激活        程序在前台运行,不过没有接收到事件.在没

New UI-布局之FrameLayout(帧布局)详解

New UI-布局之FrameLayout(帧布局)详解  --转载请注明出处:coder-pig,欢迎转载,请勿用于商业用途! 小猪Android开发交流群已建立,欢迎大家加入,无论是新手,菜鸟,大神都可以,小猪一个人的 力量毕竟是有限的,写出来的东西肯定会有很多纰漏不足,欢迎大家指出,集思广益,让小猪的博文 更加的详尽,帮到更多的人,O(∩_∩)O谢谢! 小猪Android开发交流群:小猪Android开发交流群群号:421858269 新Android UI实例大全目录:http://bl

S7-300过程映像区详解

? 一.概念? W过程镜像区输入字 PIW立即输入区字? PIW不用等系统刷新,立即读入 IW等待系统刷新后读入 ? 二.PIW/IW,PQW/QW? 引用西门子论坛一位大侠的比方加深理解:? ???"打个比如,你吃饭的时候要吃菜,?你一般都是将菜从锅里乘到碗里,?再从碗里夹着吃,?可是有时候饿得太急了,你顾不了那么多,直接就从锅里夹菜吃了. 这个PIW和IW其实最终装的都是一个东西,相当于这个"菜",?只不过PIW是锅里的菜,?而IW是碗里的菜!?? 要注意了,上面的动作已

C函数的调用过程   栈帧

C语言中,每个栈帧对应着一个未运行完的函数.栈帧中保存了该函数的返回地址和局部变量. 首先,栈是从高地址向低地址延伸的.寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址). 先来看一个代码 #include <stdio.h> void fun() {     int tmp = 10;     int*p = (int*)(*(&tmp + 1));     *(p - 1) = 20; } int main() {     int a = 0;

[Android Pro] 深入理解函数的调用过程——栈帧

cp :http://blog.csdn.net/x_perseverance/article/details/78897637 每一个函数被调用时,都会为函数开辟一块空间,这块空间就称为栈帧. 首先,我们了解一下不同种类的寄存器: (1)eax,ebx,ecx,edx :通用寄存器 (2)ebp:存放指向函数栈帧栈底的地址 (3)esp:存放指向函数栈帧栈顶的地址 (4)eip:程序计数器——保存程序当前正在执行指令的下一个指令的地址 接着我们以下面这段代码为例,深入到函数的调用过程中去: #