C语言之栈桢

一、内存分布

一般我们把进程的地址空间分为:栈区(heap)、堆区(stack)、未初始化静态全局区、已初始化静态全局区、字符常量区、代码区。如图:
这里写图片描述

二、栈帧的建立与撤销

我们知道每一次函数调用的过程都要为函数开辟栈空间,用于本次函数调用中临时变量的保存,现场保护。这块栈空间称之为函数栈桢。每一个函数都有自己的栈帧空间,并且独占自己的栈帧空间。
系统提供两个特殊的寄存器用于标识栈桢的顶部和底部。
ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶,即栈顶寄存器。      
EBP:基址指针寄存器(extended base pointer),即栈底寄存器

除了与栈相关的寄存器外,我们还需要记住另一个至关重要的寄存器。    EIP:指令寄存器(extended instruction pointer),也称程序计数器。其内存放着一个指针,该指针永远指向下一条等待执行的指令地址。      
首先栈是自高地址向低地址即向下生长的。我们通过如下程序了解栈帧建立过程:

int myadd(int _a, int _b)
{
    int _z = _a + _b;
    return _z;
}
int main()
{
    int a = 0xAAAAAAAA;
    int b = 0xBBBBBBBB;
    int c=myadd(a, b);
    printf("you should run here:%d\n",c);
    system("pause");
    return 0;

首先main 函数调用myadd函数,其反汇编代码如下:

这里写图片描述
在调用函数之前,形成的函数栈桢是这样的:
这里写图片描述
这时寄存器状态为:
这里写图片描述
之后调用函数call指令做了两件事情:
1.将当前汇编指令的下一条指令地址进行保存(入栈保存),即将00F13EA9入栈保存。保存的目的是为了回到main函数,执行call指令的下面的指令。
2.跳转至目标函数的入口地址处(jmp),即修改eip寄存器为myadd函数首地址00F11570。如图所示:
这里写图片描述
这里写图片描述
此时寄存器和内存显示为:
这里写图片描述
此时栈桢状态为;
这里写图片描述
此时可以看出形参实例化是从右到左的。

这时已经进入了myadd函数,因此会形成一个新的栈桢:
这里写图片描述
这里写图片描述
在进行完成加法运算后,将返回值放到了eax寄存器中。进行一些出栈操作后,
mov esp,ebp之后,此时ebp指向main函数的ebp,esp向高地址处移动。
进行ret指令:
1,将之前保存的函数值的返回地址进行出栈。
2,将之前保存的地址放回到eip寄存器当中,即将00F13EA9放入到eip寄存器中。

这里写图片描述
此时栈桢状态为:
这里写图片描述
执行完ret指令后回到了main函数中:
这里写图片描述

这里写图片描述
而此时可以看到将eax的内容放入到了c中,即把返回值赋给了c.
此时main函数调用myadd函数全部完成。

练习

在上面例子的基础上,若只知变量a,如何通过变量a修改变量b的值?

int myadd(int _a, int _b)
{
    int *p = &_a;
    p++;
    *p = 10;
    int _z = _a + _b;
    return _z;
}

此博客只供借鉴,若有什么问题和建议,可以留言,望大家多多包涵!!!

猜你喜欢

转载自blog.csdn.net/mmwwxx123/article/details/80259704