news 2026/7/29 14:39:18

C++ 中 new/delete 与 malloc/free 的区别:从内存分配到对象生命周期

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 构造与析构的本质区别


对象构造堆内存代码对象构造堆内存代码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_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 配对关系图


C 方式

C++ 方式

必须配对

必须配对

必须配对

必须配对

必须配对

禁止混用

禁止混用

new

delete

new[]

delete[]

malloc

free

calloc

free

realloc

free


八、重载能力


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)));


十、底层内存流程对比


free 的流程

free 函数

将内存归还操作系统

malloc 的流程

malloc 函数

向操作系统申请内存

返回 void* 指针

delete 的完整流程

delete 表达式

调用析构函数
(清理资源)

调用 operator delete
(可重载,通常调用 free)

释放内存

new 的完整流程

new 表达式

调用 operator new
(可重载,通常调用 malloc)

分配原始内存

调用构造函数
(初始化对象)

返回正确类型指针


十一、总结


new/deletemalloc/free的区别可以浓缩为以下核心要点:


  1. 构造与析构是根本分水岭new分配内存的同时调用构造函数完成对象初始化,delete释放内存前调用析构函数完成资源清理。malloc/free仅处理原始内存的分配与释放,不关心对象语义。对于任何包含非平凡构造函数/析构函数的类,必须使用new/delete


  1. 类型安全与便捷性new根据类型自动计算大小、返回正确类型的指针;malloc需要手动计算字节数、返回void*需要强制转换。new的出错处理基于异常(std::bad_alloc),malloc基于返回NULL


  1. 严格配对规则new配对deletenew[]配对delete[]malloc配对free绝对不可以混用,否则结果是未定义行为——可能内存泄漏、可能程序崩溃、可能数据损坏。


  1. 现代 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[]>

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/29 14:37:23

桶形移位器:数字电路中的高效数据搬运工

1. 从“桶”说起&#xff1a;一个被低估的数字电路核心在数字电路和处理器设计的圈子里&#xff0c;有几个名字是绕不开的&#xff1a;加法器、乘法器、寄存器。但还有一个家伙&#xff0c;它不像加法器那样是算术运算的绝对主角&#xff0c;也不像乘法器那样计算复杂、引人注目…

作者头像 李华
网站建设 2026/7/29 14:37:19

TI SimpleLink芯片供应商认证与OTP配置实战指南

1. 项目概述与核心价值在物联网设备开发中&#xff0c;安全启动和固件认证是构建产品信任基石的起点。想象一下&#xff0c;你生产的智能门锁、工业传感器或医疗设备&#xff0c;如果启动时加载的代码可以被任意替换&#xff0c;后果将不堪设想。德州仪器&#xff08;TI&#x…

作者头像 李华
网站建设 2026/7/29 14:37:08

ESP32 Socket 3.1.19深度解析:架构、实战避坑与资源优化

1. 项目概述&#xff1a;为什么是Socket 3.1.19&#xff1f; 如果你正在用ESP32做物联网项目&#xff0c;尤其是涉及到网络通信&#xff0c;比如连接MQTT服务器、发送HTTP请求或者搭建一个简单的Web服务器&#xff0c;那你大概率绕不开一个核心组件——Socket。今天我们不聊那些…

作者头像 李华
网站建设 2026/7/29 14:36:16

如何在iOS和macOS设备上免费运行Windows和Linux虚拟机?UTM完整指南

如何在iOS和macOS设备上免费运行Windows和Linux虚拟机&#xff1f;UTM完整指南 【免费下载链接】UTM Virtual machines for iOS and macOS 项目地址: https://gitcode.com/gh_mirrors/ut/UTM UTM是一款专为苹果生态系统设计的强大虚拟机软件&#xff0c;让你在iPhone、i…

作者头像 李华
网站建设 2026/7/29 14:34:45

弹性GPU算力平台怎么选?从万卡智算到自动扩缩容的全维度选型指

一、引言&#xff1a;弹性伸缩GPU算力为何成为AI时代的核心基础设施随着大模型训练与推理需求呈指数级增长&#xff0c;GPU算力已成为人工智能产业发展中最核心的生产力要素。然而&#xff0c;企业自建GPU集群面临投入成本高、资源利用率低、扩容周期长等现实困境&#xff0c;传…

作者头像 李华