内存问题是 C/C++ 程序员的"职业噩梦":写着写着就崩,调半天才发现是少了一行 delete;改个数组大小,realloc 用错又炸了;看到 placement new 直接跳过。其实,大部分坑都来自同一个根源------你没真正搞清"谁在什么地方,谁负责回收"。这篇文章就是一步步把这件事讲透,从内存布局到动态分配,再到 C++ 特有的内存机制,帮你把脑子里的那张"内存地图"补完整。
目录
[1. C/C++内存分布](#1. C/C++内存分布)
[2. C语言中动态内存管理方式:malloc/calloc/realloc/free](#2. C语言中动态内存管理方式:malloc/calloc/realloc/free)
[3. C++内存管理方式](#3. C++内存管理方式)
[3.1 new/delete操作内置类型](#3.1 new/delete操作内置类型)
[3.2 new和delete操作自定义类型](#3.2 new和delete操作自定义类型)
[4. new和delete操作自定义类型](#4. new和delete操作自定义类型)
[4.1 operator new与operator delete函数](#4.1 operator new与operator delete函数)
[5. new和delete的实现原理](#5. new和delete的实现原理)
[5.1 内置类型](#5.1 内置类型)
[5.2 自定义类型](#5.2 自定义类型)
[6. malloc/free和new/delete的区别](#6. malloc/free和new/delete的区别)
1. C/C++内存分布
先看一段小程序,思考每个变量"住在哪一层楼"(在哪个内存区):
cpp
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = { 1, 2, 3, 4 };
char char2[] = "abcd";
const char* pChar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof(int) * 4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
free(ptr1);
free(ptr3);
}
这些名字分别位于哪里?(单选)
选项:A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
-
globalVar在哪里?____ -
staticGlobalVar在哪里?____ -
staticVar在哪里?____ -
localVar在哪里?____ -
num1在哪里?____ -
char2在哪里?____ -
*char2在哪里?____ -
pChar3在哪里?____ -
*pChar3在哪里?____ -
ptr1在哪里?____ -
*ptr1在哪里?____
答案是:C、C、C、A、A、A、A、A、D、A、B

几个核心概念(配合上图对照):
-
栈(stack) :存放非静态局部变量、函数参数、部分返回现场,向下增长。函数进出由编译器自动管理,速度快。
-
内存映射段(mmap区):高效I/O与共享库装载区域,也可用于共享内存与进程间通信(了解即可)。
-
堆(heap) :程序运行期 动态分配的内存,向上增长,用完要手动释放。
-
数据段(静态区) :放全局变量 与静态变量等。
-
代码段:存放可执行指令与只读常量(如字符串常量)。

2. C语言中动态内存管理方式:malloc/calloc/realloc/free
cpp
void Test()
{
//1.malloc/calloc/realloc的区别是什么?
int* p2 = (int*)calloc(4, sizeof(int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
//这里需要free(p2)吗?
free(p3);
}
是什么:
-
malloc(n)只分配n字节,不初始化; -
calloc(count,size)
分配count*size字节,并初始化为全0; -
realloc(p,newSize)
按新大小调整p指向的块,可能搬家 ,返回新地址;失败则返回NULL且原指针仍有效。 -
free(p)释放由上述接口得到的块。
为什么:满足C中手动管理内存的需求,避免一次性固定大小。
怎么用:
-
realloc成功后旧指针可能失效,以后只用新指针 ;失败时返回NULL,原指针仍可用。 -
上面示例中,
p3已接管那片内存,最终只需free(p3),不再额外free(p2)。
【表格:C动态分配接口对比】
| 接口 | 是否初始化 | 失败返回 | 调整大小 | 典型用法 |
|---|---|---|---|---|
| malloc | 否 | NULL | 否 | 原始分配 |
| calloc | 是(清零) | NULL | 否 | 需要清零 |
| realloc | N/A | NULL(原块保留) | 是 | 动态扩缩容 |
| free | - | - | - | 释放堆内存 |
3. C++内存管理方式
C里的接口在C++中仍可用,但对"对象的构造/析构"就不够友好,所以C++提供了new/delete。
3.1 new/delete操作内置类型
- 规则 :单对象用
new/delete,成片连续 用new[]/delete[],一定要成对匹配。
3.2 new和delete操作自定义类型
cpp
void Test()
{
//动态申请一个int
int* ptr4 = new int;
//动态申请并初始化为10
int* ptr5 = new int(10);
//动态申请连续的int数组(示例为3个)
int* ptr6 = new int[3];
delete ptr4;
delete ptr5;
delete[] ptr6;
}
class A
{
public:
A(int a = 0) : _a(a) { cout << "A():" << this << endl; }
~A() { cout << "~A():" << this << endl; }
private:
int _a;
};
关键差异 :对自定义类型 ,new会调用构造函数 ,delete会调用析构函数 ;而malloc/free只管内存,不管构造与析构。

4. new 和 delete 操作自定义类型
4.1 operator new与operator delete函数
-
new/delete是操作符 ,它们底层会调用 全局函数operator new与operator delete来申请/释放内存。 -
对象构造/析构并不是
operator new/delete干的,是编译器在分配/释放前后插的调用流程。
示例:对比自定义类型与内置类型的分配释放
cpp
int main()
{
//对自定义类型:new/delete除了开空间,还会调构造/析构
A* p1 = (A*)malloc(sizeof(A)); //只分配字节,不调构造
A* p2 = new A(1); //分配并构造
free(p1);
delete p2;
//对内置类型:行为更接近malloc/free
int* p3 = (int*)malloc(sizeof(int));
int* p4 = new int; //可能做默认初始化
free(p3);
delete p4;
//数组版本
A* p5 = (A*)malloc(sizeof(A)*10);
A* p6 = new A[10]; //为每个元素依次调用构造
free(p5);
delete[] p6;
return 0;
}
底层:operator new: 该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
cpp
void *__CRTDECL operator new(size_tsize) _THROW1(_STDbad_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_allocnomem;
_RAISE(nomem);
}
}
return p;
}
void operator delete(void* p) noexcept
{
if(!p) return;
free(p);
}
new做了什么?

delete做了什么?

5. new和delete的实现原理
5.1 内置类型
-
行为与
malloc/free类似; -
区别:
new/delete的单对象 与new[]/delete[]的数组 要匹配;分配失败时,new抛异常 ,malloc返回NULL。
5.2 自定义类型
-
new原理 :①调
operator new拿到原始内存;②在这块内存上执行构造函数;③返回指针。 -
delete原理 :①先执行析构函数 清理资源;②再调
operator delete释放原始内存。 -
new T[N] :先申请一整块,再对每个元素依次调用N次构造;
-
delete[] :对每个元素依次析构N次,最后一次性释放。
6. malloc/free和new/delete的区别
共同点 :都从堆 申请内存,且都需要手动释放 。
不同点:
| 维度 | malloc/free | new/delete |
|---|---|---|
| 形式 | 函数 | 操作符 |
| 初始化 | 不做初始化 | 可做初始化 |
| 指定大小 | 传字节数 | 写类型,数组用[]写个数 |
| 返回值 | void*需强转 |
直接是目标类型指针 |
| 失败语义 | 返回NULL需判空 |
抛bad_alloc需捕获 |
| 自定义类型 | 不调构造/析构 | 会调构造/析构 |
总结
-
五大分区 :代码段 放++指令与只读常量++ ;数据段 放全局/静态;堆 用于++动态申请++ ;栈 用于++函数活动期++ 的数据;内存映射段 服务于++共享库和高效I/O++。
-
成对使用 :
new↔delete、new[]↔delete[]、malloc/calloc/realloc↔free,别混用。 -
对象语义:自定义类型一定要考虑构造/析构是否被正确调用。
-
底层 :
new背后是operator new(通常用malloc),失败抛异常;delete背后是operator delete(最终free)。
完