C语言下程序的堆栈调用(详细,图示)

以前接触程序时,只知道程序写的对,一般都能运行出来,但是却不知道程序是怎么一步一步将每一步编译链接起来的,今天我们用汇编来看一下程序到底是怎么在程序中运行的。

#include <stdio.h>
int Add(int x,int y)
{
	int sum = 0;
	sum = x+y;
	return sum;
}
int main()
{
	int a = 2;
	int b = 3;
	int ret = Add(a,b);
	printf("%d\n",ret);
	return 0;
}
	


我们在写程序的时候都是先写main函数,知道main函数是系统函数,直接调用就行。但是,实际上main函数是被三个其他的函数一次调用而来的,而程序在执行的时候会依次给main函数开辟一块内存,然后在根据我们写的程序依次以堆栈的形式在这块内存上存取数据。如此一来,我们队程序的调用就可以清楚明确的了解了。


                                             

在调试的过程中,我们转到反汇编写来看看。在调用main函数之后,系统先将ebp(栈底指针)和esp(栈顶指针)压入到一块堆栈中(运行时堆栈,又叫栈帧),然后是esp与4cH想减(即把esp指针向上挪4c空间大小),再将ebx,esi,edi分别压到

堆栈中,经过le啊,mov,mov三条之令之后,main函数的初始空间就被填充成了13h(二进制位19)个cc


                                                        


为了更加形象的了解堆栈的创建和销毁,我们以图示的方式来演示一下

                                                  

当吧ebx,esi,edi这三个寄存器压入堆栈之后,将13h大小的空间初始化cc后,esp指针向上移动到edi处,此时main函数调用才算结束。程序接着往下执行,这时候到了真正的主程序,执行int a=2, int b=3;看看内存中a=2和b=3是怎么存储的

                                               

     有没有注意到刚刚的ebp指针指向的位置是0018FF3C,现在02和03的位置正是在刚刚ebp指针的上面,一次我们可以想象到一条程序的存储是先从栈底开始存放,逐层向上

                           


接下来程序往下走进入函数部分


00401020   push        ebp
00401021   mov         ebp,esp
00401023   sub         esp,44h
00401026   push        ebx
00401027   push        esi
00401028   push        edi
00401029   lea         edi,[ebp-44h]
0040102C   mov         ecx,11h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]
4:        int sum = 0;
00401038   mov         dword ptr [ebp-4],0
5:        sum = x+y;
0040103F   mov         eax,dword ptr [ebp+8]
00401042   add         eax,dword ptr [ebp+0Ch]
00401045   mov         dword ptr [ebp-4],eax
6:        return sum;
00401048   mov         eax,dword ptr [ebp-4]
7:    }
0040104B   pop         edi
0040104C   pop         esi
0040104D   pop         ebx
0040104E   mov         esp,ebp
00401050   pop         ebp
00401051   ret


我们发现在调用Add函数的时候刚开始和main函数一样都是先开辟一块空间,然后就是将ebp和esp压入栈帧中,同时将11h的空间初始化为cc,这时候栈底指针和栈顶指针指向发生了变化

                                        

接下来到了好戏时间,先是执行int sum = 0;系统从ebp处往上分配给sum4个字节的内存赋值为0,接着执行int x+y,仔细看这块对应的汇编语言,它将ebp+8和ebp+0ch相加起来然后赋给了ebp-4,而我们知道,程序刚刚给sum = 0分配的内存正是ebp-4,而ebp+8h和ebp+0ch就是刚刚a和b形参的位置。这就说明,系统把形参a,b相加起来,把和放在了指向sum的内存处。


程序到这,堆栈的创建就算完成了,接下来就要到把在函数里执行的结果返回到刚刚在main函数里创建的指针处,继续看汇编程序。

0040104B   pop         edi
0040104C   pop         esi
0040104D   pop         ebx
0040104E   mov         esp,ebp
00401050   pop         ebp
00401051   ret

下来就是刚刚创建函数的堆栈的销毁了,先是pop(弹出堆栈)edi,esi,ebx这三个寄存器,然后将ebp给esp,也就是说把刚刚上面开辟的空间收回,然后经过ret跳回至刚刚在main函数处调用函数那儿(现场保护)。

回到调用处是执行add esp+8,就是说把栈顶指针向下移动8位,也就是刚刚跳过存放形参的位置


  


经过了汇编程序这么一走,我们看到了程序a+b的值在函数里运算完,传给了最初的ebp-0ch,就是刚刚实参b的上面(ret指向的内存),然后再经过printf函数打印出来,然后再执行一遍刚刚销毁Add函数的步骤,这样整个函数的堆栈创建和销毁就算执行完了。


整理一下上面的步骤就是:

1、先给main函数开辟空间

2、将寄存器和实参分别压到堆栈中

3、开辟一片形参空间,然后实参传给形参

4、给Add函数开辟一片空间,将寄存器和函数里的程序压入到这片空间中

5、把刚刚的形参进行运算,将计算结果放在函数的空间中

6、把结果传递给主函数,并且打印出来

7、Add函数的销毁

8、主函数的销毁

  • 7
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值