前言
2026 年后端、嵌入式、C++ 校招面试中,内存管理是必考核心模块,很多同学只会背堆栈概念,搞不清全局变量、静态变量、字符串常量、动态内存分别存放在哪一段,同时分不清malloc/free与new/delete底层差异、operator new与定位 new 的使用场景。本文完全基于作者自己学习的内容总结,如有错误,还望大家指出,一起学习。
一、C/C++ 程序五大内存分区详解
程序编译链接加载到内存后,操作系统会划分内核空间 与用户空间,用户空间从上至下分为 5 大区域:栈、内存映射段、堆、数据段(静态区)、代码段(常量区),内核空间用户代码无法读写。
内存布局总览(从高地址→低地址)
- 内核空间:操作系统内核占用,应用无权访问
- 栈区(向下增长,高地址→低地址)
- 内存映射段:动态库、文件映射、进程共享内存
- 堆区(向上增长,低地址→高地址)
- 数据段(静态区):.data 已初始化全局 / 静态变量、.bss 未初始化全局 / 静态变量
- 代码段(.text/.rodata 只读常量区):程序指令、字符串字面量

分区详细说明
1. 栈(堆栈 stack)
- 存储内容:函数非静态局部变量、函数形参、函数返回地址、临时变量
- 增长方向:向下(高地址向低地址拓展)
- 生命周期:函数调用创建栈帧,函数执行完毕自动回收,无需手动释放
- 限制:栈空间大小固定(Linux 默认 8MB),局部数组过大容易栈溢出
- 特性:编译器自动管理,分配释放速度极快
2. 内存映射段
- 核心用途:高效 IO 映射、加载动态共享库、mmap 创建进程间共享内存
- 2026 年开发场景:大文件读写、零拷贝、多进程通信底层依赖此区域,入门阶段只需了解概念
3. 堆(heap)
- 存储内容:
malloc/calloc/realloc/new动态申请的内存 - 增长方向:向上(低地址向高地址拓展)
- 生命周期:完全由程序员手动管理,必须主动
free/delete释放;未释放会内存泄漏,程序结束操作系统统一回收 - 特性:空间无固定上限,受虚拟内存大小限制;频繁分配释放会产生内存碎片
4. 数据段(全局 / 静态区)
细分两段:
.data段:已初始化非 0的全局变量、静态变量,程序编译时占用可执行文件磁盘空间.bss段:未初始化 / 初始化为 0 的全局、静态变量,磁盘不占空间,程序加载到内存时操作系统自动清零- 生命周期:程序启动分配,程序结束才释放,全局生效
5. 代码段(只读常量区 .text + .rodata)
.text:编译后的程序机器指令(函数代码),只读,禁止修改.rodata:只读常量,如字符串字面量"abcd"、const char*指向的常量字符串- 特性:只读,强行修改会触发段错误崩溃
实战代码分区判断题(PPT 原题完整解析)
c
运行
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→ C 数据段:全局已初始化变量存.data 段staticGlobalVar→ C 数据段:静态全局变量同样存.datastaticVar→ C 数据段:static 修饰局部变量,生命周期提升至全局,存放静态区localVar→ A 栈:普通非静态局部变量,函数栈帧内num1→ A 栈:局部数组,数组本体开辟在栈上char2→ A 栈:字符数组本体在栈,字符串abcd编译时拷贝到栈数组中,可修改char2[0] = 'x'*char2→ A 栈:数组第一个字符,位于栈内存pChar3→ A 栈:指针变量本身是局部变量,存放在栈*pChar3→ D 代码段:"abcd"是字符串常量,存只读常量区,不能修改pChar3[0]='x'会崩溃ptr1→ A 栈:指针变量本身在栈*ptr1→ B 堆:malloc 开辟的内存空间在堆上
二、C 语言动态内存管理:malloc/calloc/realloc/free
C 语言依靠库函数完成堆内存分配,四个函数均定义在<stdlib.h>头文件。
1. 四大函数原型与核心区别
(1)malloc
c
运行
void* malloc(size_t size);
- 功能:分配
size字节连续堆内存,不会初始化内存,内存中是随机垃圾值 - 参数:size 为需要分配的总字节数
- 返回值:分配成功返回堆首地址;失败返回
NULL,使用前必须判空 - 适用场景:已知总字节,不需要初始化的内存块
(2)calloc
c
运行
void* calloc(size_t num, size_t size);
- 功能:分配
num个、每个大小size字节的连续空间,自动将所有字节初始化为 0 - 参数:num 元素个数,size 单个元素字节大小
- 对比 malloc:多一步清零操作,分配速度略慢;数组场景优先使用 calloc,省去手动 memset
- 示例:
int* p2 = (int*)calloc(4, sizeof(int));分配 4 个 int,全部初始 0
(3)realloc
c
运行
void* realloc(void* ptr, size_t new_size);
- 功能:调整已分配堆内存大小,扩容 / 缩容 两种扩容逻辑:
- 原内存后方有足够空闲堆空间:直接向后拓展,返回原指针地址
- 后方空间不足:堆中开辟一块全新满足大小的内存,拷贝旧数据到新空间,释放原内存块,返回新指针
- 特殊规则:
ptr == NULL时,realloc等价于malloc(new_size) - 坑点:扩容失败返回
NULL,原内存不会释放,不能直接ptr = realloc(ptr, size),会丢失原指针造成内存泄漏
(4)free
c
运行
void free(void* ptr);
- 功能:释放 malloc/calloc/realloc 申请的堆内存
- 注意:不能释放栈、静态区、常量区指针;重复 free、free 野指针会程序崩溃
面试经典例题解析
c
运行
void Test ()
{
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
// 这里需要free(p2)吗?
free(p3 );
}
答案:不需要手动 free (p2) realloc 扩容时,如果开辟了新内存,底层会自动释放 p2 指向的旧内存;最后只需要释放最新的 p3 指针即可。如果手动 free (p2) 会导致重复释放,程序崩溃。
面试高频问题 1:malloc/calloc/realloc 三者区别总结
- 初始化:malloc 不初始化;calloc 分配后全部置 0;realloc 仅拷贝原有数据,新增空间随机值
- 参数形式:malloc 传总字节;calloc 传元素个数 + 单元素大小;realloc 传旧指针 + 新总字节
- 用途:malloc 单块无初始化内存;calloc 数组清零场景;realloc 动态调整内存大小
面试高频问题 2:malloc 底层实现原理(glibc 版)
glibc 中 malloc 采用空闲链表管理堆内存:
- 操作系统提供堆起始地址,维护空闲内存双向链表
- 调用 malloc:遍历链表寻找第一个满足大小的空闲块(首次适配算法),分割内存,用户使用一块,剩余小块放回空闲链表
- 调用 free:将释放的内存块放回链表,自动合并相邻空闲块,减少内存碎片
- 空闲链表无足够大内存时,向操作系统调用 brk/mmap 拓展堆空间
三、C++ 专属内存管理:new 与 delete 操作符
C 语言malloc/free在 C++ 中可兼容,但无法面向对象处理自定义类型,因此 C++ 设计new/delete操作符,底层依然封装 malloc/free,但增加对象构造、析构逻辑。
3.1 new/delete 操作内置类型
基础语法示例
cpp
运行
void Test()
{
// 1.动态申请单个int空间,未初始化
int* ptr4 = new int;
// 2.动态申请单个int并初始化为10
int* ptr5 = new int(10);
// 3.动态申请连续3个int数组空间
int* ptr6 = new int[3];
// 释放单个对象用delete,数组必须配套delete[]
delete ptr4;
delete ptr5;
delete[] ptr6;
}
强制匹配规则(2026 面试高频坑)
new 单个变量↔deletenew[] 数组↔delete[]内置类型偶尔不匹配不会崩溃,但自定义类型一定会内存泄漏、未调用析构函数。
3.2 new/delete 操作自定义类型(核心差异)
malloc/free 和 new/delete 最本质区分:new 会调用构造函数初始化对象,delete 调用析构函数释放对象内部资源;malloc/free 仅开辟原始内存,不执行构造析构 完整示例代码:
cpp
运行
#include <iostream>
using namespace std;
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "构造函数A() 对象地址:" << this << endl;
}
~A()
{
cout << "析构函数~A() 对象地址:" << this << endl;
}
private:
int _a;
};
int main()
{
// malloc仅开辟内存,不调用构造
A* p1 = (A*)malloc(sizeof(A));
// new:开辟内存 + 调用构造函数初始化对象
A* p2 = new A(1);
free(p1); // free仅释放内存,不调用析构
delete p2;// delete先调用析构清理资源,再释放内存
// 内置类型对比
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]; // 调用10次构造
free(p5);
delete[] p6; // 调用10次析构
return 0;
}
运行结果可以直观看到:malloc 开辟的内存没有构造、free 释放时无析构;new/delete 完整管理对象生命周期。
四、operator new 与 operator delete 底层全局函数(重点)
很多初学者混淆new操作符和operator new函数,这里彻底区分:
new / delete:操作符,程序员直接使用,完整流程分配 + 构造 / 析构 + 释放operator new / operator delete:系统全局库函数,new 底层内部自动调用它申请堆内存,本质封装 malloc
4.1 operator new 源码简化解析
cpp
运行
void *operator new(size_t size)
{
void *p;
// 调用malloc申请堆内存
while ((p = malloc(size)) == nullptr)
{
// 内存不足时执行用户自定义内存处理函数
if (_callnewh(size) == 0)
{
// 分配失败抛出bad_alloc异常,区别于malloc返回NULL
static const std::bad_alloc nomem;
throw nomem;
}
}
return p;
}
核心逻辑:
- 底层依赖 malloc 分配堆内存
- 分配失败不会返回空指针,抛出
bad_alloc异常,程序不捕获会直接终止
4.2 operator delete 源码简化解析
cpp
运行
void operator delete(void *pUserData)
{
if (pUserData == nullptr)
return;
// 底层最终调用free释放内存
_free_dbg(pUserData, _NORMAL_BLOCK);
}
核心逻辑:底层封装 free,接收空指针直接返回,不会崩溃。

cpp
class A
{
public:
A(int a1 = 0, int a2 = 0)
:_a1(a1)
,_a2(a2)
{
cout << "A(int a1 = 0, int a2 = 0)" << endl;
}
A(const A& aa)
:_a1(aa._a1)
{
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa)
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a1 = aa._a1;
}
return *this;
}
~A()
{
//delete _ptr;
cout << "~A()" << endl;
}
void Print()
{
cout << "A::Print->" << _a1 << endl;
}
A& operator++()
{
_a1 += 100;
return *this;
}
private:
int _a1 = 1;
int _a2 = 1;
};
class B
{
public:
~B()
{
cout << "~B()" << endl;
}
private:
int _b1 = 2;
int _b2 = 2;
};
int main()
{
//int* p1 = new int[10]; // -> malloc
//delete p1; // -> free
//free(p1);
B* p2 = new B[10];
delete p2;
A* p3 = new A[10];
delete[] p3; //危险写法
return 0;
}
这里为什么编译器申请的空间不一样?
编译器分配堆内存大小不一样的根本原因: 当类拥有用户自定义析构函数时,new[]会多开辟一块头部空间,用来保存数组元素个数; 如果类没有自定义析构函数(编译器默认 trivial 平凡析构),不需要记录数量,因此不额外分配头部。
总结底层调用链路
new A执行顺序:operator new(malloc 开辟内存) → 调用 A 构造函数delete p执行顺序:调用 A 析构函数 →operator delete(free 释放内存)new A[N]:调用operator new[],内部多次调用 operator new,循环 N 次构造delete[] p:循环 N 次调用析构,再调用operator delete[]释放整块内存
五、定位 new 表达式 placement-new(拓展知识点)
定义
定位 new 不会开辟新堆内存,在一块已经提前分配好的原始内存上,手动调用构造函数初始化对象。
使用语法
cpp
运行
new (place_address) type;
new (place_address) type(初始化参数);
place_address必须是一块已经分配完成的内存指针(栈、堆、内存池都可以)。
实战示例(内存池标准用法)
cpp
运行
#include <iostream>
using namespace std;
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "构造A:" << this << endl;
}
~A()
{
cout << "析构~A:" << this << endl;
}
private:
int _a;
};
int main()
{
// 1.malloc提前开辟原始堆内存,无对象、无构造
A* p1 = (A*)malloc(sizeof(A));
// 定位new:在已分配内存上调用构造函数创建对象
new(p1)A(10);
// 手动调用析构函数(定位new必须手动析构,不能直接delete)
p1->~A();
free(p1);
// 2.operator new开辟内存搭配定位new
A* p2 = (A*)operator new(sizeof(A));
new(p2)A(20);
p2->~A();
operator delete(p2);
return 0;
}
核心使用场景:内存池
2026 年高并发、游戏、服务器开发大量使用内存池,预先批量分配大块内存,运行时直接取出内存块,通过定位 new 构造对象,减少频繁 malloc 带来的性能损耗与内存碎片。
注意事项
- 定位 new 仅初始化对象,不分配内存,释放内存仍需使用
free/operator delete - 销毁对象不能使用 delete,必须手动显式调用析构函数
obj->~类名()
六、malloc/free 与 new/delete 完整对比(面试背诵版)
共同点:两者都是在堆区分配内存,都需要程序员手动释放,不释放会造成内存泄漏。 六大核心区别:
- 本质类型不同 malloc/free 是库函数,需要包含头文件
<stdlib.h>;new/delete 是 C++ 内置操作符,编译器直接识别。 - 内存初始化能力 malloc 仅分配原始内存,内容随机;new 支持直接初始化内置类型、自动调用自定义类型构造函数。
- 内存大小计算 malloc 需要手动计算总字节
malloc(sizeof(A)*10);new 自动推导类型大小new A[10],无需手动计算。 - 返回值与类型安全 malloc 返回
void*,使用时必须强制类型转换;new 直接返回对应类型指针,类型安全,无需强转。 - 分配失败处理机制 malloc 分配失败返回
NULL,代码需要 if 判空;new 分配失败抛出bad_alloc异常,需要 try-catch 捕获,默认直接崩溃。 - 自定义类型对象处理(最关键) malloc/free 只操作裸内存,不会调用构造、析构函数,对象内部动态资源会泄漏;new 分配后自动调用构造初始化,delete 释放前调用析构清理对象资源。
额外补充区别:
- new 无扩容接口;realloc 可以动态调整 malloc 分配的内存大小
- new/delete 支持全局 / 类内重载,自定义内存分配逻辑;malloc/free 不支持重载
七、2026 年高频面试题汇总(附带标准答案)
问题 1:malloc/calloc/realloc 的区别?
- malloc 分配指定字节内存,不初始化;
- calloc 分配 n 个指定大小元素,内存全部初始化为 0;
- realloc 修改已有堆内存大小,自动拷贝旧数据,空间不足会更换内存地址;
- 参数形式、适用场景、初始化行为三者均有差异。
问题 2:new/delete 底层是如何实现的?
- new:先调用 operator new(底层 malloc)开辟堆内存,再在内存上调用构造函数创建对象;数组 new 会多次调用构造。
- delete:先调用对象析构函数释放内部资源,再调用 operator delete(底层 free)释放堆内存;数组 delete 循环调用所有元素析构。
问题 3:malloc 开辟的内存可以用 delete 释放吗?new 的内存能用 free 释放吗?
内置类型下程序可能不崩溃,但属于未定义行为,禁止混用;自定义类型会造成严重内存泄漏:delete 会调用析构,free 不会;free 直接释放内存,不会执行析构清理对象内部堆资源。
问题 4:定位 new 的作用和使用场景?
定位 new 在已分配好的内存上手动调用构造函数,不额外分配堆内存;主要用于内存池优化,减少频繁动态分配的性能开销。
问题 5:栈、堆、静态区生命周期区别?
- 栈:函数调用期间存在,函数结束自动销毁;
- 堆:程序员手动分配释放,程序结束系统回收;
- 静态区:程序启动分配,整个程序运行期间存在,程序退出销毁。
八、开发避坑总结(2026 项目实战踩坑点)
- 数组 new 必须配套 delete \[\],自定义类型不匹配会漏调用析构,内存泄漏;
- realloc 扩容不要直接赋值原指针,扩容失败返回 NULL 丢失原内存;
- 禁止重复 free/delete、释放栈 / 常量区指针,会直接段错误;
- 静态局部变量存数据段,不要用来存储大量临时数据,程序全程占用内存;
- 字符串字面量存只读常量区,不要尝试修改,会崩溃;局部字符数组 char2 \[\]="abc" 存储在栈,可以修改;
- 高并发场景优先内存池 + 定位 new,减少 malloc 频繁分配带来的内存碎片;
- new 内存分配失败会抛异常,生产环境建议增加 try-catch 捕获 bad_alloc。
