C语言函数调用过程中的内存变化解析

描述

相信很多编程新手村的同学们都会有一个疑问:C 语言如何调用函数的呢?局部变量的作用域为什么仅限于函数内?这个调用不是指C 语言上的函数调用的语法,而是在内存的视角下,函数的调用过程。本文将从C 语言调用实例,内存视角,反汇编代码来探讨C 语言函数的调用过程,也可以说是C 语言函数调用过程图解。通过这个C 语言函数调用过程图解,同学们将会知道,C 语言函数在调用时,内存空间是怎样变化的。

要想理解这一个过程还好涉及到函数栈帧的概念。函数栈帧指的是,在调用函数时,系统在栈空间中给函数所分配的一段连续空间。其中 ebp(栈帧基址指针)则是指明了当前函数的栈帧基地址,对函数的资源(局部变量、实参等)的访问,都要通过 ebp+offset(偏移量)来进行访问。而 esp 则是栈指针,指示当前栈空间栈顶的位置。

以下代码即是此次探讨 C 语言函数调用过程的实例源码:

int subFunc(int abc)
{
        int def=0x9999;
        abc=0x8888;
        return abc;
}
 
int _tmain()
{
 
        subFunc(0x2222);
       
        return 0;
}

源码很简单,在一个主函数中,调用一个带参数的子函数。源码使用 Visual Studio2010 进行调试,并同时查看内存窗口、反汇编窗口及变量窗口。

进入调试模式,并将断点定在调用子函数 subFunc()处,然后运行并观察。

通过观察窗口,可以知道,此时还是在执行 main 函数,而 ebp(栈帧基址指针)指向的是 0x0073fb64,esp=0x0073fa98。从反汇编代码可以看到,在调用函数前,需要先将参数压栈,也就是将实参存到了 0x0073fa94 处,然后再调用到子函数。

进入到子函数时,esp 已经变成了 0x0073fa8c,而 0x0073fa90 处存放的是,子函数执行完后返回到 main 函数中的地址。进入到子函数后,先将 main 函数的 ebp 压栈,然后将当前栈顶指针的值赋值给 ebp 作为当前子函数的 subFunc()的栈帧基址指针。此时 esp 和 ebp 都变成了 0x0073fa8c。

紧接着,可以看到,esp 一下子被减去了 0x0cch,也就是说栈空间一下子增长了 0x0cch,并且这段空间全部被赋值为 0xcc。再往下看,可以看到子函数中的局部变量被分配在了 0x0073fa84 处(因为变量是 32 位的,然后 CPU 却是 64 位的,所以空了 32 位不作使用),也就是说,这一段被初始化为 0xcc 的栈空间是被用来给局部变量分配空间的。

接下来再看,在 main 函数传递了一个实参 0x2222 给子函数 subFunc 中的形参 abc。在对 abc 进行读写时,其实就是在对前面实参所被存储的空间进行读写,也就是说形参在作为参数也作为局部变量的同时,它所被分配的内存空间是在函数栈帧基址 ebp 之下。

而子函数被执行完后,返回的过程则是一个与上面过程相逆的过程。将相应的数据出栈,恢复 ebp 等信息,释放子函数的栈空间,返回到主函数。所以局部变量的作用域只是在函数中,当函数被执行完返回时,函数的栈帧都被释放了,局部变量等数据也就没有了,不存在了,也就是说局部变量的生命周期是与函数的生命周期等同的。

经过以上的 C 语言函数调用过程图解,相信已经理解了 C 语言在内存中是如何调用的了。然后可以总结并得出下面的函数调用的栈帧图解。从函数调用的层面看,栈空间是被从下往上一块一块地增长的,并且是后分配的先被释放,先分配的后被释放。

编辑:hfy

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分