前言
对于内存管理的学习,我们在C语言的学习中应该已经有所涉猎了,这篇文章将对C++的内存管理进行一定的讲解
1.C/C++的内存分布
- 1.1 小检测
在正式学习之前,我们可以先做一下这个小测验,检验一下之前的学习成果还有多少

答案(点击箭头打开)
C C C A A A A A D A B
前面几个应该都不难,这里我将对后六个进行一定的解释
1.char2 *char2:
首先char2是栈上的一个数组,*char2指向的是首元素,而首元素a也是存在于栈上的,所以选AA
2.pchar3 *pchar3:
pachar3是栈上的一个指针变量,而*pachar3则是首元素,首元素a是常量,处于常量段上,所以选AD
3.ptr1 *ptr1
ptr1是栈上的一个指针变量,而*ptr1指向的是malloc新开的空间,所以处于堆上,所以选AB
这里怕大家不能够完全理解,再给出一张图:

- 1.2 分类
栈:局部变量
堆:新开的数据,动态申请的数据(如malloc)
数据段(静态区):全局变量
代码段(常量区):可执行的代码,常量
- 1.3 分布图像

2.C语言中内存的管理方式
- 1.1 申请空间的方式
对于C语言中申请空间有这三个主要的函数,malloc,realloc,calloc,这里我们主要讲一讲它们三者的区别
1.malloc
主要是用于原始初始化,申请的是原始字节,大多数需要用sizeof搭配计算,但无法初始化内容
例:int *arr = (int *)malloc(10 * sizeof(int)); // 分配40字节,内容未初始化
2.realloc
主要用来重新初始化,不能够初始化未初始化的指针,可以重新申请大小,并且可以保留原有数据
例:int *arr = (int *)malloc(10 * sizeof(int));
arr = (int *)realloc(arr, 20 * sizeof(int)); // 扩展到20个int,前10个保留
3.calloc
与malloc用法功能基本相同,但是会将所有内容初始化为0
例:int *arr = (int *)calloc(10, sizeof(int)); // 分配10个int,全部初始化为0
- 1.2 释放空间的方式
在C语言中主要使用free函数来释放空间
注意事项:
1.只能释放在堆上的数据,也就是动态申请的空间
2.填的是首元素的地址,否则无法释放
3.不能够重复释放
4.对于数组,要依次释放,否则会造成内存泄漏
5.free完后,立马指向NULL
例:
int p = malloc(size); if (p == NULL)
// … 使用 …
free(p);
p = NULL; // 防御性编程
3.C++管理内存
- 3.1 new delete的用法
1.基础用法:
int* p1 = new int;
delete p1;
int* p2 = new int[10];
delete[] p2;
要注意的是,如果new的空间不止一个,delete时要加上[]
2.初始化用法
int* p1 = new int(0);
delete p1;
int* p2 = new int[10]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
delete[] p2;
如果只new了一个空间,后面接(),在括号内填上想初始化的数据就可以了
如果new了多个空间,那么后面就要接{}进行初始化,在括号内依次填想初始化的数据就可以了
3.自定义初始化
class A
{
public:
A(const int& a, const int& b):
_a(a), _b(b)
{}
private:
int _a;
int _b;
};
int main()
{
A* a = new A[3]{ {1, 3}, {2, 4}, {3, 5} };
return 0;
}
这里new的空间是对于自定义类型而言的,但是原理和用法都是相同的,要注意的是,这里运用了隐式类型转换,当然也可以先初始化对象再拷贝构造,同样的利用匿名构造也是可以的
- 3.2 new失败(抛异常)
虽然对于编译来说,只要不要过大基本上new不会失败,但是这里还是给出检查new是否抛异常的方法
抛异常是通过try+catch的方法来进行检查是否有new成功,这里有一个例子
void deep_function() {
std::cout << "我要申请超大内存..." << std::endl;
// 申请 1TB 内存,必然失败
int* p = new int[1000000000000];
// ❗ 如果 new 失败,这里不会执行!直接跳到 catch
std::cout << "申请成功,不会打印这行" << std::endl;
*p = 10;
}
void middle_function() {
std::cout << "中间层:准备调用 deep_function" << std::endl;
deep_function(); // 异常从这里抛出来,middle_function 也不处理
std::cout << "中间层:调用完成,不会打印这行" << std::endl;
}
int main() {
try {
std::cout << "main:开始" << std::endl;
middle_function(); // 异常一路抛到 main
std::cout << "main:调用完成,不会打印这行" << std::endl;
}
catch (std::bad_alloc& e) { // ✅ 在这里捕获!
std::cout << "main:捕获到异常!原因:" << e.what() << std::endl;
}
std::cout << "main:程序继续运行,没有崩溃" << std::endl;
return 0;
}
解释一下这个例子就是,因为申请1TB内存肯定会失败,因为try的原因,这里会直接跳过到catch中间的所有过程,然后到执行catch中的代码,通过e.what()打印出抛异常原因
对于try和catch的运行逻辑,这里给出一张图片吗:

我们再将try和catch的作用分开解析一下
try的作用:
划定需要监控的代码范围,只要有一行的代码中出现了抛异常,就会跳到catch
catch的作用:
按异常类型匹配并处理,只有出现抛异常时才会执行其中的代码,否则就不会执行
4.new和delete的底层原理
- 4.1 内置类型
对于内置类型来说,new和malloc,delete和free是基本完全相似的,不同的是new失败时会返回抛异常,但是malloc失败时返回的是NULL
- 4.2 自定义类型
对于new:
先是利用“operator new”申请空间,然后再利用自定义类型的构造函数,从而完成对象的构造
对于delete:
先是调用自定义类型的析构函数进行空间的清理,然后再调用“operator delete”释放对象
5.new/malloc和delete/free的区别
- 5.1 共同点
都是在堆上去申请空间,并且需要用户去手动释放
- 5.2 不同点
1. malloc和free是函数,new和delete是操作符
2. malloc申请的空间不会初始化,new可以初始化
3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,
如果是多个对象,[]中指定对象个数即可
4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需
要捕获异常
6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new
在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成
空间中资源的清理释放
6.总结
在写之前,本来是感觉写不了很多的,但越是往下深挖就会发现之前根本没有学的很扎实,在写这篇文章的过程中我也复习和学到了很多新东西,希望这篇文章也能够帮到正在学习C++的你

评论(0)
暂无评论