一次搞懂函数执行时的栈区内存分配

本文详细探讨了函数执行内存流程,包括局部变量创建、随机值原因、参数传递顺序、形参实参关系、函数调用过程及返回机制。通过C语言实例,揭示了函数调用时内存分配和变量交互的奥秘。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.函数执行时内存的整个流程

这里写一个简单的c语言程序来说明问题,代码如下;

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

main函数也是一个函数,因此它也是具有返回值和参数的,同样它也是可以被调用的。这里用vs2019来测试。
在这里插入图片描述
在代码调试中的反汇编中进入汇编代码窗口,如图main函数作为返回值返回给了__cdecl invoke_main()函数。而__cdecl invoke_main()函数还会继续作为返回值被其他函数调用,这里不再赘述,读者可以自行查看。
在main()函数被调用时栈区内存会为main()函数分配空间。
调用main函数前:
在这里插入图片描述
进入main()函数,执行汇编代码:
在这里插入图片描述
push:将edp保存的地址压入栈中,esp指向位置更新,方便以后再用。
move:将esp只想位置赋给edp;
sub:此时edp已保存esp指向的位置,esp减去一些值来为main()函数开辟空间,这里因为栈区内存优先使用高地址,所以这里是减去一些地址空间。
在这里插入图片描述
之后继续push进了三个值,那么esp指针更新,然后执行箭头所指向的4句话,执行完后,所开辟的空间被全部赋值为cccccccc;至此main()函数的预加载正式处理完毕。
在这里插入图片描述

2.局部变量是怎么创建的

main()函数与加载完成,接下来执行创建 a,b,c,等局部变量。
在这里插入图片描述
创建局部变量a:将16进制的14h放入ebp-8指向的位置。即原来的cccccccc变为14 00 00 00,即10进制的20;
在这里插入图片描述
在这里插入图片描述
同理:局部变量b也是这样创建的,只是位置变为ebp-14h;

2.为什么局部变量的值是随机值

这个问题我们通过刚才的演示,可以看到在函数预加载的时候,编译器默认将开辟的空间赋值为cccccccc,所以当我们申请局部变量但是没有赋初值的时候,局部变量的默认值为cccccccc;

3.函数是怎么传参的?传参的顺序是怎么样的

接着上面的步骤执行,main()函数该进行调用Add()函数,在进行调用Add()函数之前,会进行传参操作,参数的传递顺序是()里面的从右到左依次操作,在本例中:先进行传b的操作,在对a进行操作
在这里插入图片描述
①是对b进行操作,从上面我们可知b的地址正是ebp-14h;将==形参b的值存入eax中,再push进栈中,==同样a也是这样;内存图变为:
在这里插入图片描述

4.形参和实参是什么关系

==由3的介绍可以看出形参x,y是实参a,b的一份临时拷贝。==当执行完函数的调用时形参又会被释放。

5.函数调用是怎么做到的

紧接着,来介绍函数的调用,接下来该执行call指令,call指令是对函数的调用命令;call指令后面的参数是一个地址007713B1 ,直接跳到该地址:
在这里插入图片描述
这个地址是Add()函数的起始地址。证明要调用Add()函数。jmp是一个跳跃命令,跳转到地址为00772010的位置。而此位置正时我们定义的Add()函数的存放位置,如图:
在这里插入图片描述
push:ebp是把主函数的起始地址保存起来,等ebp,esp,维护完Add()函数后,回过头来还能找到main()函数的起始地址和终止地址;
在这里插入图片描述

接下来的操作又是函数里面的一些操作,和main()函数一样。
值得说明的是:
在这里插入图片描述
在进行加法操作时,这里是把ebp+8和ebp+ch的值都放到eax中,而ebp+8和ebp+ch正是形参x和y的值,这点是非常巧妙的。
然后,将eax的结果给ebp-8;也就是z的地址。

6.函数调用结束后是怎么返回的

在这里插入图片描述
这里就是将ebp-8的值放入寄存器eax中;此时Add()函数已经完成使命,空间可以释放了。这里就是用寄存器eax记住返回值来实现值返回,在调用Add()函数时记住返回位置来实现址返回。
在这里插入图片描述
这里令esp=ebp,栈顶栈底相同,Add()函数的空间完全释放。
当函数执行完时会存在返回值,为了在执行完Add()函数后,能够继续执行主函数的后续步骤,在执行call指令时,会把call指令的下一指令的地址压入栈内。
在这里插入图片描述
在这里插入图片描述
这时esp和ebp都返回到main()函数的栈帧里面,继续维护main()函数的操作。执行后面的printf等操作。之后的相关操作是把内存释放掉,结束main函数。
在这里插入图片描述
如果读者对汇编相关知识有兴趣或者有疑问可以阅读下面的这本书:
汇编语言入门教程
这里附上我在进行流程总结时的内存图。
内存图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值