在 C/C++ 编程中,内存管理是核心基础技能,而
malloc/free
和new/delete
作为两套内存分配释放机制,是面试中高频出现的考点。
一、内存管理的 "双生花":基础概念解析
1.1 malloc/free:C 语言的内存管家
malloc
全称是 "memory allocation",是 C 标准库中用于动态内存分配的函数,其原型为:
cpp
void* malloc(size_t size);
- 从堆 (heap) 中分配指定字节数的内存
- 分配成功返回指向内存起始地址的 void * 指针,失败返回 NULL
- 分配的内存空间未初始化,内容为随机值
free
用于释放malloc
分配的内存,原型为:
cpp
void free(void* ptr);
- 只能释放
malloc
/calloc
/realloc
分配的内存 - 释放后指针应置为 NULL,避免野指针
- 多次释放同一指针会导致未定义行为
动态内存分配函数详解[4]:free()_free函数-CSDN博客
1.2 new/delete:C++ 的内存魔法师
new
和delete
是 C++ 的关键字,用于动态对象创建和销毁,基本形式为:
cpp
T* ptr = new T; // 分配内存并调用构造函数
delete ptr; // 调用析构函数并释放内存
T* arr = new T[n]; // 分配数组内存
delete[] arr; // 释放数组内存
- 本质是运算符重载,可以自定义行为
- 自动计算所需内存大小,无需显式指定
- 分配过程包括:内存分配 + 构造函数调用
- 释放过程包括:析构函数调用 + 内存释放
【C++入门】new 和 delete表达式_c++ new delete-CSDN博客
1.3 内存分配的 "租房" 比喻
为了帮助理解,我们可以将内存分配比作租房:
场景 | malloc/free | new/delete |
---|---|---|
找房过程 | 直接找房东 (操作系统) 谈,自己确定面积 | 通过中介 (编译器) 找房,中介知道需求 |
入住准备 | 拿到空房子 (未初始化内存) 自己装修 | 中介帮忙装修 (调用构造函数) |
退房流程 | 直接还钥匙给房东,不管屋内状态 | 中介来验收 (调用析构函数) 再还钥匙 |
特殊需求 | 租多个房间要自己管理门牌号 | 中介提供套房管理 (数组分配有专门语法) |
通过这个比喻,我们可以直观理解两套机制的核心差异,接下来我们深入底层看看它们的实现原理。
二、底层实现:从汇编视角看内存分配
2.1 malloc 的内存分配流程
malloc 的实现通常基于操作系统的内存分配接口,在 Linux 下最终会调用brk
或mmap
系统调用。典型的 malloc 实现 (如 ptmalloc) 结构如下:

malloc 的核心特点:
- 维护多个空闲块链表 (fast bin/small bin/large bin) 提高分配效率
- 采用内存池技术减少系统调用开销
- 分配的内存块前会包含元数据 (大小、状态等)
- 内存释放时通常不会立即还给操作系统,而是加入空闲链表
2.2 new/delete 的底层实现
C++ 的 new/delete 本质是对 operator new/operator delete 运算符的调用,其底层实现可以分为两步:

可以看到,C++ 的 new/delete 在底层通常会调用 malloc/free,但增加了构造析构函数的调用和异常处理机制。
2.3 关键差异对比表
特性 | malloc/free | new/delete |
---|---|---|
所属范畴 | C 标准库函数 | C++ 关键字 / 运算符 |
内存分配位置 | 堆 (heap) | 堆 (heap) |
类型安全 | 无,需强制类型转换 | 有,自动推导类型 |
初始化 | 不初始化,内容随机 | 调用构造函数初始化 |
清理 | 直接释放内存 | 调用析构函数再释放内存 |
异常处理 | 返回 NULL 表示失败 | 抛出 bad_alloc 异常 |
数组支持 | 需手动管理,无专门函数 | 有 delete [] 专门处理数组 |
可重载性 | 不可重载 | 可以重载全局 / 类专属版本 |
内存对齐 | 通常 4/8 字节对齐 | 按对象类型自然对齐 |
三、面试高频考点深度解析
3.1 基础概念类问题
考点 1:简述 malloc/free 和 new/delete 的主要区别
这是最基础的问题,考察对两者本质的理解,回答要点:
- 所属语言层面:malloc 是 C 库函数,new/delete 是 C++ 关键字
- 内存管理粒度:new 自动计算大小,malloc 需显式指定
- 初始化差异:new 会调用构造函数,malloc 仅分配内存
- 类型安全:new 返回正确类型指针,malloc 需强制转换
- 异常处理:new 失败抛异常,malloc 返回 NULL
- 数组支持:new []/delete [] 专门处理数组,malloc 需手动管理
考点 2:为什么 C++ 中建议使用 new/delete 而非 malloc/free
进阶问题,考察对 C++ 特性的理解,核心原因:
- 对 C++ 对象的完整生命周期管理(构造 / 析构函数调用)
- 更好的类型安全性,避免强制类型转换错误
- 支持运算符重载,可自定义内存管理策略
- 自动处理内存大小计算,减少人为错误
- 异常机制更符合 C++ 错误处理范式
3.2 实践应用类问题
考点 3:什么时候需要混用 malloc/free 和 new/delete?
实际开发中可能遇到的场景:
- 与 C 代码交互时,C 接口返回的内存需要用 free 释放
- 自定义内存分配器,可能用 malloc 实现 operator new
- 处理特定内存区域(如共享内存),需要手动管理
- 性能敏感场景,需要绕过 C++ 的构造析构开销
考点 4:分析以下代码的问题:
cppint* arr = (int*)malloc(10 * sizeof(int)); for(int i=0; i<10; i++) { arr[i] = i; } delete arr;
这是典型的混用错误,问题点:
- malloc 分配的内存用 delete 释放,行为未定义
- 没有调用 int 的构造函数(虽然 int 是 POD 类型影响不大)
- 数组内存释放应该用 delete [] 而非 delete
- 缺少 NULL 指针检查
正确写法:
cpp
int* arr = (int*)malloc(10 * sizeof(int));
if(arr == NULL) { /* 错误处理 */ }
for(int i=0; i<10; i++) {
arr[i] = i;
}
free(arr); // 用free释放malloc分配的内存
arr = NULL;
3.3 底层原理类问题
考点 5:new 的实现过程分为几步?请简述
关键步骤:
- 调用 operator new 函数分配原始内存
- 在分配的内存上调用构造函数初始化对象
- 返回指向初始化后对象的指针
- 若内存分配失败,调用 new_handler 并可能抛出异常
考点 6:为什么 delete 数组需要用 delete []?
这是高频问题,涉及数组内存释放的底层机制:
- new [] 分配内存时会记录数组大小(通常存放在指针前的位置)
- delete [] 会根据记录的大小调用对应次数的析构函数
- 若使用 delete 释放数组,只会调用一次析构函数,导致内存泄漏
- 对于 POD 类型数组,delete 和 delete [] 效果相同,但为了代码一致性仍应使用 delete []
3.4 内存泄漏类问题
考点 7:列举使用 malloc/free 可能导致内存泄漏的情况
常见场景:
- malloc 后未调用 free
- 指针修改后丢失原始地址,无法 free
- 函数返回前未释放分配的内存
- 异常处理中未释放已分配的内存
- free 后未将指针置为 NULL,导致野指针
考点 8:new/delete 场景下如何避免内存泄漏?
最佳实践:
- 使用 RAII 原则,将指针封装在类中,析构函数中 delete
- 使用智能指针 (std::unique_ptr/std::shared_ptr) 替代原始指针
- 确保 delete 与 new 成对出现,遵循 "谁分配谁释放" 原则
- 对数组使用 delete [],避免析构函数只调用一次
- 在异常安全代码中,使用 try-finally 确保内存释放
四、历年面试真题详解
4.1 字节跳动 2023 秋招 C++ 开发真题
题目: 分析以下代码的输出结果,并解释原因
cpp#include <iostream> #include <cstdlib> using namespace std; class Test { public: Test() { cout << "Test constructor" << endl; } ~Test() { cout << "Test destructor" << endl; } }; int main() { Test* p1 = (Test*)malloc(sizeof(Test)); Test* p2 = new Test; free(p1); delete p2; return 0; }
解析:
输出结果:

原因分析:
p1 = (Test*)malloc(sizeof(Test))
:仅分配内存,未调用构造函数,所以没有输出构造信息p2 = new Test
:分配内存并调用构造函数,输出 "Test constructor"free(p1)
:直接释放内存,不调用析构函数,无输出delete p2
:先调用析构函数,输出 "Test destructor",再释放内存
考点: 考察 malloc/free 和 new/delete 在对象构造析构上的差异,malloc 分配的内存不会调用构造函数,free 也不会调用析构函数,这是 C++ 对象管理的核心考点。
4.2 腾讯 2022 社招 C++ 高级工程师真题
题目: 实现一个简单的内存分配器,要求同时支持 malloc/free 和 new/delete 接口,并解释设计思路。
解析: 这是一道设计题,考察内存管理的综合能力,以下是核心实现思路:
cpp
#include <iostream>
#include <vector>
#include <mutex>
using namespace std;
class MemoryAllocator {
private:
vector<void*> free_blocks; // 空闲块链表
mutex mtx; // 互斥锁,保证线程安全
public:
// 模拟malloc接口
void* my_malloc(size_t size) {
lock_guard<mutex> lock(mtx);
// 简化实现,实际应维护不同大小的块链表
if (!free_blocks.empty()) {
void* ptr = free_blocks.back();
free_blocks.pop_back();
return ptr;
}
return ::malloc(size); // 调用标准malloc
}
// 模拟free接口
void my_free(void* ptr) {
if (!ptr) return;
lock_guard<mutex> lock(mtx);
free_blocks.push_back(ptr);
// 实际应考虑内存合并等策略
}
// 重载operator new
void* operator new(size_t size) {
return my_malloc(size);
}
// 重载operator delete
void operator delete(void* ptr) noexcept {
my_free(ptr);
}
// 数组版本
void* operator new[](size_t size) {
return my_malloc(size);
}
void operator delete[](void* ptr) noexcept {
my_free(ptr);
}
};
// 使用示例
class MyClass : public MemoryAllocator {
public:
MyClass() { cout << "MyClass created" << endl; }
~MyClass() { cout << "MyClass destroyed" << endl; }
};
int main() {
// 使用自定义分配器
MyClass* obj1 = new MyClass;
delete obj1;
void* buf = my_malloc(1024);
my_free(buf);
return 0;
}
设计要点:
- 采用内存池技术,提高分配效率
- 同时实现 C 风格 (malloc/free) 和 C++ 风格 (new/delete) 接口
- 加入互斥锁支持多线程环境
- 实际生产环境还需考虑内存对齐、碎片整理、内存映射等优化
4.3 微软 2021 校招真题
题目: 解释以下代码为什么会导致内存泄漏,并给出修复方案
cppvoid processData() { int* data = new int[100]; // 处理数据... if (someCondition()) { return; } delete data; // 当someCondition为true时,未释放内存 }
解析:
- 内存泄漏原因 :当
someCondition()
为 true 时,函数直接返回,没有执行delete data
,导致 new 分配的数组内存未释放 - 修复方案 1:使用 RAII 原则,封装为智能指针
cpp
void processData() {
std::unique_ptr<int[]> data(new int[100]);
// 处理数据...
if (someCondition()) {
return; // 智能指针析构时自动释放内存
}
// 无需手动delete
}
- **修复方案 2:**使用 try-finally 确保释放
cpp
void processData() {
int* data = new int[100];
try {
// 处理数据...
if (someCondition()) {
return;
}
} finally {
delete[] data; // 无论是否异常都会执行
}
}
考点: 考察异常安全和内存泄漏的预防,RAII 是 C++ 中处理资源管理的重要原则,智能指针是现代 C++ 编程的基本技能。
4.4 Google 2020 面试题
题目: 为什么 C++ 中建议将析构函数声明为虚函数?这与 new/delete 有什么关系?
解析:
- **核心原因:**当通过基类指针删除派生类对象时,确保调用正确的析构函数
- 示例代码:
cpp
class Base {
public:
~Base() { cout << "Base destructor" << endl; }
};
class Derived : public Base {
public:
~Derived() { cout << "Derived destructor" << endl; }
int* ptr;
Derived() { ptr = new int[10]; }
};
void test() {
Base* base = new Derived;
delete base; // 若Base析构函数非虚,仅调用Base::~Base()
}
问题分析:
- 当 Base 析构函数不是虚函数时,
delete base
只会调用 Base 的析构函数 - Derived 的析构函数未被调用,导致其分配的内存 (ptr) 未释放
- 正确做法是将 Base 的析构函数声明为虚函数:
virtual ~Base() {}
与 new/delete 的关系:
- new/delete 在释放多态对象时,需要通过虚函数表找到正确的析构函数
- 若析构函数非虚,delete 操作将无法正确释放派生类资源
4.5 腾讯(2023):new[]与delete配对问题
题目:
cppint* p = new int[10]; delete p; // 错误!应使用delete[] // 实际行为: // 1. 仅调用一次析构函数(若为自定义类型) // 2. 仅释放第一个元素内存,其余9个元素泄漏
底层原理:
-
new[]
在分配内存时头部添加数组大小(如4字节存储元素数量) -
delete[]
根据该信息调用正确次数的析构函数 -
使用
delete
仅释放头部导致后续内存未被释放
4.6 阿里(2024):malloc(0)行为分析
题目:
cppvoid* p1 = malloc(0); void* p2 = new char[0];
解析:
-
malloc(0)
可能返回NULL或唯一非空指针(平台相关) -
new char[0]
保证返回非空指针(可安全传递) -
两者均不可解引用
4.7 华为(2023):定位new应用
题目:
cpp#include <new> void initPool(void* buf) { Data* p = new(buf) Data(); // 在预分配内存构造对象 }
使用场景:
-
内存池性能优化
-
避免动态分配开销
-
嵌入式系统无堆环境
【C++特殊工具与技术】优化内存分配(四):定位new表达式、类特定的new、delete表达式-CSDN博客
五、内存管理最佳实践指南
5.1 现代 C++ 内存管理策略
①优先使用智能指针
std::unique_ptr
:独占所有权,适合单一对象std::shared_ptr
:共享所有权,自动引用计数std::weak_ptr
:弱引用,解决循环引用问题
②遵循 RAII 原则
- 资源获取即初始化 (Resource Acquisition Is Initialization)
- 将内存资源封装在类中,利用析构函数自动释放
③减少手动内存管理
- 使用 STL 容器 (如 vector/map) 代替手动分配数组
- 避免混用 malloc/free 和 new/delete,保持接口一致性
5.2 混合编程中的内存管理
当 C 和 C++ 代码混合时,需注意:
- C 代码分配的内存用 free 释放,C++ 代码分配的用 delete 释放
- 类对象必须用 new/delete 管理,确保构造析构调用
- 自定义类型转换时注意内存对齐问题
- 考虑封装 C 接口,提供 C++ 风格的内存管理接口
5.3 内存泄漏检测工具
实际开发中应借助工具检测内存问题:
- Valgrind:Linux 下强大的内存检测工具,可检测泄漏和越界
- AddressSanitizer:Clang/LLVM 内置的内存错误检测器
- Visual Leak Detector:Windows 下的内存泄漏检测库
- 智能指针 + 静态分析工具:如 Clang-Tidy 可检测潜在内存问题
5.4 面试应答策略
面对内存管理相关面试题,建议采用以下思路:
- 先理清问题涉及的核心概念(分配 / 释放、构造 / 析构、异常处理等)
- 用具体代码示例说明差异和问题
- 从底层原理出发解释现象(如内存布局、虚函数表等)
- 结合最佳实践给出解决方案
- 提及现代 C++ 的替代方案(智能指针、STL 等)
5.5 经典真题详解
**真题1:内存泄漏的根源与检测方法(**2025年字节跳动C++面试题)
解析 :
内存泄漏指动态分配的内存未正确释放。常见原因包括:
- 分配与释放不匹配(如
new
配free
) - 指针覆盖导致原始地址丢失
- 循环引用(
shared_ptr
的噩梦)
检测工具链:

**真题2:智能指针的选择策略(**腾讯2025校招真题)
解析:
unique_ptr
:独占所有权,无性能开销(推荐默认选择)shared_ptr
:共享所有权,需警惕循环引用weak_ptr
:打破循环引用的利器
循环引用示例:
cpp
class B;
class A {
std::shared_ptr<B> b_ptr;
};
class B {
std::shared_ptr<A> a_ptr; // 形成循环引用
};
// 解决:将A或B中的指针改为weak_ptr
5.6 深水区考点
考点1:定位new(placement new)的原理
示例:
cpp
char* buffer = new char[sizeof(MyClass)];
MyClass* obj = new (buffer) MyClass; // 在buffer上构造对象
obj->~MyClass(); // 需手动调用析构函数
delete[] buffer; // 释放内存
考点2:内存对齐的影响
