
前言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/freeC 语言依靠库函数完成堆内存分配四个函数均定义在stdlib.h头文件。1. 四大函数原型与核心区别1mallocc运行void* malloc(size_t size);功能分配size字节连续堆内存不会初始化内存内存中是随机垃圾值参数size 为需要分配的总字节数返回值分配成功返回堆首地址失败返回NULL使用前必须判空适用场景已知总字节不需要初始化的内存块2callocc运行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全部初始 03reallocc运行void* realloc(void* ptr, size_t new_size);功能调整已分配堆内存大小扩容 / 缩容 两种扩容逻辑原内存后方有足够空闲堆空间直接向后拓展返回原指针地址后方空间不足堆中开辟一块全新满足大小的内存拷贝旧数据到新空间释放原内存块返回新指针特殊规则ptr NULL时realloc等价于malloc(new_size)坑点扩容失败返回NULL原内存不会释放不能直接ptr realloc(ptr, size)会丢失原指针造成内存泄漏4freec运行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) 会导致重复释放程序崩溃。面试高频问题 1malloc/calloc/realloc 三者区别总结初始化malloc 不初始化calloc 分配后全部置 0realloc 仅拷贝原有数据新增空间随机值参数形式malloc 传总字节calloc 传元素个数 单元素大小realloc 传旧指针 新总字节用途malloc 单块无初始化内存calloc 数组清零场景realloc 动态调整内存大小面试高频问题 2malloc 底层实现原理glibc 版glibc 中 malloc 采用空闲链表管理堆内存操作系统提供堆起始地址维护空闲内存双向链表调用 malloc遍历链表寻找第一个满足大小的空闲块首次适配算法分割内存用户使用一块剩余小块放回空闲链表调用 free将释放的内存块放回链表自动合并相邻空闲块减少内存碎片空闲链表无足够大内存时向操作系统调用 brk/mmap 拓展堆空间总结无论扩容会不会更换地址不需要程序员手动 free (p2)最终只需要释放 realloc 返回的最新指针p3。 ⚠️ 致命错误如果写free(p2); free(p3);大概率触发重复释放double free程序直接崩溃。三、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[] array↔delete[]Mismatching may not crash for built-in types, but for custom types it will definitely cause memory leaks and fail to call destructors.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 底层内部自动调用它申请堆内存本质封装 malloc4.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接收空指针直接返回不会崩溃。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.hnew/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 年高频面试题汇总附带标准答案问题 1malloc/calloc/realloc 的区别malloc 分配指定字节内存不初始化calloc 分配 n 个指定大小元素内存全部初始化为 0realloc 修改已有堆内存大小自动拷贝旧数据空间不足会更换内存地址参数形式、适用场景、初始化行为三者均有差异。问题 2new/delete 底层是如何实现的new先调用 operator new底层 malloc开辟堆内存再在内存上调用构造函数创建对象数组 new 会多次调用构造。delete先调用对象析构函数释放内部资源再调用 operator delete底层 free释放堆内存数组 delete 循环调用所有元素析构。问题 3malloc 开辟的内存可以用 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。