函数栈帧的创建和销毁(vs2013)

1.寄存器

eax、ebx、ecx、edx    { esp、ebp }(这两个重点,存放的是地址,这两个地址是用来维护函数栈帧的。)

2.每一个函数调用,都要(在栈区上)创建一个空间

接下来,用下面简单程序来向大家解释函数的栈帧的创建与销毁

函数栈帧的创建和销毁(vs2013)_第1张图片

现在mian函数被调用,所以要在栈区创建一个空间

 函数栈帧的创建和销毁(vs2013)_第2张图片

 这时我们按下F10 进入调试,点击上方 调试 空能选择 窗口 - 调用堆栈 函数栈帧的创建和销毁(vs2013)_第3张图片

 一直按F10 知道出现以下界面

函数栈帧的创建和销毁(vs2013)_第4张图片

 这里表示main函数在vs2013中是被调用的

 那么是谁调用了main函数呢,往上翻,你会发现

 

 所以main函数是在__tmainCRTstartup函数中被调用,而__tmainCRTstartup函数是被mainCRTstartup调用的。

即 main --------> __tmainCRTstartup  -------> mainCRTstartup

所以,有这函数栈帧的创建和销毁(vs2013)_第5张图片么个东西的,在调用main函数之前,也要为它们准备空间

以上就是大家讲一下,栈空间存储模式。

接下来我们开始详细解释 函数的创建与销毁。

首先我们回到主界面,按下 F10 进入调试,直接反键,选择转到反汇编

函数栈帧的创建和销毁(vs2013)_第6张图片

 这样我们就进入了C语言的汇编代码了

函数栈帧的创建和销毁(vs2013)_第7张图片

函数栈帧的创建和销毁(vs2013)_第8张图片

 接下来 我们来理解这些代码的意思

首先设置成如下情况

函数栈帧的创建和销毁(vs2013)_第9张图片

 方便我们一会通过地址来解释。

现在大家注意,我们马上要调用mian函数了

函数栈帧的创建和销毁(vs2013)_第10张图片

然后我进入 main函数 

函数栈帧的创建和销毁(vs2013)_第11张图片

 它的第一步 是push sbp     /    ebp(压栈操作),pop(出栈操作)

// 压栈:给栈顶放一个元素, 出栈从栈顶删除一个元素

意思就是说想栈压了一个数据

函数栈帧的创建和销毁(vs2013)_第12张图片

这是没有进行压栈之前

  当压了一个数据ebp之后,它成了栈顶,所以esp栈顶指针,要指向它了

函数栈帧的创建和销毁(vs2013)_第13张图片

 你会发现xsp的值变量,是因为压栈使用习惯是先高后低,也就意味地址减去4个字节。

这时我们通过调式 - 内存会发现,ebp的值确实压进了esp

函数栈帧的创建和销毁(vs2013)_第14张图片

 接下来第二条语句 mov,mov在这里的意思就是把esp的值赋给ebp

即ebp不再指向下面位置,而是跟esp指向同一个位置

 函数栈帧的创建和销毁(vs2013)_第15张图片

我再通过监视发现,刚才我想法是对的。

 函数栈帧的创建和销毁(vs2013)_第16张图片

 下面就是第三条语句

 

 意思为esp减去一个十六进制数 0E4h

即减去 288

esp值就变成了

 意味着esp的地址变小了,esp不再指向原来的位置,如下图:

函数栈帧的创建和销毁(vs2013)_第17张图片

 接下来 就三条push语句,意思就是给顶上压进三个元素

 

同时 esp要指向栈顶edi

函数栈帧的创建和销毁(vs2013)_第18张图片

 跟监视和内存也证明了这一观点,esp确实把这三个数压进了。

如图:函数栈帧的创建和销毁(vs2013)_第19张图片

 函数栈帧的创建和销毁(vs2013)_第20张图片

 函数栈帧的创建和销毁(vs2013)_第21张图片

 然后就是下一条语句

 lea  等价于  load effective address(加载有效地址)

即 把 有效地址  [ebp+FFFFFF1Ch]  加载到 edi里面去。

我把显示符号位勾选上,这样方便你们理解

函数栈帧的创建和销毁(vs2013)_第22张图片

edi减去 0E4h  再根据上文

函数栈帧的创建和销毁(vs2013)_第23张图片

 等于就是说

 让ebp指向了箭头位置函数栈帧的创建和销毁(vs2013)_第24张图片

 然后接下来三条语句

函数栈帧的创建和销毁(vs2013)_第25张图片

ecx。39h 意思就是:把 39h 放ecx里面去

eax.0CCCCCCCCh        /               eax = 0CCCCCCCCh

 最后一句的意思是,把从edi这个位置开始,向下的 39h次的数据,全部都改成0CCCCCCCCh

(也就是吧main函数初始化)

dword 的意思double word ,4个字节,也就是说每次操作4个字节

函数栈帧的创建和销毁(vs2013)_第26张图片

 函数栈帧的创建和销毁(vs2013)_第27张图片

 函数栈帧的创建和销毁(vs2013)_第28张图片

 到目前为止,我们的main函数栈空间就开辟好了。

接下来就轮到我们 a 和 b ,c了(再取消勾选符号名)

函数栈帧的创建和销毁(vs2013)_第29张图片

 意思就 a 放在地址 ebp-8 的位置上, b放在地址 ebp-14h位置上,

c放在地址 ebp - 20h

函数栈帧的创建和销毁(vs2013)_第30张图片

( 注意c的位置不是最后一个栈空间。只是画的不够大。)

 函数栈帧的创建和销毁(vs2013)_第31张图片

 接下来就函数调用(传参)

 函数栈帧的创建和销毁(vs2013)_第32张图片

 第一句 mov 把 ebp-14h的值赋给eax,就是先把b的值传过去,eax=20函数栈帧的创建和销毁(vs2013)_第33张图片

 函数栈帧的创建和销毁(vs2013)_第34张图片

然后它要push eax 

即 函数栈帧的创建和销毁(vs2013)_第35张图片

 然后把ebp-14h的值赋给ecx,即   ecx =10,然在再压栈函数栈帧的创建和销毁(vs2013)_第36张图片

 (注意 现在 esp指向ecx栈顶)

以上两个动作就是在传参。

 就下来就是重要 的 call 这条语句  call(调用函数的意思/另外记住call的地址)

到这里 想看得更仔细一点 按F11

函数栈帧的创建和销毁(vs2013)_第37张图片

 按下F11之后函数栈帧的创建和销毁(vs2013)_第38张图片

 你会发现call里面放的是下一条指令的地址,也就是压栈了地址

函数栈帧的创建和销毁(vs2013)_第39张图片

 为什么要这么做呢?,因为call 去调用add函数,调完了,你要回来。你要回到call语句的下一条指令的位置,然后向下执行,而恰好把call语句下一条指令记住了,放在这。一会回来的时候,我们就能用上,回来的时候我们就会找到这个地址,往下继续执行

函数栈帧的创建和销毁(vs2013)_第40张图片

然后我们按下 F11,进入如下界面

函数栈帧的创建和销毁(vs2013)_第41张图片

 这时候我们真正来到add函数内部,你会发现跟为main准备函数空间一样。这里我就不再赘述。不然不懂,可以参考前面的过程。

函数栈帧的创建和销毁(vs2013)_第42张图片

 由此可见 z =30的

接下来就是返回参数

函数栈帧的创建和销毁(vs2013)_第43张图片

这句话的意思就是 把 ebp-8 (c=30),存入寄存器 eax中,应该局部变量用完是要销毁的,所以需要先存起来。 到后面要用的时候再拿出来

然后  三pop语句,(出栈操作)

函数栈帧的创建和销毁(vs2013)_第44张图片

 结果如下:函数栈帧的创建和销毁(vs2013)_第45张图片

 接下来 mov语句,把 ebp的值赋给esp

函数栈帧的创建和销毁(vs2013)_第46张图片

 

函数栈帧的创建和销毁(vs2013)_第47张图片

 

函数栈帧的创建和销毁(vs2013)_第48张图片

 

 

 这里相当于吧 a和b的空间也还给操作系统了。

 这里吧 eax的值(z= 30)赋给 ebp - 20h 即赋给c,故 c = eax =30

最后程序的结果显而易见 打印 30

总结

本文从内存层面上主要讲解函数的以下几个问题

  • 局部变量是怎样创建的

首先我们给这个函数分配号栈帧空间,栈帧空间初识化空间之后,然后给我局部变量分配一定的空间

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

因为随机值使我们放进去的 

  • 函数是怎样传参的,传参的顺序是怎样的

当我们要去调用函数的时候,我们已经push push 把这两参数 a,b,从右向左开始压栈,压进去。当我们真正进入函数时,add的函数栈帧通过指针的偏移量,找到我们的形参

  • 形参和实参之间是什么关系

形参是实参的一份临时拷贝,改变形参,不会改变实参。

  • 函数调用是怎么做的

我们在调用之前我就把call指令,下一步指令记住了,压进去了,把ebp调用这个函数的上一个函数的栈帧的ebp存进去了,当我们函数调用完了,返回的时候,弹出(pop)ebp,就能找原始上一个函数调用的ebp,然后指针往下走的时候就能找到esp的顶,然后回到栈帧空间,然后就是我们记住call下一步的命令的地址,当我们往回返的时候,就可以跳到call的下一步的指令的地址,让我们函数调用完了之后,可以返回,

  • 函数调用结果如何返回

返回的结果是通过寄存器返回得到的。

你可能感兴趣的:(c语言)