关于C++的存储方案(作用域、链接性、持续性)在另一篇博文:引入名称空间之前C++变量和函数的存储方案及特点
动态内存分配(Dynamic Memory Allocation)
所谓动态内存分配就是在程序执行过程中动态地分配或回收存储空间的内存分配方法。动态内存分配由系统根据程序的需要即时分配内存,与之相对的静态内存分配是编译器在程序执行前为程序中的各种变量分配好存储空间。
通常,编译器使用四种独立的内存:一块用于存放代码,一块用于静态变量(可能再细分),一块用于自动变量,另一块用于动态存储。C++的动态内存由运算符new和delete控制,而不是由作用域和链接性规则控制。因此,可以在一个函数中分配动态内存,而在另一个函数中将其释放。动态内存不是LIFO(Last In First Out),其分配和释放顺序要根据new和delete在何时以何种方式被使用。
虽然存储方案概念不适用于动态内存,但适用于用来跟踪动态内存的自动和静态指针变量。如在一个函数内做如下定义:int* p = new int[10];由new分配的10个int类型大小的内存将一直保留在内存中,直到使用delete运算符将其释放。但当包含该声明的代码块执行完毕时,p指针将消失。简而言之,通过new分配的内存不遵循作用域和链接性规则,但指向该内存的指针遵循。
new运算符的使用
1.使用new运算符初始化
对于简单的变量类型,可在类型名后面加上初始值,并将其用括号括起:
nt* p = new int (3);
这种语法也适用于有合适构造函数的类。
对于复合类型,如结构体、数组等,需要使用列表初始化:
struct a {int x, int y, char c};
a* p = new a {3, 24, 'b'};
列表初始化也适用于单值变量(C++11)。
2.new:运算符、函数
运算符new和new[]分别调用如下分配函数:
void* operator new (std::size_t);
void* operator new[] (std::size_t);
同样,运算符delete和delete[]也有对应的释放函数:
void* operator delete (void*);
void* operator delete[] (void*);
std::size_t是一个typedef,对应于合适的整型。new和new[]语句可以转换为另一个语句:
int* p = new int; → int* p = new (sizeof(int));
int* p = new int[n]; → int* p = new (n*sizeof(int));
new运算符也可以包含初始值,因此,使用new运算符时,可能不仅仅是调用new()函数。
3.定位new运算符
通常,new负责在堆中找到一个能够满足要求的内存块。new运算符还有另一种变体,被称为定位(placement)new运算符,使用它能够指定使用的位置。定位new运算符包含在头文件new中。
下面代码展示了定位new运算符的使用规则:
#include <iostream>
#include <new>
using namespace std;
struct demo
{
int x;
int y;
char c;
};
char buffer1[50];
char buffer2[200];
int main (void)
{
demo *p1, *p2;
int *p3, *p4;
p1 = new demo;
p3 = new int[20];
p2 = new (buffer1) demo;
p4 = new (buffer2) int[20];
cout << "p1: " << p1 << endl;
cout << "p2: " << p2 << endl;
cout << "p3: " << p3 << endl;
cout << "p4: " << p4 << endl;
delete p1;
delete p3;
// delete p2;
// delete p4;
return 0;
}
由上面代码可以看到,定位new运算符需要提供地址参数。其实定位new运算符的功能就是复制一段内存中的内容到另一段内存中去。
特别需要注意的是,在这段代码中定位new运算符不能使用delete运算符释放内存(执行注释掉的代码会引发运行阶段错误),因为数组buffer1和buffer2指定的内存时静态内存,而delete运算符只能释放常规new运算符分配的堆内存。这意味着如果buffer1和buffer2的内存是由常规new运算符分配的那么delete运算符可以正常使用。
同常规new运算符一样,定位new运算符也有另一种形式(多了一个地址参数):
int *p1 = new int;
int *p2 = new (buffer1) int; //等价于int *p2 = new (sizeof(int), buffer1);
int *p3 = new (buffer2) int[n]; //等价于int *p3 = new (n*sizeof(int), buffer 2);
与常规new运算符不同的是,定位new运算符只能重载,不能替换。
4.new失败时
使用new运算符分配动态内存可能找不到请求的内存量。C++原先的做法是让new返回空指针,但现在将引发异常std::bad_alloc,然后程序终止(异常提示因系统而异)。如果想在new失败时返回空指针,应使用另一类new:
int *p = new (std::nothrow) int;
int *p = new (std::nowthrow) int[n];