数学建模社区-数学中国

标题: [转帖]什么叫“堆”,“堆”跟“栈”有什么区别? [打印本页]

作者: rashige    时间: 2004-4-29 10:22
标题: [转帖]什么叫“堆”,“堆”跟“栈”有什么区别?
<>一、预备知识—程序的内存分配  F! D7 t  n# }0 r" e$ ?5 Q( J1 D
一个由c/C++编译的程序占用的内存分为以下几个部分! J2 U. R/ ^+ l! X  ?
1、栈区(stack)—   由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。0 N5 A9 Z7 F! i  ?; n9 o- ]
2、堆区(heap) —   一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。: T4 @; n2 ^4 J7 C: O
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放 ; l( \1 ]' p0 q* l
4、文字常量区  —常量字符串就是放在这里的。 程序结束后由系统释放
7 G" S) v  w! t& K5、程序代码区—存放函数体的二进制代码。</P>6 r% T( K0 K) Y1 K7 `0 X. b7 g
<>二、例子程序" s( ?  P# F2 [3 t1 {. V
这是一个前辈写的,非常详细0 I4 P' e; f- o2 F, g& Y9 \4 z
//main.cpp
& n' T- b. D( d9 {$ Mint a = 0;    全局初始化区
8 _- B4 a% [+ u5 o0 n+ D: zchar *p1;     全局未初始化区 ; _, y1 S( i: [
main() / ^* }0 b0 x( Z& _- B; Z* g; {
{
. x2 |; j" i  w' R' Mint b;              栈 * F* b9 r9 j  V8 X  H% ?0 U$ v
char s[] = "abc";   栈
' l1 `$ E& ?# O9 n, X1 ]char *p2;            栈 . d+ |. [$ K  X; s
char *p3 = "123456";     123456\0在常量区,p3在栈上。
: o" E0 W5 Y+ u0 f, ^+ H2 }static int c =0;       全局(静态)初始化区
! e  n0 K* a- F  x9 Yp1 = (char *)malloc(10); " {- R5 d# m" b/ P3 P
p2 = (char *)malloc(20); # M& v5 a+ r# X' l
分配得来得10和20字节的区域就在堆区。 ' f% p% V1 r# T  b# b/ k& o
strcpy(p1, "123456");  123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
+ |' K. e' F( I9 N: Y- J} / w. E( U1 p; }8 M! Z
</P>  @1 P4 O! R  n  l$ |& J
<>二、堆和栈的理论知识, L8 h5 k6 B3 g
2.1申请方式: E3 Q3 Q9 G6 N7 l/ l4 C
  stack:* O7 P4 n7 G$ x, h
     由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间             0 A4 v+ d9 o9 `5 n
   heap:& C& s; \8 n8 {# A; F
     需要程序员自己申请,并指明大小,在c中malloc函数0 q% L3 }7 |$ O3 k. q7 x! P% W; q; D( V
       如p1 = (char *)malloc(10); 7 L! U" v! G) w9 X, u8 ?( R2 V
     在C++中用new运算符. g/ M! _! u6 l
       如p2 = (char *)malloc(10);
3 e6 |& }+ g% g! o+ X      但是注意p1、p2本身是在栈中的。* b- K  \% N$ H2 x( L6 Z
</P>
8 d* z/ g2 r6 i5 a* l3 o( o<>2.2
1 w7 t9 x) W; D' r* j申请后系统的响应, \* Q/ ^* a. n8 {
   栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
4 F2 f- m* r  F1 m& A6 [( T   堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
7 r  @2 }. D/ H# a会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中</P>* K4 u" C$ N2 n  g1 M5 Y8 e9 U
+ E8 v& T' a- P" {
<>2.3申请大小的限制
: ^9 ]8 t4 K( K; B   栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
* x& D1 @; \# a2 L   堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
6 u" Z, J$ Z$ V) G1 O; ]! B3 w</P>
" M' g2 l! G+ |+ W5 j) \, G<>2.4申请效率的比较:3 |% U0 i. ^( a6 R: R! y: \$ z4 y& u
   栈由系统自动分配,速度较快。但程序员是无法控制的。* }; e6 ~- ~. G! G
   堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
( x% ?7 F' x- F5 L/ f   另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。</P>
  P$ N6 U. s) B( S; R: u7 P2 J<>2.5堆和栈中的存储内容
) c2 [. V% ^9 k# n   栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
$ L( A) ]8 R! x+ z9 X' P   当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
1 E$ o/ O6 S2 c1 w2 i4 o   堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。</P>
& ]! @! V4 E- C! p+ o9 A9 _" }3 h, `" r
<>2.6存取效率的比较</P>
2 I( m4 h& Q. R) w8 [. \/ u<>char s1[] = "aaaaaaaaaaaaaaa"; 5 b; Z$ |1 P3 B& ?. Z
char *s2 = "bbbbbbbbbbbbbbbbb"; 1 [. F. v) J$ b2 @% _
aaaaaaaaaaa是在运行时刻赋值的;4 q' k( q& @/ H6 c: s2 T$ k& Y, {: {
而bbbbbbbbbbb是在编译时就确定的;
( ~& E; L) r/ h  y" l7 F1 c但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
4 j/ l4 t# U2 j! K比如: / ]  c0 a/ }' n; c/ Q% c$ S
#include &lt;stdio.h&gt; ! I% x  C; o6 E0 `. k9 P
void main() 5 D, z. |/ t, k$ _  ]) Y2 F- Y
{
8 V* W2 u7 x+ o% {; z" [% Schar a = 1; ( M$ w' ?4 `3 {/ p
char c[] = "1234567890";
6 R: J' F' S; h  c- Rchar *p ="1234567890"; $ \4 {* V1 s! |7 D, k- P  v2 [: E' s
a = c[1]; 3 I+ J4 _( V- Z
a = p[1];
, K. t8 Q% g& treturn; , Q1 l/ R' E! I
}
) {. R" [$ `* A6 E& M4 s) L# `对应的汇编代码8 }4 i3 |1 \' h& M
10:   a = c[1]; : h, ?& V; \# n( Q+ Y2 F$ P6 @% N
00401067 8A 4D F1 mov       cl,byte ptr [ebp-0Fh] ; [$ }4 z) w# y
0040106A 88 4D FC mov         byte ptr [ebp-4],cl ( I3 _) {$ n5 ^- e. P. M
11:   a = p[1];
3 d3 J5 X. m4 ~& |0 D! s0040106D 8B 55 EC mov         edx,dword ptr [ebp-14h]
8 X% P. d0 \7 z  Q1 ~$ f00401070 8A 42 01 mov       al,byte ptr [edx+1]
" A" D1 e' l) ?) k. ^6 i00401073 88 45 FC mov         byte ptr [ebp-4],al 9 ?0 C& ^  M+ |& U
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了</P>
8 `" w7 @  E: P& V/ E" [% m4 ]2 o$ N4 X! G
<>2.7小结:
5 Y& P3 w- f2 [8 F   堆和栈的区别可以用如下的比喻来看出:% V" [1 u' E% A. H2 F
   使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。9 i2 u: s  g$ M( S; z# t9 g; V
    使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。</P>
作者: ilikenba    时间: 2004-4-29 11:11
<>学习,</P><>其实简单的说直接用类型名申请的变量都是放在了栈中,但是有时这并不能满足您的需要,可以用malloc或new在堆中申请空间,但要注意的是栈中的空间系统会自动收回,而对中的则需要用delete回收!</P>




欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5