c语言--栈帧结构分析

这是一篇在自己写了很久的文章了,因为要清理电脑,放在博客上当备份。


写在前边:

栈帧结构对于初学C语言着,可能有些陌生。但是不能否认的是栈帧在C语言中扮演着很重要的角色,下边就栈帧分析下内存。如果真对C语言感兴趣的请耐心的看下边的叙述。

栈帧说的简单点就是调用函数的过程中,为这个函数开辟一个栈空间,用来保存这个函数中的临时变量。而这个存储函数临时变量的空间就被称为栈空间。而这个栈空间就被称为栈帧。

*******************************************************

索引:

在分析栈帧前还需要简述几个简单的知识点。(在栈帧的分析中我们会用到的)

EIP:EIP又称为PC指针寄存器。EIP中存储的是CPU正在执行指令的下一条指令。指令寄存器(extended instruction pointer),其内存放着一个指针,该指针永远指向下一条等待执行的指令地址。 可以说如果控制了EIP寄存器的内容,就控制了进程——我们让eip指向哪里,CPU就会去执行哪里的指令。eip可被jmp、call和ret等指令隐含地改变(事实上它一直都在改变)(ret指令就是把当前栈顶保存的返回值地址 弹到eip中)

esp:栈顶寄存器,存放了指向函数栈帧栈顶的地址。其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。

Ebp:栈地寄存器(又可称基址寄存器),存放了指向栈帧栈底的地址。其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。

在这块就需要简单说一下函数的内存图了:

 

(&&对这就个图的介绍):内存中地址由小到大,先可以简单的化成上边的这样一个区域,此次主要研究的是堆和栈区,下边的内存暂不仔细去研究(下边内存并不全面,只是取了几个代表)。

栈区和堆区是相对而生的,堆区是向上生长的,栈区是向下生长的。堆区和地址空间的大小排列一样,而这快容易出问题的应该算栈区了。而我们下边会仔细去叙述一下堆区的。

而这里需要强调的一点是堆区和栈区之间有一段空白,堆区和栈区不是相连的区域。

上边说了这么多的话语了,现在简单粗暴一点,直接上代码。

我们运行的代码在VC6.0环境下边,原因只是因为编译器环境简单。

定义一个main函数,定义两个参数a,b。在main函数中调用一个函数使得两个数相加,暂且将这个函数称为Add,最终返回到main函数中。打印出结果。

函数如下

 

暂时定义ab为这样,为了等会更直观的展现出来。代码很简单现在进行分析。

程序执行进入汇编语言过程中。(汇编语言的指令不详细介绍了)

 

栈向下生长,因此ebp栈底反而在地址大的地方,esp栈顶在地址小的地方。


这张图片所描述的是栈区的放大空间。

栈是向下生长的,因此这块需要注重的一个细节是ebp栈底指针反而指向地址大的地方,esp栈顶指针指向在地址小的地方。

 

调用出汇编代码,函数从main开始,前边的预处理暂时不仔细去研究,简单来描述,预处理给main分配了一个空间,并全部初始化了。如果真对这段汇编代码感兴趣,可以查阅相关的汇编语言的书籍。

 

 

当代码进行到add时时在栈空间中已经进去了a,b,以及定义的ret。在这块需要注意的是mov指令将a移动到【ebp-4】即ebp指针下移指向a,将a的地址放进去。b移动到【ebp-8】中即ebp指针指向了b的地址。

这块需要说的和之前一样栈中的生长方式是向下生长的,因此地址由高到低,一次存放。即实际上就是给a先开辟空间,再给b开辟空间。

 

此处push即压栈的过程,而eaxcup里的通用寄存器,此时的eax里放的是esp的地址。即栈帧的压栈过程是往栈顶压栈,通用寄存器里边放的是esp的地址。此时就是最关键的地方了。在理论上的栈先进后出,就在这用直观的代码显示出来了。

esx,ebx,ecx,edx都是通用寄存器,在CPU中起一个存储临时变量的作用)

eax存储的是【ebp-8】,即b的地址,即从汇编语言来看就是将,b的地址压栈进,esp顶部。而a也是这个原理根据汇编代码可以分析出,ab的下边。即根据汇编代码可以分析画出下边的图。

 

 

        此时的esp指向了压栈a的地址空间。

这个过程返回到编辑器,调用一下寄存器,与内存就可以清晰的展现在面前。

 

此时的调用esp栈顶的地址,a,b的地址展现的很清楚了。而这块还需要注意的是栈是由上向下生长的。

这块需要知道一个汇编语言的命令。

Call命令:(两个作用)

1、将当前执行指令的下一条指令的下一条地址压入栈中。

2、随机call就会跳转(jmp)至指定函数代码块去执行。

 

此时的界面是调用玩call指令之后的图,即现在的栈顶将add这个地址压栈进去。可看见esp的最上端是add的地址。

 

上图就是调用到add函数的代码。跟main有异曲同工之处。

此时的eip开始调用add()函数中的值。

 

即在空间图上可以分析为在空间上在分配一个空间用来存放add函数的临时变量。

 

Add函数就是跟上边的main函数一样的存放在属于它自己的栈帧空间里的。具体的就不详细描述了。可以跟着上边的main对比来看一下。

而在这块 add函数返回的这块的仔细的剖析一下。

 

在函数add中,a,b的和赋值给z,而将两个数所加之和赋值给,通用寄存器eax中,暂时存放起来。

 

    在函数的结束部分,有这样一段汇编代码。这个过程就是栈的释放过程。

将ebp的地址赋给了esp.然后释放了ebp的空间,即main:ebp被释放。Ebp返回到main函数中的ebp地址中去。而原来的main:ebp空间是空的所以向上自动跳一格。

而最后的ret这块需要强调一下。

Ret命令的两个作用:

1、弹出pop栈顶

2、弹出栈顶的地址,并且将返回值放入EIP中。

则此时的ebpesp又返回到之前的地址。到现在,栈的形成与销毁就完成了。

得出一个函数的栈空间都是自己开辟的,使用完后销毁释放空间。


你可能感兴趣的:(C语言,栈帧结构,C语言)