代码在内存中的分布。函数在栈中的调用过程,即函数的栈帧。

本文探讨了代码在内存中的分配,强调了代码区、静态区、栈和堆的区别。重点在于函数的栈帧,介绍了EBP、ESP和EIP等寄存器在函数调用过程中的作用,并通过示例详细阐述了函数如何在栈中生成调用,包括参数实例化和返回过程,展示了函数调用的完整栈帧结构。

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

首先来了解一下,代码在内存中的分配,咱们先用图来看一下:


地址分配图很清楚的可以看出,代码在内从中分布。

其中要注意几点:

代码区具有只读性,文字常量也只具有只读性。

静态区又叫全局变量区,生命周期是文件的生命周期。

最主要的是栈和堆,栈的空间是系统管理,用时自动申请,不用时自动销毁。而堆是由程序员自己管理,自己申请自己销毁,如果不销毁就会产生内存泄漏。



接下来主要分析函数在栈中的调用,即函数的栈帧:

在了解函数栈帧之前,我们先得了解计算cpu中的几个寄存器。

1.EBP叫基址指针寄存器,用于指向栈底。

2.ESP叫指针寄存器也是EBP的一种,用于指向栈顶。

3.EIP/IP/PC叫指令寄存器,用来保存当前指令的下一条指令的地址。

还有很多寄存器,像EAX,EBX,ECX,EDX,DI 等,现在只需要知道这几个寄存器

我们还得知道:

代码的执行过程,在我们看来代码的入口就是main()函数,但是main()函数也是被其它函数调用,可以在VS2008中打开栈调用过程,可以看见调用main()函数的是mainCRTStarup()函数。

这里咱们就自己的逻辑入口main()函数起,来咱们用图来解释一下函数在栈中的生成调用。


上面是函数的栈帧结构从main()函数的开始。

要研究函数栈帧,咱们先用一段代码来演示:

#include<stdio.h>
int function(int x,int y)
{
	return x+y;
}
int main()
{
	int a = 0x66666666;
	int b = 0x99999999;
	int c = function(a,b);
	printf("finish off\n");
	return 0;
}


首先函数在main()中定义了两个变量,所以在栈中就有

在栈中先定义的地址高,因为栈是向下生长的,所以如上图。

当执行到

int c = function(a,b);

这句代码,它先跳转到function()这个函数,这时就用到了在汇编中的一个助记符call。
这里还需要了解到,函数对应的汇编过程:

int main()
{
010213F0  push        ebp  
010213F1  mov         ebp,esp 
010213F3  sub         esp,0E4h 
010213F9  push        ebx  
010213FA  push        esi  
010213FB  push        edi  
010213FC  lea         edi,[ebp-0E4h] 
01021402  mov         ecx,39h 
01021407  mov         eax,0CCCCCCCCh 
0102140C  rep stos    dword ptr es:[edi] 
	int a = 0x66666666;
0102140E  mov         dword ptr [a],66666666h 
	int b = 0x99999999;
01021415  mov         dword ptr [b],99999999h 
	int c = function(a,b);
0102141C  mov         eax,dword ptr [b] 
0102141F  push        eax  
01021420  mov         ecx,dword ptr [a] 
01021423  push        ecx  
01021424  call        @ILT+165(_function) (10210AAh) 
01021429  add         esp,8 
0102142C  mov         dword ptr [c],eax 
	printf("finish off\n");
0102142F  mov         esi,esp 
01021431  push        offset string "finish off\n" (102573Ch) 
01021436  call        dword ptr [__imp__printf (10282BCh)] 
0102143C  add         esp,4 
0102143F  cmp         esi,esp 
01021441  call        @ILT+315(__RTC_CheckEsp) (1021140h) 
	return 0;
当用到CALL后IP寄存器会将function()函数地址保存起来,然后CALL跳转到function()函数中。

当函数执行完了,想要返回main()函数中,所以在CALL时候会把下一条指令的地址压栈。

然后在在栈中开辟新空间,并把对应的形参实例化,而在实例化过程中是从右向左实例化。正如汇编中的执行语句。

如图:


上面所说的,

当函数执行完了,想要返回main()函数中,所以在CALL时候会把下一条指令的地址压栈。

所以,有


最后将EBP中地址地址压入栈中,然后再进行EBP,ESP指针位置变换,开辟空间,并计算表达式如图:




这样后就会执行完成,然后做得是,函数返回,把计算结果放到eax寄存器后,又出栈,因为栈是先进后出,所以已原来的方法回退回去。

变成了:


所以就完成了函数调用,这就是函数的栈帧。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值