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

  • Post author:
  • Post category:其他




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函数。

在这里插入图片描述

如果读者对汇编相关知识有兴趣或者有疑问可以阅读下面的这本书:


汇编语言入门教程


这里附上我在进行流程总结时的内存图。


内存图



版权声明:本文为weixin_53935876原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。