参考
项目 | 描述 |
---|---|
精通C++ (第九版) | 托尼·加迪斯、朱迪·沃尔特斯、戈德弗雷·穆甘达 (著) / 黄刚 等 (译) |
搜索引擎 | Bing |
描述
项目 | 描述 |
---|---|
操作系统 | Windows 10 专业版(64 位) |
C++ 编译器 | gcc version 8.1.0 (x86_64-win32-seh-rev0, Built by MinGW-W64 project) |
动态内存分配
在某些时候,你并不能在程序运行前便知道程序需要多少的内存空间,你需要在程序运行时就这个问题做出选择。
举个栗子
为了实现某一功能,你需要用到 C++ 中的数组。数组中所需要存放的数据的个数尚不明确,为了创建一个合适的数组(尽可能的存放所有的数据,但不创建过大的数组,避免浪费内存),你对各种因素进行了分析,选择创建一个能够包含一百个元素的数组。但好景不长,风云突变,你又选择创建了能存放两百个元素的数组。但…
如果在上面这个例子中,我们能够在运行时创建合适大小的数组将省去不少的麻烦。
new 运算符与 delete 运算符
在 C++ 中,要实现动态内存分配,需要用到 new 运算符及 delete 运算符。
new 运算符
在程序运行时,通过使用 new 运算符你将能够向计算机请求从一片特殊的内存区域,即堆内存中请求合适的内存空间。如果堆内存中尚有可以利用且存在程序所要求的一定大小的内存空间,那么计算机将会将分配给程序的内存空间的首地址发送给程序,程序接受该地址并通过该地址向该地址所对应的内存空间进行数据的存取。
new 运算符的操作数是一个已在程序中定义的抽象数据类型,计算机将通过提供的抽象数据类型分配大小合适的内存空间。
#include <iostream>
#include <string>
using namespace std;
int main(){
// 声明一个指针用于存放计算机返回的内存地址
int* ptr;
// 通过 new 运算符向计算机申请能够存放
// int 数据类型的内存空间。
ptr = new int;
// 通过指针初始化数据
*ptr = 9;
cout << *ptr << endl;
system("pause");
}
执行效果
9
请按任意键继续. . .
注:
使用 new 运算符请求内存空间的过程中,可能会出现由于请求的内存空间过大等情形造成请求失败。请求失败,new 运算符将抛出异常,你需要对该异常进行捕获并做出适当的处理,防止程序由此而崩溃。在某些情况下,请求失败并不会导致 C++ 抛出错误,使用 new 的请求结果将为地址 0 。
delete 运算符
计算机分配给程序的堆内存是有限的,如果程序频繁的提出请求而不归还堆内存,那么堆内存将因此而耗尽,导致后续的内存请求无法得到期望的响应。
使用 delete 运算符,你将能够释放向计算机请求的堆内存空间。对此,请参考如下示例:
#include <iostream>
#include <string>
using namespace std;
int main(){
int* ptr;
ptr = new int;
*ptr = 9;
cout << *ptr << endl;
cout << (long long)ptr << endl;
// 使用 delete 运算符释放请求的堆内存空间
delete ptr;
cout << *ptr << endl;
cout << (long long)ptr << endl;
system("pause");
}
执行效果
使用 delete 运算符释放计算机分配的堆内存空间后,指针中存放的地址不会发生变化,而指针指向的内存空间中存放的数据已被替换。
9
16465456
39724208
16465456
请按任意键继续. . .
注:
- 如果指针指向存放于堆内存中的数组,那么释放该部分内存空间时,你需要在 delete 运算符于指针间放置一对中括号。对此,请参考如下示例:
#include <iostream>
#include <string>
using namespace std;
int main(){
int* arr;
arr = new int[4];
// 通过 delete 运算符释放计算机分配的堆内存空间
delete [] arr;
system("pause");
}
- delete 运算符不可用于释放非通过 new 运算符申请的内存空间。否则,C++ 将抛出错误(释放空指针所指向的内存空间除外,稍后将对此进行讲解)。对此,请参考如下示例:
#include <iostream>
#include <string>
using namespace std;
int main(){
int a = 1;
int* aPtr = &a;
// 使用 delete 释放非通
// 过 new 运算符申请的内存空间.
// 该语句将导致 C++ 抛出错误。
delete aPtr;
cout << *aPtr << endl;
system("pause");
}
- delete 运算符对空指针不会起任何作用。对此,请参考如下示例:
#include <iostream>
#include <string>
using namespace std;
int main(){
int* aPtr = 0;
// C++ 并不会因为你通过 delete 运算符
// 释放地址 0 所指向的内存空间。因为,
// delete 运算符不会对空指针所指向的
// 空间进行任何操作。
delete aPtr;
system("pause");
}
空指针与悬空指针
在释放了计算机分配的堆内存空间后,原先指向该内存空间的指针将因此变成悬空指针,也即野指针。悬空指针指向非法内存空间(悬空指针存储着非计算机所分配的非法地址),使用悬空指针将带来潜在的危害。为此,在释放计算机分配的堆内存空间后,因选择使用 0、NULL 及 nullptr 作为指针的值,以使其成为空指针。空指针能避免野指针带来的危害,但你若试图修改空指针所指向的内存空间中的数据,C++ 将抛出错误(空指针指向的内存地址 0 所对应的内存空间往往被操作系统所使用)。
释放内存空间后应将原先指向该内存空间的指针设置为空指针
#include <iostream>
#include <string>
using namespace std;
int main(){
int* ptr;
ptr = new int;
// 使用 delete 运算符释放由计算机分配的堆内存
delete ptr;
// 将 ptr 设置为空指针
ptr = NULL;
system("pause");
}
将悬空指针设置为空指针还有一个好处,那便是能够防止因再次使用 delete 对该指针进行释放空间的操作而引起的错误。对此,请参考如下示例:
#include <iostream>
#include <string>
using namespace std;
int main(){
int* ptr = new int;
delete ptr;
// 由于再次使用 delete 运算符
// 释放内存空间导致 C++ 抛出错误。
delete ptr;
system("pause");
}
多次释放空指针所指向的内存空间
#include <iostream>
#include <string>
using namespace std;
int main(){
int* ptr = 0;
delete ptr;
delete ptr;
delete ptr;
// 程序正常运行,未抛出任何错误。
system("pause");
}
内存泄漏
如果在使用完通过 new 分配的内存块后,忘记通过 delete 将其释放,则称程序中出现了内存泄漏。在程序中止前,泄漏的内存块将处于不可用状态。