2.7小结: 堆和栈的区别可以用如下的比喻来看出: 使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是***度小。 使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且***度大。
windows进程中的内存结构
在阅读本文之前,如果你连堆栈是什么多不知道的话,请先阅读文章后面的基础知识。
接触过编程的人都知道,高级语言都能通过变量名来访问内存中的数据。那么这些变量在内存中是如何存放的呢?程序又是如何使用这些变量的呢?下面就会对此进行深入的讨论。下文中的C语言代码如没有特别声明,默认都使用VC编译的release版。
首先,来了解一下 C 语言的变量是如何在内存分部的。C 语言有全局变量(Global)、本地变量(Local),静态变量(Static)、寄存器变量(Regeister)。每种变量都有不同的分配方式。先来看下面这段代码:
#include <stdio.h>
int g1=0, g2=0, g3=0;
int main() { static int s1=0, s2=0, s3=0; int v1=0, v2=0, v3=0;
//打印出各个变量的内存地址
printf("0x%08x\n",&v1); //打印各本地变量的内存地址 printf("0x%08x\n",&v2); printf("0x%08x\n\n",&v3); printf("0x%08x\n",&g1); //打印各全局变量的内存地址 printf("0x%08x\n",&g2); printf("0x%08x\n\n",&g3); printf("0x%08x\n",&s1); //打印各静态变量的内存地址 printf("0x%08x\n",&s2); printf("0x%08x\n\n",&s3); return 0; }
编译后的执行结果是:
0x0012ff78 0x0012ff7c 0x0012ff80
0x004068d0 0x004068d4 0x004068d8
0x004068dc 0x004068e0 0x004068e4
输出的结果就是变量的内存地址。其中v1,v2,v3是本地变量,g1,g2,g3是全局变量,s1,s2,s3是静态变量。你可以看到这些变量在内存是连续分布的,但是本地变量和全局变量分配的内存地址差了十万八千里,而全局变量和静态变量分配的内存是连续的。这是因为本地变量和全局/静态变量是分配在不同类型的内存区域中的结果。对于一个进程的内存空间而言,可以在逻辑上分成3个部份:代码区,静态数据区和动态数据区。动态数据区一般就是“堆栈”。“栈(stack)”和“堆(heap)”是两种不同的动态数据区,栈是一种线性结构,堆是一种链式结构。进程的每个线程都有私有的“栈”,所以每个线程虽然代码一样,但本地变量的数据都是互不干扰。一个堆栈可以通过“基地址”和“栈顶”地址来描述。全局变量和静态变量分配在静态数据区,本地变量分配在动态数据区,即堆栈中。程序通过堆栈的基地址和偏移量来访问本地变量。
├———————┤低端内存区域 │ …… │ ├———————┤ │ 动态数据区 │ ├———————┤ │ …… │ ├———————┤ │ 代码区 │ ├———————┤ │ 静态数据区 │ ├———————┤ │ …… │ ├———————┤高端内存区域
堆栈是一个先进后出的数据结构,栈顶地址总是小于等于栈的基地址。我们可以先了解一下函数调用的过程,以便对堆栈在程序中的作用有更深入的了解。不同的语言有不同的函数调用规定,这些因素有参数的压入规则和堆栈的平衡。windows API的调用规则和ANSI C的函数调用规则是不一样的,前者由被调函数调整堆栈,后者由调用者调整堆栈。两者通过“__stdcall”和“__cdecl”前缀区分。先看下面这段代码:
#include <stdio.h>
void __stdcall func(int param1,int param2,int param3) { int var1=param1; int var2=param2; int var3=param3; printf("0x%08x\n",¶m1); //打印出各个变量的内存地址 printf("0x%08x\n",¶m2); printf("0x%08x\n\n",¶m3); printf("0x%08x\n",&var1); printf("0x%08x\n",&var2); printf("0x%08x\n\n",&var3); return; }
int main() { func(1,2,3); return 0; }
编译后的执行结果是:
0x0012ff78 0x0012ff7c 0x0012ff80
0x0012ff68 0x0012ff6c 0x0012ff70
├———————┤<—函数执行时的栈顶(ESP)、低端内存区域 │ …… │ ├———————┤ │ var 1 │ ├———————┤ │ var 2 │ ├———————┤ │ var 3 │ ├———————┤ │ RET │ ├———————┤<—“__cdecl”函数返回后的栈顶(ESP) │ parameter 1 │ ├———————┤ │ parameter 2 │ ├———————┤ │ parameter 3 │ ├———————┤<—“__stdcall”函数返回后的栈顶(ESP) │ …… │ ├———————┤<—栈底(基地址 EBP)、高端内存区域