函数的调用过程(栈帧)

来源:互联网 发布:搜不到网络打印机 编辑:程序博客网 时间:2024/05/18 00:06

1、什么是栈帧?

栈帧也叫过程活动记录,是编译器用来实现函数调用过程的一种数据结构。C语言中,每个栈帧对应着一个未运行完的函数。从逻辑上讲,栈帧就是一个函数执行的环境:函数调用框架、函数参数、函数的局部变量、函数执行完后返回到哪里等等。栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)。

2.

#include<stdio.h>
int Add(int x,int y)
{
int z = 0;
z = x + y;
return 0;
}
int main()
{
int a = 10;
int b = -10;
int ret = Add(a,b);
printf("ret = %d\n", ret);
return 0;
}

我们发现其实main函数在 __tmainCRTStartup 函数中调用的,而 __tmainCRTStartup 函数是在 mainCRTStartup 被调用的。我们知道每一次函数调用都是一个过程。这个过程我们通常称之为: 函数的调用过程。这个过程要为函数开辟栈空间, 用于本次函数的调用中临时变量的保存、 现场保护。 这块栈空间我们称之为函数栈帧。
而栈帧的维护我们必须了解ebp和esp两个寄存器。 在函数调用的过程中这两个寄存器存放了维护这个栈的栈底和栈顶指针。比如:调用main函数, 我们为main函数分配栈帧空间, 那么栈帧维护如下:

ebp存放了指向函数栈帧栈底的地址。esp存放了指向函数栈帧栈顶的地址。
注意:ebp指向当前位于系统栈最上边一个栈帧的底部,而不是系统栈的底部。严格说来,“栈帧底部”和“栈底”是不同的概念;ESP所指的栈帧顶部和系统栈的顶部是同一个位置。

1 . 从main函数的地方开始, 要展开main函数的调用就得为main函数创建栈帧, 那我们先来看main函数栈帧的创建。


2.Add函数的调用,参数的传递



按F11进入Add函数代码执行处:



函数返回:


       由此可见,通过ebp,能够很容易定位到上面的参数。当从func函数返回时,首先esp移动到栈帧底部(即释放自动变量),然后把上一个函数的栈帧底部指针弹出到ebp,再弹出返回地址到cs:ip上,esp继续移动划过参数,这样,ebp,esp就回到了调用函数前的状态,即现在恢复了原来的main的栈帧。

    OK,到这里应该说明白了栈帧在栈帧的分布和形成过程,那么栈帧在我们编程过程中给我们什么启示呢?

    栈帧上的动态内存分配

    前面已经说明过一点:在大部分系统中,栈帧上可以进行动态内存的分配。malloc、calloc和realloc函数都是在堆上动态分配一块内存,在使用过后一定要记得释放动态分配的内存,否则就会产生内存泄露,最终降低系统的性能。

    但是如果要在栈帧上动态分配内存的话,那么在函数返回时会自动释放这些内存,而不必担心忘记释放动态分配的内存。我们知道在linux内核中,每个进程的栈只有1-2个页的大小,即4K-8K大小,需要很珍惜的使用这部分空间;不过实用户栈的空间很大,可以随着需要动态的扩充,而不必担心栈不够用,因此我们还是可以放心的使用alloca动态分配函数在用户栈帧上分配内存。








原创粉丝点击