数学建模社区-数学中国

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

作者: rashige    时间: 2004-4-29 10:22
标题: [转帖]什么叫“堆”,“堆”跟“栈”有什么区别?
<>一、预备知识—程序的内存分配! s5 O4 m5 R8 q0 T# U/ `
一个由c/C++编译的程序占用的内存分为以下几个部分
7 G5 Q/ r4 Q! {) s7 M9 n- h( ]1、栈区(stack)—   由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。8 K. K) w5 ]; G" a
2、堆区(heap) —   一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。- |! H1 \* }& f9 t
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放 % z: P' l+ E5 _2 S1 p7 }$ u
4、文字常量区  —常量字符串就是放在这里的。 程序结束后由系统释放$ p. y$ x7 q8 a
5、程序代码区—存放函数体的二进制代码。</P>
# P$ r, i* m3 _/ O! x. ^<>二、例子程序
6 J3 n6 B  U, ^" m8 V这是一个前辈写的,非常详细
: W$ Z/ K% u. R: d+ a9 E: d# P//main.cpp
, C( f5 Y, [/ I7 p  s  h* @3 X. Rint a = 0;    全局初始化区 5 s9 G1 M. t) y& n+ Y2 X
char *p1;     全局未初始化区 9 w/ f5 B9 t3 m8 X: O
main()
& M/ e" r# H+ J{
7 e, C- {  I6 ~int b;              栈 ' p$ I4 k- F5 j  m- p( j
char s[] = "abc";   栈
+ G0 t9 z) B. q1 @, i$ _! k3 D8 R0 cchar *p2;            栈 9 c& V) ^: A( k. O# i& K
char *p3 = "123456";     123456\0在常量区,p3在栈上。 # D1 H4 w7 j1 u' {. y
static int c =0;       全局(静态)初始化区
' w8 z1 M6 z, K. H) C: X4 dp1 = (char *)malloc(10);
8 K( a& e2 H! Z9 h& L$ mp2 = (char *)malloc(20); 6 s1 Y, _+ H9 E. E& Y4 I. o
分配得来得10和20字节的区域就在堆区。
0 c$ Z- {+ |' l* w, F) q: J, wstrcpy(p1, "123456");  123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
8 J/ @4 Y# _, Z+ G* l7 P/ [}
7 [3 J, u% ?* P</P>
* B) ~, M/ l& j- L7 u# `5 q<>二、堆和栈的理论知识
- t% l2 f; U- l2.1申请方式& l; T; n- W. W# H8 m
  stack:
( t9 }( d+ h5 |4 G4 [+ e- O     由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间            
$ U' N5 c+ t, J! }   heap:
6 l% r7 y9 W4 P( F     需要程序员自己申请,并指明大小,在c中malloc函数5 P  J: w+ Q( G, V+ O& L
       如p1 = (char *)malloc(10);
8 {% ]1 j! ^, R1 H     在C++中用new运算符. D2 i0 _$ q. ^& K0 ^& d
       如p2 = (char *)malloc(10);
2 O7 Q0 `( |+ h      但是注意p1、p2本身是在栈中的。% ~+ x, q* P, y
</P>
$ a. }% _! x! d1 D! e; Q<>2.2
) ^6 L# m3 F+ E4 |! G& l' ~4 m' u  l申请后系统的响应
  l( V/ U3 d8 u/ I   栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。' D6 H' Z' i  x2 L$ Y: Y1 @7 p
   堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
! Z# H3 x# n. E3 q' H会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中</P>9 P2 Y# ~% W2 q* D. l- q

, x" S' w# b2 e2 p7 W2 Q6 M' K! ^<>2.3申请大小的限制. A: Z8 q  U+ {) ?1 x
   栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
) Y9 X( Q# o% C" U2 \0 Y   堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。# z3 _! f  y/ [; K
</P>
0 f5 S( i3 U/ P1 O8 v  \- q7 ?<>2.4申请效率的比较:
" B" |0 P3 o2 L   栈由系统自动分配,速度较快。但程序员是无法控制的。$ \7 I" z# _  h" z4 y
   堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.* i0 r# Q% V$ h$ z/ x
   另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。</P>1 f7 m- T0 ]! [& X  x; |* l& M
<>2.5堆和栈中的存储内容
, G* v6 }0 Z. v2 o; z. B" R   栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。1 f# f4 X4 D/ Q: g" b- e
   当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
5 d, B, t: V" p   堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。</P>
3 R; r) z3 J, j8 B- K5 B) M" I+ ~  w! ]
<>2.6存取效率的比较</P>$ O5 `' M# N, V+ c% V1 U$ K# H
<>char s1[] = "aaaaaaaaaaaaaaa"; % U) K& s, \2 ]3 v; N+ ]
char *s2 = "bbbbbbbbbbbbbbbbb"; ( q0 L" p, z9 @
aaaaaaaaaaa是在运行时刻赋值的;6 A( e0 [6 @4 ]% I  H
而bbbbbbbbbbb是在编译时就确定的;
  l0 V6 S9 ^# p; W+ a5 e( z9 g# T但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
6 M: o' E& a8 W' _比如:
0 |1 {: @; \8 j$ q#include &lt;stdio.h&gt;
1 Q! y9 L& A0 l3 F* m  O/ Q' \void main()
( @2 P$ Z& |2 R4 E: n{
7 u' j) P0 r4 o7 a/ jchar a = 1;
9 k* l. v% Q1 K/ h6 }char c[] = "1234567890";
( m5 @, r& O, t# J# ]1 rchar *p ="1234567890";
. y! h: _$ X2 v* Ra = c[1];
) x' n7 o' J- `a = p[1]; & \+ z( ^; h. E
return;
4 W: l2 }& |1 h6 T8 X: R* `} 6 F6 ]8 }' t) A) G. v
对应的汇编代码
1 Z6 o+ u8 k7 o; e3 U10:   a = c[1]; / q6 o, A, j$ g, l' B- s) O4 j/ F; ]
00401067 8A 4D F1 mov       cl,byte ptr [ebp-0Fh]
1 j8 A2 u0 h/ F0040106A 88 4D FC mov         byte ptr [ebp-4],cl 0 {  u# Z/ y; |* y7 ]3 {
11:   a = p[1]; $ R7 X9 c+ y7 |9 l, o
0040106D 8B 55 EC mov         edx,dword ptr [ebp-14h]   M* U- p- F# d: c& e1 |, `! Y0 N
00401070 8A 42 01 mov       al,byte ptr [edx+1]
9 f# f, _- f) U00401073 88 45 FC mov         byte ptr [ebp-4],al
9 [. O, _0 V. {1 S( s- j' e9 f7 l第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了</P>0 t7 r! P! G( [& X# S

4 w4 S, l0 L0 P8 v<>2.7小结:
" l* c- X/ w. O: O   堆和栈的区别可以用如下的比喻来看出:
4 @9 Q5 Z: N5 e! [0 `* ?   使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。8 A$ [/ j) N& f  d  ~, r
    使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。</P>
作者: ilikenba    时间: 2004-4-29 11:11
<>学习,</P><>其实简单的说直接用类型名申请的变量都是放在了栈中,但是有时这并不能满足您的需要,可以用malloc或new在堆中申请空间,但要注意的是栈中的空间系统会自动收回,而对中的则需要用delete回收!</P>




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