寄存器

寄存器(Register)是 CPU 内部极高速的存储单元,用于暂存指令、数据和地

址。它的主要作用是减少 CPU 访问内存的次数,从而极大提升程序执行速度。

常见的寄存器有 eax、ebx、ecx、edx、ebp、esp 等

其中 ebp(栈顶指针) 、 esp(栈底指针) 是用于维护函数栈帧的

main 函数也是被调用的

本文的所有代码都是在 visual studio 2013 中运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int Add(int x, int y)
{
int sum = 0;
sum = x + y;
return sum;
}

int main()
{
int a = 10;
int b = 20;
int c = 0;
c = Add(a, b);
printf("%d\n", c);
return 0;
}

这段代码在进入调试后,可以看到如图所示

main() 函数也是被调用的,main()在 __tmainCRTStartup()函数中被调用,而

__tmainCRTStartup() 在 mainCRTStartup() 中被调用

栈区

C语言中通常会将内存划分为 静态区(也称全局/静态存储区)、堆区栈区

个核心区域。

  • 静态区:存放全局变量、静态变量(static 修饰的局部或全局变量)以及字

    符串常量。这部分内存在程序编译时就已确定大小,并在程序整个运行期间都存

    在(从加载到退出)。

  • 堆区:用于动态内存分配(如 malloc/new)。由程序员手动申请和释放(若不释

    放,程序结束时由操作系统回收)。堆区的大小受限于系统虚拟内存,相

    对较大,但分配速度较慢,容易产生内存碎片。

  • 栈区:存放局部变量、函数参数、函数调用返回地址等。由编译器自动管

    理,遵循“后进先出”(LIFO)规则。栈区大小较小(通常几 MB),分配速

    度极快,但超出容量会导致栈溢出。

栈区的使用习惯是从高地址向低地址使用,但具体的要视编译器而定

在 visual studio 2013 调试环境中,栈区的使用是从高地址到低地址

汇编查看函数栈帧的创建与销毁过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int Add(int x, int y)
{
int sum = 0;
sum = x + y;
return sum;
}

int main()
{
int a = 10;
int b = 20;
int c = 0;
c = Add(a, b);
printf("%d\n", c);
return 0;
}

这段代码在进入调试后,转到反汇编

上文说到在调用 main() 函数前会调用__tmainCRTStartup(),也就是

此时占栈中如图所示

先看创建变量 a 前的汇编代码,取消勾选显示符号名


红框中框选的汇编代码 push 表示压栈 , 将 ebp 压入栈中, 压栈结束

后,esp 会自动指向压入的变量


再往后走一步

这里红框选中的汇编代码, mov 表示赋值, 将 ebp 的值赋值给 esp

此时查看 ebp esp 中的值


再往后走一步

这里红框选中的汇编代码, sub 表示减等, 将 esp -= 0E4H 0E4H是一个

十六进制数,换算成十进制数是 228

此时查看 esp 中的值


再往后走一步

这里红框选中的汇编代码, push 表示压栈, 将栈中压入
ebx esi edi , 压

栈结束后,esp 会自动指向压入的变量

查看 ebx esi edi

再往后走一步

这里红框选中的汇编代码, lea 表示load effctive address, 加载有效地址,

表示在 ebi 中加载的是 ebp - 0E4h 这块大小的地址

调试查看 ebi 的值

而这一段汇编语言是在将从 edi 指向的位置开始,向下 ecx 个

dword(double word 一个 word 表示两个字节) 的数据全部赋值为

0xCCCCCCCCh


再往后走一步

这里红框选中的汇编代码, 表示向 ebp - 8 指向的内存空间中放入

十六进制数 0Ah,也就是 10 , 就是变量 a


图中汇编代码表示向 ebp - 14h(20) 和 ebp - 20h(32) 指向的内存空间

处放入 14h(20,变量 b) 和 0(变量c)


图中红框选中的汇编代码是将 ebp - 14h 指向内存空间中的值(即变量

b)赋值给 eax , 将 ebp - 8 指向内存空间中的值(即变量a)赋值给 ecx ,并

将 eax ecx 压入栈中


图中红框选中的汇编代码 call 是调用函数,同时 call 指令下一条指令的

地址 00091AA0 压入栈中


这是进入 Add 函数后的汇编代码,红框选中的就是在为 Add 函数

创建空间,并压对应的寄存器入栈,为变量 sum 分配空间


图中红框选中的汇编代码是将 ebp + 8 的值赋值给 Add 中的 eax , eax

再加等 ebp + 0Ch(十进制数12),这实际上就是再传参,显然这里是从

Add 函数栈帧外拿值放到 Add 函数内的 eax 中,这也就能解释为什么形

参是实参的一份临时拷贝,加等的值赋值给 ebp - 8(即变量 sum )


图中红框选中的汇编代码是将 ebp - 8 的值(即变量 sum )的值保存在

Add 函数中的 eax 中, 然后依次 pop 也就是弹出 edi esi ebx , 弹出与压

栈是相反的,每弹出一个元素 esp 会自行指向弹出后的栈底空间,弹出

后再将主函数中的 ebp 赋给 esp , 然后再弹出 ebp, 此时可以发现,esp

指向了前文中 call 指令下一句指令,ret 表示返回值


图中红框选中的汇编代码是将 esp + 8 , 即释放了形参 x 和 y 的空间,同

时将函数的返回值赋值给了 ebp - 20h 即变量 C

到这里主函数 main 和 Add 函数的栈帧的创建和销毁就分析完成了