C++程序存储区域划分
首先了解两个数据结构:栈和队列,栈的特点是先进后出,队列的特点是先进先出;
在C++中,变量和函数通常存储在栈内,比如有以下程序:
int a=0; //GVAR 全局初始化区
int* p1; //bss 全局未初始化区
int main() //text 代码区
{
int b; //stack 栈区变量
char s[]="abc"; //stack 栈区变量
int* p2=NULL; //stack 栈区变量
char* p3="123456"; //123456\0在常量区, p3在stack 栈区
static int c=0; //GVAR 全局初始化区
p1=new int(10); //heap 堆区变量
p2=new int(20); //heap 堆区变量
return 0; //text 代码区
}
顺序观察变量的地址:
&b 0x00aff968
s 0x00aff95c // 数组名也是地址
&p2 0x00aff950
可见,顺序越靠前的变量地址值越高,这是因为变量被逐个存入栈中,数据越靠近栈底,地址值越高;栈区域允许改变对象的值,在常量区的对象不允许改变值;
注意到在main函数外存在变量a,p1
,变量a,p1
不在栈区域,处在全局变量区域;在函数外或者类外定义的变量被称为全局变量(全局变量体现了其在函数内部或者类内部也可以看到它);根据其是否进行了初始化,变量会被细分到初始化区GVAR和未初始化区bss;
在函数内使用static
定义变量时,该变量不会在栈区域分配,而是在全局变量区分配;
执行p1=new int(10)
和p2=new int(20)
后,变量p1,p2
的值为:
p1 0x00f6c160
p2 0x00f6c190
在程序中,new int(10)
比new int(20)
先执行,其地址不像栈的分布,p1
的值小于p2
,这是因为关键词new
意味着在堆区域分配内存;
形象地,代码和数据的存储如下:
- 栈空间的资源分配和回收由系统自动完成,堆空间的资源分配和回收可以通过开发人员完成(new 和delete);
- 对于BSS未初始化区,一般会默认设为0值;
- 实际上图中少了一个区域,在堆空间和BSS区域中间还存在一个常量区;
- TEXT代码区用于存放程序除了数据以外的逻辑
C++动态分配和回收原则
动态分配资源来自heap(堆),在堆上分配内存时,常用new
关键字,与new
相对应的是delete
;动态分配内存会带来不确定性,在实时性要求较高的场合,最好不要用动态分配,因为动态分配的消耗时间是不确定的。
程序在内存管理中常涉及3个操作:
- 分配某个大小的内存块;
- 释放一个之前分配的内存块;
- 垃圾收集:寻找不再使用的内存并予以释放;
C++内部没有垃圾收集机制,需要开发者灵活掌握内存的管理;垃圾回收机制看似友好,但间隔性地释放空间会带来碎片化的内存,过多的碎片在内存中会阻碍一次性分配连续的大容量空间。
RAII(Resource Acquisition Is Initialization)是C++特有的资源管理方式,RAII依托于栈和析构函数对所有资源进行管理;RAII有较成熟的智能指针:std::auto_ptr
C++中几种变量的对比
栈和堆中的变量对比:
- 1.作用域:栈区域变量的作用域限于函数体内(即
{...}
),而堆区域变量作用于整个程序,由关键字new
开始,由delete
结束; - 2.编译期间大小确定:栈区变量大小的范围在编译期间就已经确定好,但堆区变量大小的范围需要在运行期间确定;
- 3.空间范围对比:对于栈区,Windows系统默认栈大小为1M,Linux默认栈大小为8M或10M(通过
ulimit -s
查看),对于堆区,所有系统的堆空间上限接近内存(虚拟内存)的总大小;开发中,不要将大量数据在栈空间操作,容易引起栈的溢出; - 4.内存分配方式:栈区内存分配的地址由高到低,堆区内存分配的地址则是由低到高;
- 5.两个区的变量均可变;
全局存储区和常量区的对比:
- 1.存储内容:全局存储区保存全局变量和静态变量,常量区保存常量;
- 2.全局变量,静态变量和常量在编译期间就确定;
- 3.全局区的变量可变,常量区不可变;
内存泄漏
内存泄漏(Memory Leak)是指:程序中已经动态分配的堆内存由于未释放,造成系统内存的浪费,导致程序运行速度变慢甚至引起崩溃;
内存泄漏发生原因和排查方式:
- 内存泄漏主要发生在堆内存的分配,在配置内存后,指向该内存的指针丢失了,这块内存就无法归还给系统;
- 因为内存泄漏属于程序运行中的问题,无法通过编译识别,所以需要在运行过程中进行诊断;
在堆区申请内存后,适当时机需要记得释放,否则会引起内存泄漏,释放方式如下:
//假设有以下资源
int* p2=NULL; //stack 栈区变量
p2=new int(20); //heap 堆区变量
char* p4=new char[7];
//释放堆区分配的变量
if(p2 != NULL)
{
delete p2;
p2=NULL;
}
if(p2 != NULL)
{
delete[] p4;
p4=NULL;
}