首页/新闻资讯/正文详情

C++ 内存管理:new 与 delete 详解——从语法到底层 operator new 与 malloc

发布时间:2026/11/5 2:41:57 来源:云帆数科 栏目:资讯中心
C++ 内存管理:new 与 delete 详解——从语法到底层 operator new 与 malloc
在 C 中new和delete是最常用的动态内存管理手段。但说不清它到底做了什么为什么new会调用构造函数而malloc不会operator new和malloc又是什么关系本文从语法、语义、底层调链三个层次把这条链路彻底讲清楚。一、C 的内存管理在讲new和delete之前先要弄清楚一个程序的内存到底被划分成了哪几块。C 程序运行时的内存通常可以划分为以下几个区域栈区、堆区、全局/静态区、常量区、代码区。不同区域的生命周期、分配方式和访问速度都不一样理解它们的差异是掌握内存管理的第一步。1.1 内存分区总览区域存放内容分配/释放方式生命周期栈区Stack局部变量、函数参数、返回地址编译器自动分配和释放随函数调用结束而销毁堆区Heapnew/malloc动态申请的内存程序员手动申请和释放从申请到释放为止全局/静态区全局变量、静态变量static程序启动时分配结束时释放整个程序运行期常量区字符串字面量、const全局常量编译期确定只读整个程序运行期代码区函数体的机器指令编译期确定只读整个程序运行期//内存管理 int globalVar 1; //全局变量存放在静态区 static int staticGlobalVar 1;//存放在静态区 void Test() { static int staticVar 1;//存放在静态区 int localVar 1;//存放在栈 int num1[10] { 1, 2, 3, 4 };//存放在栈 char char2[] abcd;//存放在栈*char2存放在栈 const char* pChar3 abcd;//存放在栈*pChar3存放在常量区 int* ptr1 (int*)malloc(sizeof(int) * 4);//ptr1存放在栈*ptr1 存放在堆 int* ptr2 (int*)calloc(4, sizeof(int)); int* ptr3 (int*)realloc(ptr2, sizeof(int) * 4); free(ptr1); free(ptr3); }1.2 从内存分区看 new 与 delete有了上面的分区概念再来看new和delete的完整链路就清晰了new从堆区拿到原始内存再在这块内存上构造对象delete先析构对象再把内存还给堆管理器。flowchart TD A[new T(args)] -- B[operator new(sizeof(T))] B -- C[malloc(size)] C -- D[操作系统堆 / 内存池] B -- E[返回 void* 原始内存] E -- F[在内存上调用 T 的构造函数] F -- G[返回 T*] H[delete p] -- I[调用 T 的析构函数] I -- J[operator delete(p)] J -- K[free(p)]一句话总结new 分配内存operator new → malloc 构造对象delete 析构对象 释放内存operator delete → free。1.3 C 语言的动态内存malloc / calloc / reallocC 的operator new底层通常就是调用malloc所以有必要把 C 的这套动态内存函数讲清楚。它们都声明在cstdlib中操作的都是堆区的原始字节内存不涉及构造和析构。函数原型是否初始化用途mallocvoid* malloc(size_t size)否内容为随机值申请指定字节数的内存callocvoid* calloc(size_t n, size_t size)是全部清零申请 n 个元素、每个 size 字节并清零reallocvoid* realloc(void* ptr, size_t size)新增部分不初始化调整已分配内存的大小malloc只负责分配不初始化。返回的指针需要强制转换失败返回nullptr。int* p (int*)malloc(10 * sizeof(int)); // 申请 10 个 int 的空间 if (p NULL) { // 处理分配失败 } // 此时 p 指向的内容是未定义的 free(p); // 释放calloc等价于malloc之后再把内存清零适合需要初始值为 0 的场景如数组、哈希表。int* p (int*)calloc(10, sizeof(int)); // 10 个 int全部为 0 free(p);realloc用于扩大或缩小一块已分配的内存。它的行为需要特别注意如果原内存后面有足够空间可能原地扩展返回原指针如果空间不够会分配一块新内存把旧数据拷贝过去释放旧内存返回新指针如果size为 0行为由实现定义可能返回nullptr或一个可释放的指针如果失败返回nullptr原内存保持不变不会丢失。int* p (int*)malloc(10 * sizeof(int)); int* q (int*)realloc(p, 20 * sizeof(int)); if (q NULL) { // 扩容失败p 仍然有效需要自行 free(p) free(p); } else { p q; // 扩容成功使用新指针 } free(p);常见错误直接写p realloc(p, newSize)。一旦realloc失败返回nullptr原来的p就被覆盖旧内存再也无法释放造成内存泄漏。1.4 malloc 的实现原理malloc并不是每次都直接向操作系统要内存而是由 C 库在用户态维护一个堆管理器通过空闲链表等数据结构管理已申请的内存块。其核心思路大致如下内存块头部每次分配时在返回给用户的地址前面额外存一小段元数据块大小、是否空闲等这就是所谓的 chunk header。空闲链表堆管理器把空闲块按大小组织成链表如 glibc 的 bin 机制分配时从合适的链表中找一块够用的内存。分割与合并找到的空闲块如果比请求大就分割成“已分配部分 剩余空闲部分”释放时若相邻块空闲则合并减少内存碎片。向操作系统申请当空闲链表里没有合适的块时通过brk/sbrk小块或mmap大块向内核申请新的内存区域。// 简化示意malloc 的核心逻辑 void* malloc(size_t size) { if (size 0) return NULL; size align_up(size); // 按对齐要求向上取整 Block* blk find_free_block(size); // 在空闲链表中查找 if (blk NULL) { blk extend_heap(size); // 没有合适的块向系统申请 if (blk NULL) return NULL; } split_block(blk, size); // 分割剩余部分放回空闲链表 blk-free 0; return (void*)(blk 1); // 跳过头部返回用户可用地址 }正因为有头部元数据和空闲链表free只需要传入用户指针就能通过指针偏移找到块头从而知道要释放多大的内存。这也解释了为什么malloc和free必须严格配对且不能释放栈上或常量区的地址。1.5 malloc 与 new 的关系把两者放在一起对比就能看清 C 和 C 在内存管理上的分工malloc/free只管理原始字节内存不调用构造/析构是 C 的接口new/delete在malloc/free之上增加了构造与析构是 C 的运算符默认的operator new内部就是调用malloc失败时转成异常因此new分配的内存不能用free释放malloc分配的内存也不能用delete释放。理解了内存分区和malloc系列函数的原理再往下看new/delete的展开过程就会非常自然。二、new 与 delete 的基本用法2.1 单个对象#include iostream class Widget { public: Widget() { std::cout Widget 构造\n; } ~Widget() { std::cout Widget 析构\n; } void run() { std::cout run\n; } }; int main() { Widget* w new Widget(); // 分配 构造 w-run(); delete w; // 析构 释放 return 0; }2.2 数组int* arr1 new int[3]; // 分配 3 个对象的内存并逐个构造 int* arr2 new int(1); // 分配 1 个对象内存并将其初始化为1 int* arr3 new int[10]{1,2,3,4,5}; //前五个初始化为1-5后面数字默认为0 delete[] arr; // 逐个析构再释放整块内存必须配对new配deletenew[]配delete[]。用delete释放new[]的内存是未定义行为在多数实现上会导致只析构第一个对象甚至堆损坏。对于错误匹配问题对于内置类型来说使用new开空间使用free来释放没有任何内存泄露对于自定义类型使用new开空间使用free来释放不会调用析构函数有内存泄露的风险对于内置类型使用new[]来开空间使用free释放不会有内存泄露底层都是malloc和free对于自定义类型使用new[]来开空间使用delete来释放空间当该类有析构函数时编译器会在空间前面多开4字节的空间来存储到底要调用几次析构函数此时使用delete来释放空间前面的四个字节不会被释放造成内存泄露当该类没有析构函数时delete也不会报错。//new/delete class A { public: A(int a 0,int b 0) :_a(1) , _b(1) { cout A() endl; } ~A() { cout ~A() endl; } private: int _a; int _b; }; void Test2() { //注意是使用花括号 A* ptr1 new A[2]{ { 1,1 }, { 1,2 } };//使用多参数隐式类型转换 A* ptr2 new A[2]{ A(1, 1), A(1, 2) };//使用匿名对象 }2.3 定位 newplacement new定位 new 不分配内存只在一块已有内存上显式调用构造和析构函数这是理解“分配与构造分离”的最好例子。A* ptr3 (A*)operator new(sizeof(A));//开空间 new(ptr3)A(1,2);//构造 ptr3-~A();//手动析构 operator delete(ptr3);//释放空间三、new 到底做了什么编译器会把new Widget()展开成大致如下的伪代码// 1. 分配原始内存 void* mem operator new(sizeof(Widget)); // 2. 在内存上构造对象 Widget* w; try { w new (mem) Widget(); // 定位 new 调用构造函数 } catch (...) { operator delete(mem); // 构造抛异常则释放内存 throw; }关键点有三个分配与构造分离operator new只负责拿到一块字节内存构造函数负责把这块内存变成对象。异常安全如果构造函数抛异常编译器会自动调用operator delete回收内存不会泄漏。类型安全new返回的是Widget*不需要强制转换而malloc返回void*。new/delete 和 malloc/free 的最大区别就是对于自定义类型来说除了开空间 new/delete 会去自动调用它的构造和析构函数内置类型无差别四、operator new 与 operator delete4.1 它们是什么operator new是 C 标准库提供的全局函数不是运算符重载意义上的“运算符”它的职责只有一个分配指定字节数的原始内存。// 标准库中的声明简化 void* operator new(std::size_t size); void* operator new[](std::size_t size); void operator delete(void* ptr) noexcept; void operator delete[](void* ptr) noexcept;4.2 申请失败默认的operator new就是malloc的一层包装malloc 申请空间失败默认返回0new是通过抛异常实现的符合它面向对象的特性。4.3 与 malloc 的对比对比项new / deletemalloc / free所属语言C 运算符C 库函数是否调用构造/析构是否返回类型具体类型指针void*需强转大小计算编译器自动计算手动传字节数失败行为抛std::bad_alloc返回nullptr能否重载可以重载operator new不可重载底层实现通常调用malloc直接向堆管理器申请五、delete 做了什么// delete w; 大致展开为 w-~Widget(); // 1. 调用析构函数 operator delete(w); // 2. 释放内存 → free(w)对于delete[]编译器需要知道数组长度才能逐个析构。因此new[]在分配时通常会多申请几个字节把元素个数cookie存在返回地址之前delete[]读取这个 cookie 后逆序析构所有元素再调用operator delete[]释放整块内存。这也是为什么new[]必须配delete[]用delete只会析构第一个元素且传给free的指针可能不是malloc返回的原始地址直接导致堆损坏。六、总结把整条链路再回顾一遍new T先调用operator new(sizeof(T))拿到原始内存默认的operator new内部调用malloc失败时走new_handler并最终抛std::bad_alloc拿到内存后编译器在内存上调用构造函数构造失败会自动回收内存delete先调用析构函数再调用operator delete最终落到free。理解了“分配与构造分离”这一核心思想就能明白为什么operator new可以被重载、为什么定位 new 存在、以及为什么new[]必须配delete[]。这也是从“会用 new”走向“理解 C 内存模型”的关键一步。

相关推荐

蓝牙技术的工作频段与基本通信原理
蓝牙技术的工作频段与基本通信原理

《蓝牙技术学习与实践》系列博客总导航 一、引言 在日常生活中,我们经常使用蓝牙耳机、蓝牙音箱、无线键盘、智能手环等设备。这些设备不需要通过网线连接,也不需要像 Wi-Fi 那样依赖无线局域网路由器,就能够在一定距离内完成数据传输。 那… · 2026/11/4 17:57:07

Agent 上下文漂移与 Reflexion 陷阱——用实验量化“加反思就好了“的成立边界
Agent 上下文漂移与 Reflexion 陷阱——用实验量化“加反思就好了“的成立边界

面试场景 面试官先让你讲 ReAct,然后追问:“ReAct 有什么工程缺陷?” 你说上下文漂移。他再问:“那加 Reflexion 是不是就解决了?” 如果你说"是",就掉进了陷阱。Reflexion 不是免费的,它有一个硬前提——可靠的评估器。 底层知识 ReAct 的核心缺陷:上下文… · 2026/11/3 14:28:28

SAP TechEd 2026 之后,ABAP 开发正在从 AI 辅助走向 Agentic AI
SAP TechEd 2026 之后,ABAP 开发正在从 AI 辅助走向 Agentic AI

2026 年 10 月的 SAP TechEd Berlin,有一条关于 ABAP 的变化很容易被低估。 过去两三年,我们谈 ABAP 和生成式 AI,关注点大多还是 Copilot 式体验。开发人员写一句需求,AI 帮忙生成一段代码,解释一个 Class,补一段 ABAP Unit,或者告诉我们某个 CDS View 为什么激活失败… · 2026/11/5 0:35:27

可自部署10分钟临时邮箱:SMTP收信、TTL清理与验证码提取
可自部署10分钟临时邮箱:SMTP收信、TTL清理与验证码提取

简介:面向开发者与Web学习者的10分钟临时邮箱源码包,围绕短时有效邮箱地址的生成、收信与到期失效流程,适合用于注册验证、下载试用等不愿暴露真实邮箱的场景,也可作为PHP邮件系统与Web服务设计的实践项目。压缩包共492个文件&… · 2026/11/5 2:41:02

DataGrip连接达梦数据库的完整配置指南
DataGrip连接达梦数据库的完整配置指南

1. 项目概述最近在帮客户做数据迁移时遇到了一个典型场景:需要连接达梦数据库(DM)进行数据操作,但发现DataGrip这款主流数据库IDE并没有预置DM的驱动配置。这其实是个挺常见的痛点——很多国产数据库或小众数据库在主流工具中缺乏原生支持。经过一番摸索… · 2026/11/5 2:41:02

Proxmox VE 7 apt-get update 100错误排查
Proxmox VE 7 apt-get update 100错误排查

很多人在 Proxmox VE 7 上装完系统、打开网页面板、新建第一台虚拟机,回头在 shell 里敲一句apt-get update,结果被一屏红色输出糊了一脸,最后一行写着E: Some index files failed to download...或者干脆只留一句apt-get update failed: exi… · 2026/11/5 2:41:02

DataGrip连接达梦数据库(DM8)实战指南
DataGrip连接达梦数据库(DM8)实战指南

1. 项目概述作为一名长期与数据库打交道的开发者,我深知连接非主流数据库时的痛苦。达梦数据库(DM)作为国产数据库的代表之一,在企业级应用中越来越常见,但很多开发者第一次连接时总会遇到各种问题。本文将基于DataGrip这款强大的数据库管理工… · 2026/11/5 2:41:02

MySQL 5.7安装与配置全指南:从入门到生产环境部署
MySQL 5.7安装与配置全指南:从入门到生产环境部署

1. MySQL 5.7安装前的认知准备MySQL作为全球最流行的开源关系型数据库,5.7版本因其稳定性与性能平衡成为众多企业的首选。这个版本首次引入了原生JSON支持、多源复制等企业级功能,同时优化了InnoDB存储引擎的性能。在开始安装前,需要明确几个… · 2026/11/5 2:41:02

Blender渲染参数与出图流程:引擎、采样、光程、降噪与提速
Blender渲染参数与出图流程:引擎、采样、光程、降噪与提速

1. 先把“渲染”这件事拆成三层来看很多人第一次点开 Blender 的渲染面板,会被密密麻麻的折叠菜单劝退,其实真正影响一张简单静物图成色的,就那么十来个参数。渲染在 Blender 里不是“一个按钮”,而是三层不同性质的东西叠在一起&… · 2026/11/5 2:40:02

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38

多智能体协作系统架构设计与实践优化
多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中… · 2026/11/5 0:00:39

大模型系统性入门实战指南:从原理到部署微调的全链路路线
大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#… · 2026/11/5 0:00:39

鸿蒙文件系统沙箱机制解析与开发实践
鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的… · 2026/11/5 0:00:39

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码