栈的创建与销毁

1.基本概念

我们先要明白一些基本概念,这样方便我们的研究过程,汇编指令不方便列出,所以给出一些基本概念方便理解。
首先:
(1)本次实验一个地址等于4个字节:地址-4就是下一个地址。
(2)栈帧:函数调用时开辟的空间
(3)函数调用:每次调用函数都需要开辟一个空间
(4)几种寄存器:

  1.    1.eax,ebx,ecx,edx:通用寄存器,保留临时数据,函数返回时确保返回值不会被销毁
  2.    2.ebp和esp这两个寄存器是用来存放地址的,用于维护栈帧
  3.    * ebp 栈底指针
  4.    * esp 栈顶指针
  5.     3.eip:指令寄存器,用于保存下一条指令的地址

调试相关汇编指令

这个指令调试时会用到,自己调试时可以做一下参考

 push:压栈,给栈顶放一个元素,esp的地址值要减小用于同步。

 pop:出栈,从栈顶出一个元素,esp的地址值要增大用于同步。

 mov:数据转移指令,把数据赋给其他地址

 add:加法指令,让esp往下走指向栈顶,用于函数出栈后减小栈帧

 sub:减法指令,让esp指向栈顶,主要用于给函数分配栈帧

 call:call指令的下一个地址,用于返回函数压入地址和转入目标函数

三个地址的概念

创建栈帧时涉及到三个地址:
(1)低地址:位于main的地址之上
(2)main函数的栈帧:main开辟的空间地址
(3)高地址:位于main地址之下
关系如图所示:


知道了他们的位置,我们接下来研究一下栈帧创建与销毁的过程



2.创建与销毁过程

第一步,发现_tmainRTStractup做准备工作

高地址的位置:

在用vs2013实验中,我们溯源main函数,发现main函数并不是一开始就调用的,而是在_tmainRTStractup中调用的,它存放在高地址中,如图可以看到它的位置。


第二步,发现系统会开辟main函数栈帧,让esp和ebp处于维护位置

1.通过esp减去main空间的大小使得esp位于main函数栈顶(栈顶位于地址值,栈底位于高地址)
esp=ebp-main


esp和ebp是两个指针,代表了函数栈帧的地址范围和空间大小,可以发现栈顶指针esp的地址比栈底指针ebp的地址值小。

2.系统给main函数所有内存地址的内容赋予初值,例如都是cc cc cc cc,这是也是我们输出乱码的原因,但是初始化还是有必要的,这样可以防止地址空间里的值不一样。


第三步,栈中压入一个函数

因为main函数开辟空间后,可能还要调用别的函数,所以我们要调用call指令(不占用空间),它的意思是在call指令后一个地址内存放释放空间后让ebp回来的地址调用函数的作用,如图所示

call指令后,esp指向call指令后一个地址,为开辟函数栈帧做准备

接下来让ebp指向esp,esp往低地址跑,这样可以开辟函数栈帧了

这样函数栈帧就创建好了

注意一点:

  • all指令是不占用空间的,但是它的下一个地址要用于存放两个寄存器指针返回时的地址


第四步,释放一个函数

先让esp指向ebp,意思就函数出栈了,实际上函数里的内存并没有清空,只是象征意义上的,我们知道每为一个入栈的函数分配好内存,会给对应地址的内存全部赋初值,所以我们出栈不需要清空了,不会影响结果。

让f函数出栈

接下来 让存放(ebp-main即bsp上一个地址)的内存出栈,同时把ebp-main的地址结果赋给ebp,这样ebp就回到了main栈帧的底部,同理,由于ebp-main出栈了,esp也会向下移动一个地址,这样esp和ebp就会回到压入该函数前的位置了

注意:
*call不占空间,所以esp往下移动一个地址时不用看call,这里把call忽略就行了

同理,当压栈多个函数时

只需要用到多个call就行了,每个call的下一个地址记载的是上一次ebp的地址,便于返回,每出一个栈ebp会根据call上面的地址回到栈底,esp回到上一个栈顶



函数的调用时内存原理

上一步我们知道了main函数里调用其他函数,会在main低地址处开辟了的概念,我们也想知道函数调用时,形参与实参分别存储在哪里?函数有返回值后返回值存储在哪里?之类的问题,请看图:

(1)实参就是main函数里的参数,他们会放在main的栈帧里
(2)调用的函数的栈帧,都是在main之上低地址里,所以形参会拷贝main栈帧里的实参,他们会放在调用函数栈帧对应的低地址里,这也就是我们常说的形参是实参的拷贝的概念


(3)我们知道函数调用结束后就没了,按理来说对应的空间会被清空,我们该如何解决这个问题呢?我们会把返回值保存在寄存器里,例如eax,而寄存器和内存不在一个空间,这样就可以让他保存下来了



第五步,得到规律和原理

(1)在函数调用的过程中,函数的参数是由右向左入栈的;然后是函数内部的 局部变量(注意static变量是不入栈的);在函数调用结束(函数运行结束)后,局部变量最先出栈,然后是参数,最后栈顶指针指向最开始存的指令地址,程序由该点继续运行。

(2)每次出栈,main上函数栈区,所有的内存释放,而esp和ebp会回到原来的维护(初始)位置,而每次入栈,bsp会到esp的位置,而esp的位置会指向新栈顶



3.浅入研究与结论

函数创建的变量是放栈哪个地方的?他们是局部变量还是全局变量?

函数里创建的所有变量都是放在对应函数内存里的,main函数栈帧里放main函数里的变量,其他函数里放他们各自的变量,而且他们都是局部变量,因为每次函数出栈esp就会往下移动,所以这些变量等同于函数一调用完数据就会销毁。还有一点要注意全局变量在静态区里,这里只是简单做一讨论。 注意的是全局变量具有全局性,是存放在静态区的,这里堆里的是变量都是局部变量,存储一般有静态区,堆区,栈区。所以这里不可能出现全局变量的

函数的形参怎么传入,工作原理是什么?

如图,看代码

   

#include<stdio.h>

int f(int x,int,y)

{

    int z;

    z=x+y;

    return z;

 }

    int main()

 {

    int a=8,b=10;

    printf("%d",f(a,b));

    return 0;

  }

可以看到各自函数创建的变量在自己的栈帧里,但是我们的形参并没有在f函数里,而是从右往左的顺序压入栈,可见形参是实参的临时拷贝这句话没有任何问题,具体运算过程应该是y复制b的内容压入栈,x再复制a的内容压入栈,用寄存器eax保存x和y相加的值,再赋给z,这样z就等于a+b的结果了

问题:

函数出栈后结果会消失,那返回值z应该销毁才对,我们怎么得到返回值呢?
答:函数中的返回值z最后会保存在寄存器里,这样就不会随着函数栈帧的销毁而销毁了

esp和ebp起到是什么作用?

esp和ebp起到确定空间大小,象征意义上销毁栈的作用,是维护栈的关键



简单附上一些能看懂的源码仅限理解

#include <stdio.h>

int f(int x,int y)

{

    int z;

    z = x + y;

    return z;

}



int main()



{

    int a = 8, b = 10;

    printf("%d", f(a, b));

    return 0;

}

int f(int x,int y)

{

00241750  push        ebp

00241751  mov         ebp,esp

00241753  sub         esp,0CCh

00241759  push        ebx

0024175A  push        esi

0024175B  push        edi

0024175C  lea         edi,[ebp-0Ch]

0024175F  mov         ecx,3

00241764  mov         eax,0CCCCCCCCh

00241769  rep stos    dword ptr es:[edi]

0024176B  mov         ecx,offset _A89CBE4B_源@cpp (024C008h)

00241770  call        @__CheckForDebuggerJustMyCode@4 (0241311h)

int z;

z = x + y;

00241775  mov         eax,dword ptr [x]

00241778  add         eax,dword ptr [y]

0024177B  mov         dword ptr [z],eax

return z;

0024177E  mov         eax,dword ptr [z]

}

00241781  pop         edi

00241782  pop         esi

00241783  pop         ebx

00241784  add         esp,0CCh

0024178A  cmp         ebp,esp

0024178C  call        __RTC_CheckEsp (0241230h)

00241791  mov         esp,ebp

00241793  pop         ebp

00241794  ret

--- 无源文件 -----------------------------------------------------------------------

00241795  jmp         dword ptr [__imp____stdio_common_vfprintf (024B174h)]

0024179B  int         3

0024179C  int         3

0024179D  int         3

0024179E  int         3

0024179F  int         3

Project2.exe!_JustMyCode_Default(void):

002417A0  push        ebp

002417A1  mov         ebp,esp

002417A3  pop         ebp

002417A4  ret

002417A5  jmp         dword ptr [__imp____acrt_iob_func (024B178h)]

002417AB  int         3

002417AC  int         3

002417AD  int         3

002417AE  int         3

002417AF  int         3

--- F:\cpp code\learn_cppl\Project2\Project2\源.cpp -----------------------------

int main()

{

002417B0  push        ebp

002417B1  mov         ebp,esp

002417B3  sub         esp,0C0h

002417B9  push        ebx

002417BA  push        esi

002417BB  push        edi

002417BC  lea         edi,[ebp-18h]

002417BF  mov         ecx,6

002417C4  mov         eax,0CCCCCCCCh

002417C9  rep stos    dword ptr es:[edi]

002417CB  mov         ecx,offset _A89CBE4B_源@cpp (024C008h)

002417D0  call        @__CheckForDebuggerJustMyCode@4 (0241311h)

int a = 8, b = 10;

002417D5  mov         dword ptr [a],8

002417DC  mov         dword ptr [b],0Ah

printf("%d", f(a, b));

002417E3  mov         eax,dword ptr [b]

002417E6  push        eax

002417E7  mov         ecx,dword ptr [a]

002417EA  push        ecx

002417EB  call        f (02413B6h)

002417F0  add         esp,8

002417F3  push        eax

002417F4  push        offset string "%d" (0247BCCh)

002417F9  call        _printf (02413A7h)

002417FE  add         esp,8

return 0;

00241801  xor         eax,eax

}

00241803  pop         edi

00241804  pop         esi

00241805  pop         ebx

00241806  add         esp,0C0h

0024180C  cmp         ebp,esp

0024180E  call        __RTC_CheckEsp (0241230h)

00241813  mov         esp,ebp

00241815  pop         ebp

00241816  ret

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值