C 的 malloc/free 与 C++ 的 new/delete 到底差在哪?一文彻底说清
很多从 C 过渡到 C++ 的程序员,最开始都习惯用 malloc 和 free 来管理动态内存。到了 C++,又多出了 new 和 delete,看起来好像就是换了个写法。但在面试中,这个问题一深挖就能筛掉一大半人。
今天我们就从语法、语义、底层机制三个层面,把它们的区别彻底拆解清楚。
1. 本质区别:函数 vs 运算符
这是最根本的差异:
malloc/free是 C 标准库提供的函数。new/delete是 C++ 语言内置的运算符。
因为是运算符,new 和 delete 可以被重载 ,你可以为特定类自定义内存分配策略(对象池、内存池等)。而 malloc/free 只是函数,你只能通过不同实现来替换(如 jemalloc、tcmalloc),无法针对某个类做特殊处理。
cpp
// new 可以重载
class MyClass {
public:
static void* operator new(size_t size) {
std::cout << "Custom new for MyClass\n";
return malloc(size);
}
static void operator delete(void* ptr) {
std::cout << "Custom delete for MyClass\n";
free(ptr);
}
};
2. 构造函数与析构函数:最大的语义鸿沟
这是面试中最核心的考点,也是最容易被问到的地方。
new 会调用构造函数,malloc 不会
cpp
class Object {
public:
Object() { std::cout << "Constructor called\n"; }
~Object() { std::cout << "Destructor called\n"; }
int data[100];
};
// 使用 new
Object* p1 = new Object; // 输出:Constructor called
// 同时分配了足够的内存,并构造了对象
// 使用 malloc
Object* p2 = (Object*)malloc(sizeof(Object));
// 什么都没输出,只是拿了一块原始内存
// p2 指向的只是一堆字节,不是真正的 Object 对象!
delete 会调用析构函数,free 不会
cpp
delete p1; // 输出:Destructor called,然后释放内存
free(p2); // 直接释放内存,不调用析构函数
关键结论 :new/delete 做了两件事:内存分配/释放 + 对象构造/析构 。而 malloc/free 只做内存分配/释放。
必须配对使用,混用是未定义行为
cpp
Object* p = (Object*)malloc(sizeof(Object));
delete p; // 危险!delete 会尝试调用析构函数,但对象根本没构造过
反过来也一样:
cpp
Object* p = new Object;
free(p); // 危险!析构函数不会被调用,可能造成资源泄漏
3. 返回类型与类型安全
malloc 返回 void*,需要手动强制类型转换:
cpp
int* p = (int*)malloc(sizeof(int) * 10);
new 直接返回正确的类型指针,类型安全且代码更简洁:
cpp
int* p = new int[10]; // 不需要转换
4. 内存分配失败时的行为
malloc 分配失败返回 NULL(或 nullptr):
cpp
int* p = (int*)malloc(SIZE_MAX);
if (p == NULL) {
// 手动处理失败
}
new 分配失败默认抛出 std::bad_alloc 异常:
cpp
try {
int* p = new int[SIZE_MAX];
} catch (const std::bad_alloc& e) {
// 处理异常
}
C++ 也提供了不抛异常的版本:
cpp
int* p = new(std::nothrow) int[SIZE_MAX];
if (p == nullptr) {
// p 为空,和 malloc 行为类似
}
5. 计算所需内存大小的方式
malloc 需要手动计算字节数:
cpp
int* arr = (int*)malloc(sizeof(int) * n);
new 由编译器自动计算:
cpp
int* arr = new int[n]; // 编译器知道 int 的大小
不仅代码更简洁,还避免了手动计算时可能出现的错误(比如忘记乘 sizeof)。
6. new/delete 的数组版本
new 和 delete 有单独的数组形式:
cpp
// 单个对象
int* p1 = new int;
delete p1;
// 数组
int* p2 = new int[10];
delete[] p2; // 必须用 delete[]
绝对不能混用:
cpp
int* p = new int[10];
delete p; // 未定义行为!可能只调用一次析构函数,甚至导致内存泄漏
为什么必须配对?因为当数组元素是类对象时,delete[] 需要知道有多少个对象,依次调用析构函数。编译器通常在数组内存块前面额外存放一个记录元素个数的值,delete[] 会去读取它,而 delete 不会。
7. 重新分配内存
malloc 有配套的 realloc,可以原地调整已分配内存块的大小:
cpp
int* p = (int*)malloc(sizeof(int) * 10);
p = (int*)realloc(p, sizeof(int) * 20); // 扩容到 20 个 int
new/delete 没有配套的 realloc 。如果想扩容,只能手动 new 一块更大的内存,拷贝内容,再 delete 旧内存。这也是为什么 std::vector 在扩容时需要重新分配并移动/拷贝元素。
8. 面试常考清单
这里整理一下面试中的高频问题:
8.1 new 和 malloc 的根本区别是什么?
答案要点:
new是运算符,malloc是函数。new会调用构造函数,malloc不会。new返回带类型的指针,malloc返回void*。new失败抛异常,malloc失败返回 NULL。new可由编译器计算大小,malloc需手动指定。
8.2 delete p 和 delete[] p 有什么区别?为什么必须配对?
答案要点:
delete用于单个对象,调用一次析构函数。delete[]用于数组,会读取数组长度信息,依次调用每个元素的析构函数。- 混用是未定义行为,可能导致内存泄漏或程序崩溃。
8.3 可以用 free 释放 new 出来的内存吗?反过来呢?
答案要点 :绝对不能。new 分配的内存必须用 delete 释放(反之亦然),否则会导致构造函数/析构函数未被正确调用,是未定义行为。
8.4 placement new 是什么?
答案要点:在已分配的内存上构造对象,不分配新内存。
cpp
void* buffer = malloc(sizeof(Object));
Object* obj = new(buffer) Object(); // 在 buffer 上构造 Object
obj->~Object(); // 手动调用析构函数(placement new 没有对应的 delete)
free(buffer);
常用于内存池、嵌入式系统等场景,也是 std::vector 等容器底层实现的一部分。
8.5 如何让 new 在分配失败时不抛异常?
答案要点 :使用 std::nothrow 版本。
cpp
int* p = new(std::nothrow) int[100];
if (p == nullptr) { /* 处理失败 */ }
9. 何时用 malloc,何时用 new?
在 C++ 中,默认应该使用 new/delete。它们与 C++ 的对象模型完美集成,能正确处理构造和析构。
少数场景下你可能仍需要 malloc/free:
- 与 C 代码交互,必须使用 C 接口分配和释放内存。
- 需要
realloc的原地扩容特性。 - 实现自定义
operator new时,底层通常还是调用malloc。
理解这些区别,不仅是为了应付面试,更是为了写出安全、正确的 C++ 代码。在 C++ 的世界里,请优先把内存管理交给 new/delete,或者更好的是,交给智能指针和容器。