函数调用过程(转载)
2017-08-01 16:58
190 查看
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函数分配栈帧空间, 那么栈帧维护如下:
![](http://images2015.cnblogs.com/blog/1069650/201704/1069650-20170427115241365-545987088.png)
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动态分配函数在用户栈帧上分配内存。
栈帧也叫过程活动记录,是编译器用来实现函数调用过程的一种数据结构。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函数分配栈帧空间, 那么栈帧维护如下:
![](http://images2015.cnblogs.com/blog/1069650/201704/1069650-20170427115241365-545987088.png)
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动态分配函数在用户栈帧上分配内存。
相关文章推荐
- C++程序员必经之路——函数调用过程【转载】
- 函数调用具体过程-堆栈【1】
- PRO*C中调用存储过程和函数
- c函数调用过程原理及函数栈帧分析
- c语言里面的调用显示系统时间的函数及实现过程是什么
- [转载]pl/sql动态调用带参数的存储过程
- 函数调用过程(栈桢的形成与释放)
- X86架构上函数调用过程的堆栈
- 20161210计算机科学导论06_函数调用过程
- JAVA调用ORACLE的存储过程、函数的返回结果集例子
- alsa驱动的函数调用过程
- 栈帧——函数的调用过程
- 汇编代码分析----函数的调用堆栈过程(进程内核栈的切换过程)
- 分析fork函数对应的系统调用处理过程
- ibatis调用oracle的函数,存储过程的方法_IN_和OUT_游标
- 32.C#调用Oracle的存储过程和函数
- 【转】Android来电过程函数调用
- 调用oracle函数与存储过程
- MySQL笔记(一个简单select的函数调用过程)
- 函数调用过程探究