也谈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所示。

/*++ mregions.cpp
 * Purpose:区分各种类型数据的内存区域
 * Created:2011-08-19
 * Author:btwsmile
 --*/
int a=30;
void main()
{
	//a,b,c在全局区
	static int b=10;
	static int c=20;
	int d=40;
	//常量区
	const char* str1="Astring";
	const char* str2="Newstring";
	//堆
	int* p=new int[3];
	p[0]=a;
	p[1]=b;
	p[2]=c;
	delete []p;
}

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

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

     9: 	//a,b,c在全局区
    10: 	static int b=10;
    11: 	static int c=20;
    12: 	int d=40;
001913AE  mov         dword ptr [d],28h  
    13: 	//常量区
    14: 	const char* str1="Astring";
001913B5  mov         dword ptr [str1],offset string "Astring" (195748h)  
    15: 	const char* str2="Newstring";
001913BC  mov         dword ptr [str2],offset string "Newstring" (19573Ch)  
    16: 	//堆
    17: 	int* p=new int[3];
001913C3  push        0Ch  
001913C5  call        operator new (191177h)  
001913CA  add         esp,4  
001913CD  mov         dword ptr [ebp-104h],eax  
001913D3  mov         eax,dword ptr [ebp-104h]  
001913D9  mov         dword ptr [p],eax  
    18: 	p[0]=a;
001913DC  mov         eax,dword ptr [p]  
001913DF  mov         ecx,dword ptr [a (197000h)]  
001913E5  mov         dword ptr [eax],ecx  
    19: 	p[1]=b;
001913E7  mov         eax,dword ptr [p]  
001913EA  mov         ecx,dword ptr [b (197004h)]  
001913F0  mov         dword ptr [eax+4],ecx  
    20: 	p[2]=c;
001913F3  mov         eax,dword ptr [p]  
001913F6  mov         ecx,dword ptr [c (197008h)]  
001913FC  mov         dword ptr [eax+8],ecx  
    21: 	delete []p;
001913FF  mov         eax,dword ptr [p]  
00191402  mov         dword ptr [ebp-0F8h],eax  
00191408  mov         ecx,dword ptr [ebp-0F8h]  
0019140E  push        ecx  
0019140F  call        operator delete (191082h)  
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 各种类型变量在内存中的地址

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值