目录
[1. C/C++内存分布图](#1. C/C++内存分布图)
[2. C++ 内存管理方式](#2. C++ 内存管理方式)
[new 和delete操作自定义类型](#new 和delete操作自定义类型)
[3. operator new与operator delete函数](#3. operator new与operator delete函数)
[4. new和delete实现原理](#4. new和delete实现原理)
[一.new 的实现原理](#一.new 的实现原理)
[5. malloc/free和new/delete的区别](#5. malloc/free和new/delete的区别)
[1. 手动分配内存后,忘记释放](#1. 手动分配内存后,忘记释放)
[2. 指针重赋值,丢失内存引用](#2. 指针重赋值,丢失内存引用)
[3. 异常导致释放代码未执行](#3. 异常导致释放代码未执行)
[4. 全局 / 静态指针指向的堆内存未释放](#4. 全局 / 静态指针指向的堆内存未释放)
1. C/C++内存分布图

相关说明:
- 栈又叫堆栈------非静态局部变量/函数参数/返回值等,是向下增长。
- 内存映射段是高效的 I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口,创建共享共享内存,做进程间通信。
- 堆用于程序运行是进行动态内存分配,堆是可以向上增长的。
- 数据段 -- 存储全局数据和静态数据。
- 代码段 --可执行的代码/只读常量。
相关应用:

选择:
选项 : A . 栈 B . 堆 C . 数据段 ( 静态区 ) D . 代码段 ( 常量区 )
globalVar 在哪里? C__
staticGlobalVar 在哪里? C__
staticVar 在哪里? C__
localVar 在哪里? A
num1 在哪里? A
char2 在哪里? A
* char2 在哪里?A_ char2[]是把字符串 "复制" 到了栈上,相当于自己在栈上存了一份。
pChar3 在哪里? A__
* pChar3 在哪里?D const char* pChar3 只是在栈上存了一个 "地址",这个地址指向常量区里 已经存在的字符串。
ptr1 在哪里? A__
* ptr1 在哪里? B__
C中动态内存方式
以前C语言部分有: C语言动态内存/管理
cpp
#include<iostream>
using namespace std;
int main()
{
int* p1 = (int*)malloc(4 * sizeof(int));
free(p1);
int* p2 = (int*)calloc(4, sizeof(int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
cout << p2 << endl;//这里需要释放p2吗?
cout << p3 << endl;
free(p3);
}
问题1 :这里P2 需要释放吗?
不需要,手动 free(p2) 反而会导致程序出现未定义行为(比如崩溃、内存错乱)。
realloc原地扩容(原内存块后方有足够空间):这时指向同一地址realloc分配新内存块(原内存块后方空间不足):此时realloc会在堆中找一块足够大的新空闲内存块,把p2指向的原数据完整复制到新块中,自动释放p2指向的旧内存块 ,然后返回新块的地址给p3。p2变成了「野指针」(指向的内存已被系统回收),再手动free(p2),就是对「已经释放的无效内存」进行释放,同样是未定义行为。
问题2:malloc/calloc/realloc 区别
- malloc:内存不初始化,分配
size字节的连续堆内存,返回首地址;分配失败返回NULL,适用于仅需分配内存、无需初始化为 0 的场景 - calloc:初始化为 0,分配
num个大小为size的连续内存(总大小num*size),并将所有字节初始化为 0 - realloc:不初始化新扩展部分,调整已分配内存块的大小:若原块后方有足够空间则原地扩容;否则分配新块、复制原数据并释放原块,返回新地址;
ptr为NULL时等价于malloc,new_size为 0 时等价于free。(void* realloc(void* ptr, size_t new_size))
关键注意事项
- 指针类型处理 :
malloc/calloc/realloc均返回void*指针,C++ 中需强制转换为目标类型指针(C 可隐式转换)。 - 地址有效性 :
realloc不保证内存地址不变,必须用返回的新指针接收,原指针可能失效(尤其是异地扩容后)。 - 内存安全
- 使用
malloc时,建议手动初始化内存,避免旧数据引发的问题。
- 使用
realloc扩容后,原指针可能被自动释放,不能重复free原指针。
2. C++ 内存管理方式
cpp
void Test(){
//动态申请一个int类型的空间
int* ptr4 = new int;
//动态申请一个int类型的空间并初始化为10
int* ptr5 = new int(10);
//动态申请10个int类型的空间
int* ptr6 = new int[3];
int* ptr7 = new int[10] {1, 2, 3, 4};
delete ptr4;
delete ptr5;
delete[]ptr6;
delete[] ptr7;
}


总结:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[ ]和delete[ ]
new 和delete操作自定义类型
cpp
#include<iostream>
using namespace std;
class A
{
public:
A(int a)
:_a(a)
{
cout << "A()" << this << endl;
}
~A()
{
cout << "~A()" <<this<< endl;
}
private:
int _a;
};
int main()
{
// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间
//还会调用构造函数和析构函数
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1);
free(p1);
delete p2;
// 内置类型是几乎是一样的
int* p3 = (int*)malloc(sizeof(int)); // C
int* p4 = new int;
free(p3);
delete p4;
A* p5 = (A*)malloc(sizeof(A) * 10);
A a1(1), a2(2), a3(3);
A* p6 = new A[3]{a1,a2,a3};
free(p5);
delete[] p6;
return 0;
}

可以直观的看出在申请自定义类型的空间时, new 会调用构造函数, delete 会调用析构函数,而 malloc 与 free 不会 。
最后那6个析构是:「栈上 3 个对象」+「堆上 3 个对象」

3种初始化数组
这是3种初始化堆数组的写,第二种效率最高,但依靠A类支持int类型构造,若是有explicit,则不支持第二种隐式类型转换,这3种:
- 堆数组 3 次拷贝构造 + 栈对象 3 次构造 + 堆数组 3 次析构 + 栈对象 3 次析构
- 堆数组 3 次构造 + 堆数组 3 次析构
- 临时对象 3 次构造 + 堆数组 3 次拷贝构造 + 临时对象 3 次析构 + 堆数组 3 次析构
3. operator new与operator delete****函数
new 和 delete 是用户进行 动态内存申请和释放的操作符 , operator new 和 operator delete 是
系统提供的 全局函数 , new 在底层调用 operator new 全局函数来申请空间, delete 在底层通过
operator delete 全局函数来释放空间。
cpp
/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,
尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void* p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void* pUserData)
{
_CrtMemBlockHeader* pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK); /* block other threads */
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg(pUserData, pHead->nBlockUse);
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
//通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果malloc申请空间
//成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。
//operator delete 最终是通过free来释放空间的。
三个误区:
- 误区 :
new就是operator new。→ 错误。new是操作符,包含 "分配内存 + 构造对象" 两步;operator new只负责分配内存。 - 误区 :
operator new只能用malloc实现。→ 错误。它可以用任何内存分配方式实现,比如内存池、mmap等。 - 误区 :类内重载的
operator new会影响全局。→ 错误。类内重载的仅对该类生效,全局的operator new依然存在
4. new和delete实现原理
一.new 的实现原理
new 是 C++ 中动态创建对象的关键字,其执行过程分为内存分配 和对象初始化 两个核心阶段,底层依赖 operator new 函数和构造函数。
- 单个对象的
new(T* ptr = new T(args);)
执行流程:
(1) 调用 operator new 分配内存
底层默认通过 malloc 分配指定大小(sizeof(T))的堆内存。
若分配失败,默认会抛出 std::bad_alloc 异常;若使用 nothrow 版本 (new(std::nothrow)T),则返回 nullptr。
(2) 调用构造函数初始化对象
在已分配的内存上,调用 T 的构造函数(传入 args),完成对象的初始化。
(3)返回对象指针
返回指向初始化完成的对象的指针。
2.数组的 new[](T* ptr = new T[N]{...};
- 调用
operator new[]分配内存- 分配的内存大小 =
N * sizeof(T) + 额外开销(通常是 4 字节,用来存储数组的元素数量N)。 - 额外开销的作用是:后续
delete[]时,需要知道要调用多少次析构函数。
- 分配的内存大小 =
- 调用 N 次构造函数
- 对数组的每个元素调用构造函数,完成初始化。
- 返回数组首元素指针
- 注意:返回的指针是数组首元素的地址,而实际分配的内存起始地址会比返回指针靠前(因为前面存了元素数量
N)。
- 注意:返回的指针是数组首元素的地址,而实际分配的内存起始地址会比返回指针靠前(因为前面存了元素数量
二.delete的实现原理
delete 是销毁动态对象并释放内存的关键字,执行过程分为对象清理 和内存释放 两个核心阶段,底层依赖析构函数和 operator delete 函数。
- 单个对象的
delete(delete ptr;)
执行流程:
- 调用析构函数清理对象
- 调用
T的析构函数,释放对象持有的资源(如堆内存、文件句柄等)。
- 调用
- 调用
operator delete释放内存- 底层默认通过
free释放之前分配的内存。
- 底层默认通过
- 数组的
delete[](delete[] ptr;)
执行流程:
- 获取数组元素数量
- 从指针
ptr向前偏移,读取new[]时存储的元素数量N。
- 从指针
- 调用 N 次析构函数
- 对数组的每个元素调用析构函数,清理所有对象。
- 调用
operator delete[]释放内存- 释放整个内 存块(包括存储元素数量的额外开销部分)
5. malloc/free和new/delete****的区别
malloc/free 和 new/delete 的
共同点是:都是从堆上申请空间,并且需要用户手动释放。
不同的地方是:
- malloc 和 free 是函数, new 和 delete 是操作符
- malloc 申请的空间不会初始化, new 可以初始化
- malloc 申请空间时,需要手动计算空间大小并传递, new 只需在其后跟上空间的类型即可,
如果是多个对象, [] 中指定对象个数即可 - malloc 的返回值为 void*, 在使用时必须强转, new 不需要,因为 new 后跟的是空间的类型
- malloc 申请空间失败时,返回的是 NULL ,因此使用时必须判空, new 不需要,但是 new 需
要捕获异常 - 申请自定义类型对象时, malloc/free 只会开辟空间,不会调用构造函数与析构函数,而 new
在申请空间后会调用构造函数完成对象的初始化, delete 在释放空间前会调用析构函数完成
空间中资源的清理释放
6.内存泄漏
内存泄漏的核心定义:在堆上分配的内存,使用完毕后既没有被释放,又失去了对这块内存的所有有效引用,导致这块内存永远无法被程序回收和复用,直到程序进程结束,操作系统才会回收它。
- 内存泄漏不会导致程序立即崩溃,它是一种 "慢性疾病"。
- 对于短期运行的小程序(如控制台测试程序),内存泄漏的影响很小(程序结束后系统会回收所有内存)。
- 对于长期运行的程序(如服务器、后台服务、游戏引擎),内存泄漏会持续占用内存,最终导致内存耗尽、程序运行缓慢甚至崩溃,这是必须严格避免的。
1. 手动分配内存后,忘记释放
cpp
#include<iostream>
using namespace std;
class A
{
public:
A(int a)
:_a(a)
{
cout << "A()" << this << endl;
}
~A()
{
cout << "~A()" << this << endl;
}
private:
int _a;
};
// 场景1:new 后忘记 delete
void test1() {
int* ptr = new int(10); // 堆上分配内存
// 业务逻辑...
return; // 忘记执行 delete ptr; ,内存泄漏
}
// 场景2:new[] 后忘记 delete[](自定义类型更危险,还会漏析构)
void test2() {
A* arr = new A[3]{1,2,3}; // 堆上分配数组
// 业务逻辑...
return; // 忘记执行 delete[] arr; ,内存泄漏+对象未析构
}
// 场景3:malloc 后忘记 free
void test3() {
char* buf = (char*)malloc(1024); // 堆上分配内存
// 业务逻辑...
return; // 忘记执行 free(buf); ,内存泄漏
}
2. 指针重赋值,丢失内存引用
指针被重新赋值后,原来指向的堆内存失去了唯一引用,变成 "无人认领" 的内存,无法释放。
cpp
void test4() {
int* ptr = new int(10); // 第一次分配内存,地址为 0x1000
ptr = new int(20); // 指针重赋值,指向新地址 0x2000
// 此时,原地址 0x1000 的内存既没有被释放,也无法通过 ptr 访问,内存泄漏
delete ptr; // 仅释放了 0x2000 的内存,0x1000 的内存永远丢失
}
3. 异常导致释放代码未执行
cpp
void func() {
throw std::runtime_error("未知异常"); // 抛出异常
}
void test5() {
int* ptr = new int(10);
func(); // 抛出异常,后续代码无法执行
delete ptr; // 这行代码永远不会被执行,内存泄漏
}
4. 全局 / 静态指针指向的堆内存未释放
cpp
int* g_ptr = nullptr;
void init() {
g_ptr = new int(100); // 全局指针分配堆内存
}
// 程序结束前未调用该函数,内存泄漏
void destroy() {
delete g_ptr;
g_ptr = nullptr;
}
规避最基础就是遵循 "谁分配,谁释放" 原则,统一内存管理策略