C++ 中 new/delete 与 malloc/free 的区别:从内存分配到对象生命周期
C++ 中 new/delete 与 malloc/free 的区别:从内存分配到对象生命周期
一、引言:两套内存管理机制的分水岭
在 C++ 中,动态内存管理有两种方式:继承自 C 语言的malloc/free和 C++ 原生的new/delete。表面上看它们都用于堆内存的分配和释放,但在对象构造、类型安全、异常处理等方面存在本质差异。
很多初学者习惯混用这两套机制,但这是非常危险的——用free释放new出来的对象、用delete释放malloc出来的内存,都会导致未定义行为。理解它们之间的根本区别,是写出正确、安全的 C++ 代码的基础。
二、核心区别速览
| 维度 | new / delete | malloc / free |
|------|-------------|---------------|
| 语言归属 | C++ 关键字/运算符 | C 标准库函数 |
| 头文件 | 无需头文件 |<cstdlib>或<stdlib.h>|
| 类型安全 | 是(返回正确类型指针) | 否(返回void*,需强制转换) |
| 计算大小 | 编译器自动计算 | 必须手动计算(使用sizeof) |
| 构造/析构 |会调用构造函数/析构函数 |不会调用构造函数/析构函数 |
| 失败行为 | 抛出std::bad_alloc异常 | 返回NULL(nullptr) |
| 重载能力 | 可以重载(operator new) | 不能重载(但可替换实现) |
| 分配来源 | 通常调用operator new,内部可能调用malloc| 直接向操作系统申请内存 |
| 释放后指针 | 建议置nullptr(不自动) | 建议置NULL(不自动) |
| 配套关系 | new 必须配对 delete,new[] 必须配对 delete[] | malloc 必须配对 free |
三、最核心的区别:构造与析构
3.1 new/delete 会调用构造和析构函数
#include <iostream> #include <string> class Person { std::string name; int age; public: Person(const std::string& n, int a) : name(n), age(a) { std::cout << "Constructor: " << name << " (" << age << ")" << std::endl; } ~Person() { std::cout << "Destructor: " << name << std::endl; } void introduce() const { std::cout << "I'm " << name << ", " << age << " years old." << std::endl; } }; int main() { std::cout << "=== new/delete ===" << std::endl; Person* p1 = new Person("Alice", 30); // 分配内存 + 调用构造函数 p1->introduce(); delete p1; // 调用析构函数 + 释放内存 std::cout << "=== malloc/free ===" << std::endl; Person* p2 = (Person*)malloc(sizeof(Person)); // 仅分配原始内存 // p2 指向的是一块未初始化的内存,Person 的构造函数没有被调用! // p2->introduce(); // 未定义行为!name 和 age 是未初始化的 free(p2); // 仅释放内存,不调用析构函数 return 0; }输出:
=== new/delete === Constructor: Alice (30) I'm Alice, 30 years old. Destructor: Alice === malloc/free === (没有任何构造或析构输出)3.2 构造与析构的本质区别
四、类型安全性
4.1 new 的类型安全
int* p1 = new int(42); // 自动推导类型,返回 int* double* p2 = new double(3.14); // 自动推导类型,返回 double* // Person* p3 = new double(3.14); // 编译错误!类型不匹配4.2 malloc 需要手动转换
int* p1 = (int*)malloc(sizeof(int)); // 必须强制转换 double* p2 = (double*)malloc(sizeof(double)); // 必须强制转换 // 危险的转换:编译通过,运行时可能出错 double* p3 = (double*)malloc(sizeof(int)); // 分配了 int 大小,却当 double 用 *p3 = 3.14; // 可能写入越界! // C++ 中推荐使用 static_cast int* p4 = static_cast<int*>(malloc(sizeof(int)));五、大小计算
5.1 new 自动计算大小
// new 根据类型自动计算所需内存大小 int* p1 = new int; // sizeof(int) 自动计算,通常 4 字节 double* p2 = new double; // sizeof(double) 自动计算,通常 8 字节 Person* p3 = new Person; // sizeof(Person) 自动计算 // 分配数组 int* arr = new int[100]; // 自动计算 100 * sizeof(int)5.2 malloc 必须手动计算
// malloc 需要手动指定字节数 int* p1 = (int*)malloc(sizeof(int)); // 手动计算 double* p2 = (double*)malloc(sizeof(double)); // 手动计算 Person* p3 = (Person*)malloc(sizeof(Person)); // 手动计算 // 容易出错:忘记乘以元素个数 int* arr = (int*)malloc(100); // 错误!只分配了 100 字节,不是 100 个 int int* arr2 = (int*)malloc(100 * sizeof(int)); // 正确 // 另一个常见错误:类型与分配大小不匹配 Person* p4 = (Person*)malloc(sizeof(int)); // 编译通过,运行时越界!六、失败处理
6.1 new 默认抛出异常
#include <new> #include <iostream> int main() { try { // 尝试分配一个巨大的内存块 int* huge = new int[100000000000]; // 可能失败 } catch (const std::bad_alloc& e) { std::cerr << "Allocation failed: " << e.what() << std::endl; } // 或者使用 nothrow 版本,返回 nullptr(类似 malloc) int* p = new (std::nothrow) int[100000000000]; if (p == nullptr) { std::cerr << "nothrow new failed" << std::endl; } }6.2 malloc 返回 NULL
#include <cstdlib> #include <iostream> int main() { int* huge = (int*)malloc(1000000000000); if (huge == NULL) { // 或 if (!huge) std::cerr << "malloc failed" << std::endl; } }七、配对使用规则
7.1 必须正确配对
// ========== 正确的配对 ========== // 单个对象 int* p1 = new int(42); delete p1; // new 配 delete // 对象数组 int* p2 = new int[10]; delete[] p2; // new[] 配 delete[] // malloc/free int* p3 = (int*)malloc(sizeof(int)); free(p3); // malloc 配 free // ========== 错误的配对(未定义行为!) ========== // 用 delete 释放 malloc 的内存 int* p4 = (int*)malloc(sizeof(int)); // delete p4; // 危险!未定义行为 // 用 free 释放 new 的内存 int* p5 = new int; // free(p5); // 危险!不调用析构函数,未定义行为 // 用 delete 释放 new[] 分配的内存 int* p6 = new int[10]; // delete p6; // 危险!可能只释放一个元素的内存 // 用 delete[] 释放 new 分配的内存 int* p7 = new int; // delete[] p7; // 危险!未定义行为7.2 配对关系图
八、重载能力
8.1 new/delete 可以重载
#include <iostream> #include <new> class MyClass { public: // 重载类的 operator new static void* operator new(size_t size) { std::cout << "Custom new: allocating " << size << " bytes" << std::endl; return ::operator new(size); // 调用全局 new } // 重载类的 operator delete static void operator delete(void* ptr) { std::cout << "Custom delete: freeing memory" << std::endl; ::operator delete(ptr); } int data[100]; // 使对象变大以便演示 }; int main() { MyClass* obj = new MyClass(); delete obj; // 输出: // Custom new: allocating 400 bytes // Custom delete: freeing memory }8.2 malloc/free 不能重载
malloc/free是 C 标准库函数,无法通过 C++ 机制重载(虽然可以在链接时替换实现,如使用 jemalloc、tcmalloc 等分配器)。
九、使用场景与选择
9.1 何时必须使用 new/delete
// 场景一:需要构造和析构的非平凡类型 class ResourceHolder { std::string data; FILE* fp; public: ResourceHolder() : fp(fopen("config.txt", "r")) { } ~ResourceHolder() { if (fp) fclose(fp); } }; ResourceHolder* rh = new ResourceHolder(); // 必须用 new 调用构造函数 delete rh; // 必须用 delete 调用析构函数 // 场景二:使用智能指针(内部使用 new) std::unique_ptr<int> ptr = std::make_unique<int>(42);9.2 何时可以使用 malloc/free
// 场景一:纯粹的内存缓冲区(无构造/析构需求) char* buffer = (char*)malloc(4096); // 使用 buffer... free(buffer); // 场景二:与 C API 交互 void* cStyleBuffer = malloc(1024); some_c_function(cStyleBuffer); free(cStyleBuffer); // 场景三:需要 realloc 调整大小 int* arr = (int*)malloc(10 * sizeof(int)); // ... arr = (int*)realloc(arr, 20 * sizeof(int)); // C++ 没有直接等价物 free(arr);9.3 现代 C++ 的推荐方式
// 在现代 C++ 中,很少需要直接使用 new/delete 或 malloc/free // 优先使用智能指针 auto ptr1 = std::make_unique<int>(42); auto ptr2 = std::make_shared<std::string>("Hello"); // 优先使用容器 std::vector<int> vec(1000); std::string str("Hello World"); // 需要自定义删除器时 struct FreeDeleter { void operator()(void* p) const { free(p); } }; std::unique_ptr<int, FreeDeleter> cMem((int*)malloc(sizeof(int)));十、底层内存流程对比
十一、总结
new/delete与malloc/free的区别可以浓缩为以下核心要点:
- 构造与析构是根本分水岭:
new分配内存的同时调用构造函数完成对象初始化,delete释放内存前调用析构函数完成资源清理。malloc/free仅处理原始内存的分配与释放,不关心对象语义。对于任何包含非平凡构造函数/析构函数的类,必须使用new/delete。
- 类型安全与便捷性:
new根据类型自动计算大小、返回正确类型的指针;malloc需要手动计算字节数、返回void*需要强制转换。new的出错处理基于异常(std::bad_alloc),malloc基于返回NULL。
- 严格配对规则:
new配对delete,new[]配对delete[],malloc配对free。绝对不可以混用,否则结果是未定义行为——可能内存泄漏、可能程序崩溃、可能数据损坏。
- 现代 C++ 实践:直接使用
new/delete已经逐渐被视为不良实践。现代 C++ 推荐使用std::make_unique/std::make_shared管理动态对象,使用std::vector/std::string等容器管理动态数组。malloc/free应限制在与 C API 交互、自定义内存分配器等底层场景中。
一条简单的记忆法则:如果你的类型有构造函数或析构函数(或者说它是 C++ 的class/struct),用new/delete;如果你只是需要一块原始的内存缓冲区(像 C 语言那样),可以用malloc/free,但更好的做法是使用std::vector<char>或std::make_unique<char[]>。
