C++ -- 内存管理

Simple浙江

内存分配方式

在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。

栈 -- 由编译器自动分配释放

在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
是由编译器在需要时自动分配,不需要时自动清除的变量存储区。通常存放局部变量、函数参数等。

堆 -- 一般由程序员分配释放

就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序(程序员)去控制,一般一个new就要对应一个delete,一个new[]与一个delete[]对应。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。

自由存储区

就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。

全局/静态存储区

全局变量和静态变量被分配到同一块内存中(在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。)

常量存储区

这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。

注意:堆和自由存储区其实不过是同一块区域,new底层实现代码中调用了malloc,new可以看成是malloc智能化的高级版本)

栈和堆的讨论

堆: 操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删 除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样代码 中的delete语句才能正确的释放本内存空间。我们常说的内存泄露,最常见的就是堆泄露(还有资源泄露),它是指程序在运行中出现泄露,如果程序被关闭掉的话,操作系统会帮助释放泄露的内存。

栈: 在函数调用时第一个进栈的主函数中的下一条指令(函数调用语句的下一条可执行语句)的地址然后是函数 的各个参数,在大多数的C编译器中,参数是由右往左入栈,然后是函数中的局部变量。

管理方式:

  • 堆中资源由程序员控制(容易产生memory leak)。
  • 栈资源由编译器自动管理,无需手工控制。

系统响应:

  • 对于堆,应知道系统有一个记录空闲内存地址的链表,当系统收到程序申请时,遍历该链表,寻找第一个空间大于申请空间的堆结点,删除空闲结点链表中的该结点,并将该结点空间分配给程序(大多数系统会在这块内存空间首地址记录本次分配的大小,这样delete才能正确释放本内存空间,另外系统会将多余的部分重新放入空闲链表中)。
  • 对于栈,只要栈的剩余空间大于所申请空间,系统为程序提供内存,否则报异常提示栈溢出。

空间大小:

  • 堆是不连续的内存区域(因为系统是用链表来存储空闲内存地址,自然不是连续的),堆大小受限于计算机系统中有效的虚拟内存(32bit系统理论上是4G),所以堆的空间比较灵活,比较大。
  • 栈是一块连续的内存区域,大小是操作系统预定好的,windows下栈大小是2M(也有是1M,在编译时确定,VC中可设置)。

碎片问题:

  • 对于堆,频繁的new/delete会造成大量碎片,使程序效率降低。
  • 对于栈,它是一个先进后出的队列,进出一一对应,不会产生碎片。

生长方向:

  • 堆向上,向高地址方向增长。
  • 栈向下,向低地址方向增长。

分配方式:

  • 堆都是动态分配(没有静态分配的堆)。
  • 栈有静态分配和动态分配,静态分配由编译器完成(如局部变量分配),动态分配由alloca函数分配,但栈的动态分配的资源由编译器进行释放,无需程序员实现。

分配效率:

  • 堆由C/C++函数库提供,机制很复杂。所以堆的效率比栈低很多。
  • 栈是极其系统提供的数据结构,计算机在底层对栈提供支持,分配专门寄存器存放栈地址,栈操作有专门指令。

“野指针”成因主要有两种:

(1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。

(2)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。

malloc/free与new/delete的区别:

malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。 它们都可用于申请动态内存和释放内存。

对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。

因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。

既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。

如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,结果也会导致程序出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。

动态分配和释放内存

C++ 提供了一种“动态内存分配”机制,使得程序可以在运行期间,根据实际需要,要求操作系统临时分配一片内存空间用于存放数据。此种内存分配是在程序运行中进行的,而不是在编译时就确定的,因此称为“动态内存分配”。

在 C++ 中,通过 new 运算符来实现动态内存分配。
new 运算符的第一种用法:

T \*p = new T;
其中,T 是任意类型名,p 是类型为 T\* 的[指针]


**new 运算符还有第二种用法,用来动态分配一个任意大小的数组:** 

T *p =new T[N];
//其中,T 是任意类型名,p 是类型为 T* 的指针,N 代表“元素个数”,可以是任何值为正整数的表达式,表达式中可以包含变量、函数调用等。这样的语句动态分配出 N × sizeof(T) 个字节的内存空间,这片空间的起始地址被赋值给 p。

程序从操作系统动态分配所得的内存空间在使用完后应该释放,交还操作系统,以便操作系统将这片内存空间分配给其他程序使用。C++ 提供 delete 运算符,用以释放动态分配的内存空间。delete 运算符的基本用法如下:

delete p;

如果是用 new 的第二种用法分配的内存空间,即动态分配了一个数组,那么释放该数组时,应以如下形式使用 delete 运算符:

delete\[\] p;

牢记,用 new 运算符动态分配的内存空间,一定要用 delete 运算符释放。否则,即便程序运行结束,这部分内存空间仍然不会被操作系统收回,从而成为被白白浪费掉的内存垃圾。这种现象也称为“内存泄露”。

阅读 2.7k
10 声望
3 粉丝
0 条评论
10 声望
3 粉丝
宣传栏