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 必须配对 deletenew[] 必须配对 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 Im 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) Im Alice, 30 years old. Destructor: Alice malloc/free (没有任何构造或析构输出)3.2 构造与析构的本质区别对象构造堆内存代码对象构造堆内存代码new Person 的过程delete Person 的过程malloc 的过程free 的过程1. operator new 分配原始内存2. 调用构造函数初始化对象返回初始化完成的对象指针1. 调用析构函数清理资源2. operator delete 释放内存仅分配原始内存(无构造)返回 void* 原始内存指针仅释放内存(无析构)四、类型安全性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_castint*(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 配对关系图C 方式C 方式必须配对必须配对必须配对必须配对必须配对禁止混用禁止混用newdeletenew[]delete[]mallocfreecallocfreereallocfree八、重载能力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_ptrint ptr std::make_uniqueint(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_uniqueint(42); auto ptr2 std::make_sharedstd::string(Hello); // 优先使用容器 std::vectorint vec(1000); std::string str(Hello World); // 需要自定义删除器时 struct FreeDeleter { void operator()(void* p) const { free(p); } }; std::unique_ptrint, FreeDeleter cMem((int*)malloc(sizeof(int)));十、底层内存流程对比free 的流程free 函数将内存归还操作系统malloc 的流程malloc 函数向操作系统申请内存返回 void* 指针delete 的完整流程delete 表达式调用析构函数(清理资源)调用 operator delete(可重载通常调用 free)释放内存new 的完整流程new 表达式调用 operator new(可重载通常调用 malloc)分配原始内存调用构造函数(初始化对象)返回正确类型指针十一、总结new/delete与malloc/free的区别可以浓缩为以下核心要点构造与析构是根本分水岭new分配内存的同时调用构造函数完成对象初始化delete释放内存前调用析构函数完成资源清理。malloc/free仅处理原始内存的分配与释放不关心对象语义。对于任何包含非平凡构造函数/析构函数的类必须使用new/delete。类型安全与便捷性new根据类型自动计算大小、返回正确类型的指针malloc需要手动计算字节数、返回void*需要强制转换。new的出错处理基于异常(std::bad_alloc)malloc基于返回NULL。严格配对规则new配对deletenew[]配对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::vectorchar或std::make_uniquechar[]。