目录
一、C/C++的内存分布
二、new与delete操作符
1.new/delete 的使用
2.new申请失败抛异常
3.new/delete操作内置类型
4.new/delete 操作自定义类型
三、operator new与operator delete函数
四、new和delete的实现原理
1.对于内置类型
2.对于自定义类型
①new的实现原理
②delete的实现原理
③new T[n] 的实现原理
④delete[] 的实现原理
五、定位 new 表达式(placement new)
六、面试题
1. malloc/free和new/delete的区别
2. 内存泄漏
①内存泄漏的体现
②内存泄漏分类
一、C/C++的内存分布
C/C++在内存分布这块是相同的
注:
栈由高地址向低地址增加
堆于数据段的末端向高地址增加
数据段通常包括三个部分:BSS、已初始化的数据段和只读数据段。BBS(未初始化数据段)是一块未初始化的内存区域(例如:static int i 未指定值会被分配至数据段),用于存储全局变量和静态变量,它的内容在程序启动时被设置为0或者为空。在可执行文件中,BSS段通常表示为一个有固定大小的区块,并且在程序加载时会被分配对应的内存空间。
二、new与delete操作符
C语言内存管理方式在C++中可以继续使用,但是C++中new和delete运算符还可提供一些高级功能,例如支持类和对象的构造和析构函数。
new/delete 不适用于realloc的内存分配。
1.new/delete 的使用
【new】
分配一个新的内存块new type;
分配动态分配数组
new type[size];
【delete】
——只能释放由new创建的内存块
删除单个分配的内存块
delete pointer_variable;
删除动态分配的数组(分配的内存块)
delete[] pointer_variable;
2.new申请失败抛异常
在 C++ 中,使用 new 运算符申请内存时,如果内存不足,会抛出 std::bad_alloc 异常。因此,在使用 new 运算符申请内存时,为了避免程序崩溃,我们需要使用 try-catch 块来捕获可能抛出的异常。具体来说,可以在申请内存的语句前面加上 try,然后在 catch 块中处理异常,如打印错误信息、释放之前申请的内存等,以确保程序可以正常运行。
以下是一个示例:
try {
int* ptr = new int[100];
// 申请内存成功,进行后续操作
} catch (const std::bad_alloc& e) {
std::cerr << "Memory allocation failed: " << e.what() << std::endl;
// 处理异常,如打印错误信息或释放之前申请的内存
}
需要注意的是,即使你的程序没有显式使用 try-catch 块来捕获 std::bad_alloc 异常,在发生内存不足时,操作系统也会自动向程序抛出 SIGSEGV 信号,导致程序崩溃。因此,为了保证程序的健壮性,建议在使用 new 运算符时始终使用 try-catch 块来捕获可能的异常。
3.new/delete操作内置类型
代码如下:
int* p1 = new int;
//通过列表初始化,可对分配到的内存直接初始化
int* p2 = new int(9);
int* pa1 = new int[10];
//通过列表初始化,可对分配到的内存直接初始化
int* pa2 = new int[10] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
cout << *p2 << endl;
for (int i = 0; i < 10; ++i)
{
cout << pa2[i] << " ";
}
cout << endl;
delete p1;
delete p2;
delete[] pa1;
delete[] pa2;
输出:
9
0 1 2 3 4 5 6 7 8 9
操作符 new/delete 和函数 malloc/free 针对内置类型没有任何差别,只是用法不一样。
4.new/delete 操作自定义类型
在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,但是,使用malloc和free来分配和释放内存时,不会调用该类型的构造函数和析构函数。
三、operator new与operator delete函数
new 和 delete 是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间, delete 在底层通过operator delete全局函数来释放空间。
实际上 operator new 和 operator delete 的用法跟 malloc 和 free 是完全是一样的功能,都是在堆上申请释放空间,但是失败了处理方式不一样,malloc失败返回NULL,operator new失败以后抛异常。
以下三种方式开辟空间和释放空间的效果是一样的
int* p1 = (int*)malloc(sizeof(int)); //malloc和free free(p1); int* p2 = new int; //new和delete delete p2; int* p3 = (int*)operator new(sizeof(int));//operator new与operator delete operator delete (p3);
四、new和delete的实现原理
1.对于内置类型
如果申请的是内置类型的空间,new 和 malloc,delete 和 free 基本类似,不同的地方是:new/delete 申请和释放的是单个元素的空间,new[] 和 delete[] 申请的是连续空间,而且 new 在申请空间失败时会抛异常,malloc会返回NULL。
2.对于自定义类型
假设自定义类型为T
①new的实现原理
- 调用 operator new 函数申请内存空间。
- 调用类型T的构造函数,初始化对象。
- 返回指向已分配的内存空间的指针。
②delete的实现原理
- 调用类型 T 的析构函数,释放对象占用的资源。
- 调用 operator delete 函数释放内存空间。
③new T[n] 的实现原理
- 调用 operator new[] 函数申请 n 个对象所需的内存空间。
- 对于每个对象,调用类型 T 的构造函数,初始化对象。
- 返回指向第一个对象的指针。
④delete[] 的实现原理
- 对于每个对象,调用类型 T 的析构函数,释放对象占用的资源。
- 调用 operator delete[] 函数释放内存空间。
五、定位 new 表达式(placement new)
定位 new 表达式(placement new)是一种特殊的 new 表达式,它允许我们将对象构造在指定的内存地址上。
使用场景:
这种方式通常用于特定的场景,比如在某些嵌入式系统中需要将对象构造在固定的内存地址上,或者需要管理自己分配的内存池等等。
new (pointer) type (arguments)
其中,pointer 是一个指向已分配的内存块的指针,type 是对象的类型,arguments 是传递给 type 构造函数的参数。
注意事项:
使用定位 new 表达式时,我们必须保证 pointer 指向的内存块已经被正确地分配,并且能够容纳 type 类型的对象。
示例如下:
//...
class MyClass {
public:
MyClass(int val) : m_value(val) {
cout << "Constructing MyClass object with value " << m_value << endl;
}
~MyClass() {
cout << "Destructing MyClass object with value " << m_value << endl;
}
private:
int m_value;
};
//...
// 分配一块内存
char* buffer = new char[sizeof(MyClass)];
// 在分配的内存上构造对象
MyClass* pObject = new (buffer) MyClass(42);
// 手动销毁对象
pObject->~MyClass();
// 释放分配的内存
delete[] buffer;
在这个例子中,我们首先使用 new char[] 表达式分配了一块内存,然后使用定位 new 表达式在这块内存上构造了一个 MyClass 对象。最后,我们手动调用了析构函数来销毁对象,并使用 delete[] 表达式释放了分配的内存。
六、面试题
1. malloc/free和new/delete的区别
共同点:
- 都是从堆上申请空间,并且需要用户手动释放。
不同点:
- malloc和free是函数,new和delete是操作符。
- malloc申请的空间不会初始化,new可以初始化。
- malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型。
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常。
- 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。
2. 内存泄漏
①内存泄漏的体现
代码如下:
void MemoryLeaks()
{
// 1.内存申请了忘记释放
int* p1 = (int*)malloc(sizeof(int));
int* p2 = new int;
// 2.异常安全问题
int* p3 = new int[10];
Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
delete[] p3;
}
注:异常安全性是指程序在抛出异常后仍能保持正确状态的能力。
②内存泄漏分类
C/C++程序中一般我们关心两种方面的内存泄漏:
堆内存泄漏(Heap Leak)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。