大家都知道在编写程序中函数是必不可少的,如果没有函数那么整个程序在编写过程中就会很不方便,复杂,而且还会出现很大的冗余。由此可见函数在我们平时编程中的重要性,我们平时学习函数只是粗浅的学习函数的参数,实参,形参,参数的设计,函数的使用等一些知识。很少有人对函数调用的过程产生疑问,或者很少有人去深入研究这些,今天这篇博客就是对函数的调用过程(栈帧)进行讲解。
首先我们先来看一段简单的代码:
#include<stdio.h>
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int ret = Add(a, b);
printf("ret = %d\n", ret);
return 0;
}
首先我们先了解一下程序的执行过程:
- 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
- 程序开始执行。接着便调用main函数。
- 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储与静态内存中的变量在程序的整个执行过程一直保留他们的值。
- 终止程序。正常终止main函数,也有可能是意外终止。
注意:建议在VC++环境下进行调试,因为VC++环境下可以清楚的一步一步看到内存中的变化。
我们由此程序探讨函数的调用过程,
#include<stdio.h>
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int ret = Add(a, b);
printf("ret = %d\n", ret);
return 0;
}
调试程序,查看【调用堆栈】,如下图:
每一次函数调用都是一个过程。
这个过程我们通常称之为:函数的调用过程
这个过程要为函数开辟栈空间,用于本次函数调用中临时变量的保存,现场保护。这块栈空我们称之为函数栈帧。
对于栈帧的维护我们必须了解ebp和esp两个寄存器。在函数调用过程中这两个寄存器存放了维护这个栈的栈底和栈顶指针。
比如:
调用main函数,我们为main函数分配栈帧空间,那么栈帧维护如下:
ebp存放了指向函数栈帧栈底的地址。
esp存放了指向函数栈帧栈底的地址。
我们要了解函数的调用过程,可以通过观察对应的汇编代码。
1.从main函数的地方开始,要展开main函数的调用就得为main函数创建栈帧,下面就是main函数栈帧的创建
2.接下来是add函数的调用,参数传递过程:
3.剩下的函数返回部分:
注栈帧这部分内容在不同的编译器上实现存在差异,但是思想是一致的。
为什么研究栈帧?可以看看
#include<stdio.h>
void fun( )
{
int tmp = 10;
int *p = (int *)(*(&tmp+1));
*(p-1) = 20;
}
int main( )
{
int a = 0;
fun ( );
printf("a = %d\n",a);
return 0;
}
运行结果:a = 20;
原因:是因为&tmp+1是指向了main-ebp,所以p指向了main-ebp,p-1指向的就是a的内存,*(p-1)=20就是直接改变a所对应的内存,所以a的值就是20.
本文详细介绍了函数调用过程中的栈帧机制,包括函数调用前后栈帧的变化、寄存器的作用以及如何通过汇编代码观察栈帧的变化。此外,还探讨了栈帧在不同编译器上的实现差异,并通过实例展示了理解栈帧的重要性。
1363

被折叠的 条评论
为什么被折叠?



