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

来源:互联网 发布:计算机网络就业 知乎 编辑:程序博客网 时间:2024/05/19 11:44

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

#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        ebp00401021   mov         ebp,esp00401023   sub         esp,44h00401026   push        ebx00401027   push        esi00401028   push        edi00401029   lea         edi,[ebp-44h]0040102C   mov         ecx,11h00401031   mov         eax,0CCCCCCCCh00401036   rep stos    dword ptr [edi]4:        int sum = 0;00401038   mov         dword ptr [ebp-4],05:        sum = x+y;0040103F   mov         eax,dword ptr [ebp+8]00401042   add         eax,dword ptr [ebp+0Ch]00401045   mov         dword ptr [ebp-4],eax6:        return sum;00401048   mov         eax,dword ptr [ebp-4]7:    }0040104B   pop         edi0040104C   pop         esi0040104D   pop         ebx0040104E   mov         esp,ebp00401050   pop         ebp00401051   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         edi0040104C   pop         esi0040104D   pop         ebx0040104E   mov         esp,ebp00401050   pop         ebp00401051   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、主函数的销毁

2 0
原创粉丝点击