也谈C++内存区域

众所周知,C++内存区域被分为5大类:栈、堆、自由存储区、全局/静态存储区、常量存储区。

    栈由编译器控制,栈空间的申请、使用和释放全权由编译器处理。这里的“全权处理”意思是责任归属,并不是说编译器在程序运行时介入管理。实际上,编译器的工作在编译期就完成了,它对栈的管理体现在编译时对寄存器esp的维护上。

    堆即程序员使用new和delete操作符进行管理的内存区域。对堆的访问往往要比栈要慢,原因在于栈由系统实现,有硬部件上的优势;而堆只是C++语言中的数据结构,是纯粹软件的,访问堆上的变量一般都需要栈的参与才能完成。

    自由存储区这个概念存在争议,争议的焦点在于它与堆是不是同一个东西。自由存储区也由程序员管理,但用到的是malloc和free操作。其实,自由存储区和堆是有区别的:一方面,管理方法不同,malloc和free是函数,new和delete是C++操作符;另一方面,malloc只负责开辟空间,返回指针是空类型指针void*,new不仅开辟空间而且还将调用对象构造函数,类似的,free只管释放空间,delete还将调用对象析构函数。

    全局/静态存储区正如其名,是全局变量和静态变量的集中地。“全局”着眼于全局变量的全局作用域;“静态”着眼于static变量的生命周期。前者容易理解,后者稍微有些难懂。其实,C++中的动和静,往往都是从对象的生命周期这个角度去界定的。对于变量来说,“动态”的变量在内存区域里进进出出的变量,它们的生命仅限于自己的作用域,一旦离开自己的作用域就将从内存中被擦除,动态变量占用的那片内存区域在程序执行过程中有可能被释放掉;“静态”的变量则不同,它最大的特点就是随程序生,随程序死,步调很一致,只要程序启动了,静态变量就在静态存储区拥有了自己的空间,程序执行过程中不会被释放,只有程序结束运行,它们才被释放。可见,静态存储区强调的是变量的生命长度与程序一致。

    常量存储区也很容易理解,即存储常量的地方。需要注意的是字面数字并不存放在这个区域,常量存储区里存储的一般是常量字符串。相同的常量字符串在常量存储区只保留一份拷贝,因此下面这个例子结果为T就容易理解了。

const char* st1="Astring";

const char* st2="Astring";

cout<<(st1==st2?‘T’:‘F’);

    全局/静态存储区默认情况下是一片初始为0x00的区域,因此全局/静态变量如果没被初始化,它将默认被置为0;常量存储区是一片只读区域,不允许程序对里面任何一个bit做删改。

    接下来,用一个简单的例子进一步说明。例子代码如mregions.cpp所示。

  1. /*++ mregions.cpp 
  2.  * Purpose:区分各种类型数据的内存区域 
  3.  * Created:2011-08-19 
  4.  * Author:winterlost 
  5.  --*/  
  6. int a=30;  
  7. void main()  
  8. {  
  9.     //a,b,c在全局区  
  10.     static int b=10;  
  11.     static int c=20;  
  12.     int d=40;  
  13.     //常量区  
  14.     const char* str1="Astring";  
  15.     const char* str2="Newstring";  
  16.     //堆  
  17.     int* p=new int[3];  
  18.     p[0]=a;  
  19.     p[1]=b;  
  20.     p[2]=c;  
  21.     delete []p;  
  22. }  

变量a是全局变量,b和c是静态变量,它们被存储在全局/静态存储区;"Astring"和"Newstring"是常量字符串,它们被存储在常量存储区;new int[3]在堆上开辟了可容纳3个整型数据的空间,delete []p释放了堆上的空间。栈并没有特别的举例,因为程序处处都用到栈。

接下来查看mregion.cpp对应的Disassembly,截取重要片段如下所示。

  1.      9:     //a,b,c在全局区  
  2.     10:     static int b=10;  
  3.     11:     static int c=20;  
  4.     12:     int d=40;  
  5. 001913AE  mov         dword ptr [d],28h    
  6.     13:     //常量区  
  7.     14:     const char* str1="Astring";  
  8. 001913B5  mov         dword ptr [str1],offset string "Astring" (195748h)    
  9.     15:     const char* str2="Newstring";  
  10. 001913BC  mov         dword ptr [str2],offset string "Newstring" (19573Ch)    
  11.     16:     //堆  
  12.     17:     int* p=new int[3];  
  13. 001913C3  push        0Ch    
  14. 001913C5  call        operator new (191177h)    
  15. 001913CA  add         esp,4    
  16. 001913CD  mov         dword ptr [ebp-104h],eax    
  17. 001913D3  mov         eax,dword ptr [ebp-104h]    
  18. 001913D9  mov         dword ptr [p],eax    
  19.     18:     p[0]=a;  
  20. 001913DC  mov         eax,dword ptr [p]    
  21. 001913DF  mov         ecx,dword ptr [a (197000h)]    
  22. 001913E5  mov         dword ptr [eax],ecx    
  23.     19:     p[1]=b;  
  24. 001913E7  mov         eax,dword ptr [p]    
  25. 001913EA  mov         ecx,dword ptr [b (197004h)]    
  26. 001913F0  mov         dword ptr [eax+4],ecx    
  27.     20:     p[2]=c;  
  28. 001913F3  mov         eax,dword ptr [p]    
  29. 001913F6  mov         ecx,dword ptr [c (197008h)]    
  30. 001913FC  mov         dword ptr [eax+8],ecx    
  31.     21:     delete []p;  
  32. 001913FF  mov         eax,dword ptr [p]    
  33. 00191402  mov         dword ptr [ebp-0F8h],eax    
  34. 00191408  mov         ecx,dword ptr [ebp-0F8h]    
  35. 0019140E  push        ecx    
  36. 0019140F  call        operator delete (191082h)    
  37. 00191414  add         esp,4    

注意行号18-20这三句对应的反汇编码,不难发现变量a/b/c各自对应的内存地址为ds:197000h/197004h/197008h,这是它们各自在全局/静态数据区占用的内存空间地址。观察14、15行代码对应的反汇编码,可以发现"Astring"和"Newstring"各自在常量存储区中的地址为195748h/19573Ch。从17行代码对应的反汇编码中看不出new得到的堆首地址,但在vs2010中让鼠标悬停在eax上,可查看eax的值为00101ae0h。而栈的地址,都以寄存器esp为基准浮动。在最终生成的可执行文件中,看不到变量名a,b,c,str,只有它们在编译时就被转换成的地址,用esp表示。

注意各类数据内存地址所在的区段:不同类型的变量处在不同区段,相同类型的变量处在相同区段并且地址连续。它们的地址被归纳在表1中。

                  表1 各种类型变量在内存中的地址


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值