1. 动态内存
使用运算符new
分配的内存称为动态内存(dynamic memory,所在内存块称为堆heap或者自由存储free store),其不受作用域和链接性规则的约束,所以可以在一个函数中分配而在另一个函数中回收/释放。程序终止时,new
分配的内存一般被释放,但也不总是成立。所以最好的做法是使用运算符delete
释放由new
分配的内存,二者前后搭配使用。
2. new运算符和函数
new
和delete
与new[]
和delete[]
要对应搭配使用。
分配运算符 | 分配函数 | 回收运算符 | 回收函数 |
---|---|---|---|
new | void * operator new(std::size_t); | delete | void operator delete(void *); |
new[] | void * operator new[](std::size_t); | delete[] | void operator delete[](void *); |
以下代码示例中,两种定义a
和b
的方式等价:
int * a = new int;
int * a = (int *) operator new(sizeof(int)); // equivalent
int * b = new int[10];
int * b = (int *) operator new(10 * sizeof(int)); // equivalent
3. 初始化
除了动态分配内存,new
运算符还可以初始化变量,有两种方式:
- 小括号
()
语法:适用于纯量(scalar)内置数据类型和有适当构造函数的类。 - 大括号
{}
列表初始化:适用于普通结构体或数组。
int * a = new int(1);
int * b = new int[3]{2, 3, 4};
4. 定位new运算符
new
运算符有一个变体,叫做定位new运算符(placement new operator),允许指明特定存储位置(比如int * a = new (buffer) int;
),而常规new
运算符默认使用内存块堆heap。
代码示例1
#include <iostream>
#include <new> // placement new
using namespace std;
const int MAXSIZE = 512; // static global variable (internal linkage)
char buffer[MAXSIZE]; // global & static varibale
int main(int argc, char const *argv[]) {
int n = 5;
cout << "Calling new and placement new the first time:\n";
double * ptr1 = new double[n]; // zero-initialization
double * ptr2 = new (buffer) double[n]; // zero-initialization
cout << "Memory addresses:\n";
cout << "\theap: " << ptr1 << ", static: " << ptr2 << '\n';
// for (int i = 0; i < n; i++) {
// cout << ptr1[i] << " at " << ptr1 + i << "; ";
// cout << ptr2[i] << " at " << ptr2 + i << '\n';
// } // all zeros
for (int i = 0; i < n; i++) {
ptr1[i] = ptr2[i] = 1000 + 20 * i;
}
cout << "Memory content:\n";
for (int i = 0; i < n; i++) {
cout << ptr1[i] << " at " << ptr1 + i << "; ";
cout << ptr2[i] << " at " << ptr2 + i << '\n';
}
cout << '\n';
cout << "Calling new and placement new the second time:\n";
double * ptr3 = new double[n]; // allocate new memory
// ptr4 same address as ptr2, overwrite old data
double * ptr4 = new (buffer) double[n];
for (int i = 0; i < n; i++) {
ptr3[i] = ptr4[i] = 1000 + 40 * i;
}
cout << "Memory content:\n";
for (int i = 0; i < n; i++) {
cout << ptr3[i] << " at " << ptr3 + i << "; ";
cout << ptr4[i] << " at " << ptr4 + i << '\n';
}
cout << '\n';
cout << "Calling new and placement new the third time:\n";
delete[] ptr1; // free ptr1
ptr1 = new double[n]; // reuse memory
ptr2 = new (buffer + n * sizeof(double)) double[n]; // address offset
for (int i = 0; i < n; i++) {
ptr1[i] = ptr2[i] = 1000 + 60 * i;
}
cout << "Memory content:\n";
for (int i = 0; i < n; i++) {
cout << ptr1[i] << " at " << ptr1 + i << "; ";
cout << ptr2[i] << " at " << ptr2 + i << '\n';
}
return 0;
}
输出
Calling new and placement new the first time:
Memory addresses:
heap: 0x7fd483c05950, static: 0x10445e0d0
Memory content:
1000 at 0x7fd483c05950; 1000 at 0x10445e0d0
1020 at 0x7fd483c05958; 1020 at 0x10445e0d8
1040 at 0x7fd483c05960; 1040 at 0x10445e0e0
1060 at 0x7fd483c05968; 1060 at 0x10445e0e8
1080 at 0x7fd483c05970; 1080 at 0x10445e0f0
Calling new and placement new the second time:
Memory content:
1000 at 0x7fd483c05a20; 1000 at 0x10445e0d0
1040 at 0x7fd483c05a28; 1040 at 0x10445e0d8
1080 at 0x7fd483c05a30; 1080 at 0x10445e0e0
1120 at 0x7fd483c05a38; 1120 at 0x10445e0e8
1160 at 0x7fd483c05a40; 1160 at 0x10445e0f0
Calling new and placement new the third time:
Memory content:
1000 at 0x7fd483c05950; 1000 at 0x10445e0f8
1060 at 0x7fd483c05958; 1060 at 0x10445e100
1120 at 0x7fd483c05960; 1120 at 0x10445e108
1180 at 0x7fd483c05968; 1180 at 0x10445e110
1240 at 0x7fd483c05970; 1240 at 0x10445e118
定位new运算符仅仅使用传入的地址分配内存,并不会记录相应内存是否已经被占用,也不会搜索未占用的内存块。所以代码示例中第二次调用定位new运算符时,由于ptr4
与ptr2
使用的地址相同,ptr4
将覆盖原来的数据。而第三次调用定位new运算符时,ptr2
的传入地址偏移了n
个double
型数据的大小。所以从打印出的数据地址可以看出,ptr2
相应数组起始地址紧随ptr4
相应数组末尾元素地址。
另外在上面的代码示例中,不能按照常规做法使用delete
运算符释放定位new运算符所分配的内存,因为buffer
所指内存是静态内存,而delete
只是用于释放由常规new
所分配的动态内存。
改编自C++ Primer Plus 6th Edition by Stephen Prata ↩︎