当前位置: 首页 > news >正文

把 C++ 内存分配拆透:new 与 malloc 的三层血缘

要理清 C++ 中new系列函数与 C 语言malloc/realloc的底层关系,核心是先拆分层级—— 很多人混淆了「new 表达式」和「operator new」,两者完全不是一回事。我们从上到下拆解,把调用链、区别、边界问题一次讲透。


一、先厘清三层概念:不要把「new」混为一谈

日常说的「new」实际是三层调用关系,自上而下:

表格

层级名称职责是否可重载
最上层new 表达式(new operator)用户写的new T(),语言内置语法不可重载,行为固定
中间层operator new只负责分配原始内存字节的全局 / 类函数支持全局重载、类成员重载
最底层系统内存分配真正向操作系统申请内存由标准库 / OS 实现

默认调用链new T()→ 调用operator new分配内存 → 调用对象构造函数 其中默认的全局operator new,底层就是直接调用malloc


二、operator new 与 malloc 的底层关联

1. 默认实现:operator new 本质是 malloc 的 C++ 封装

C++ 标准没有强制规定operator new的底层实现,但GCC/libstdc++、Clang/libc++、MSVC 所有主流编译器的默认全局实现,都是通过malloc分配内存

简化版伪代码如下,可直观看到两者的关系:

cpp

运行

#include <cstdlib> #include <new> // 全局 operator new 默认实现(核心逻辑) void* operator new(std::size_t size) { if (size == 0) size = 1; // 标准要求:0字节也需返回有效指针 while (true) { void* ptr = malloc(size); // 核心:底层调用 malloc 分配字节 if (ptr != nullptr) return ptr; // 分配失败:C++ 特有逻辑——调用 new_handler 重试 std::new_handler handler = std::get_new_handler(); if (!handler) throw std::bad_alloc{}; // 无处理函数则抛异常 handler(); // 执行用户注册的内存释放逻辑,然后重试 } } // 对应的 operator delete 默认实现 void operator delete(void* ptr) noexcept { if (ptr) free(ptr); // 底层直接调用 free }

对应的nothrow版本(失败返回空指针,不抛异常)底层同样走malloc,只是失败时直接返回nullptr

2. 两者的核心差异:同源但职责不同

虽然底层都是malloc申请内存,但上层语义天差地别:

表格

维度malloc(C 标准库)operator new(C++)
失败行为返回NULL默认抛出std::bad_alloc异常
返回值void*,需手动强转void*(operator new 本身)/ 类型指针(new 表达式)
核心职责仅分配原始字节内存仅分配原始字节内存
对象处理完全不涉及对象生命周期new 表达式会额外调用构造函数
扩容能力配套realloc可调整内存大小无原生配套扩容函数
可定制性不可重载支持全局重载、类成员重载
对齐支持满足标准最大对齐C++17 后支持超对齐分配(align_val_t重载)

3. 什么时候 new 和 malloc 毫无关系?

只要你重载了operator new,就可以完全绕开malloc

  • 全局重载:自己实现全局operator new,直接调用brk/mmap等系统调用,或接入自研内存池
  • 类成员重载:给特定类单独重载operator new,用预分配内存池减少 malloc 开销
  • 自定义分配器:STL 容器通过allocator分配内存,可完全脱离 malloc

这种场景下,new 只是一套内存分配接口,底层和 C 语言的 malloc 没有任何关联。


三、realloc 在 C++ 中的处境:为什么没有「new 版 realloc」

C 语言中realloc可以原地扩容、或异地搬迁内存,是非常实用的内存调整函数。但 C++ 标准中没有对应的realloc new,这不是遗漏,而是刻意设计。

1. 直接用 realloc 处理 C++ 对象是未定义行为

realloc调整内存时,是按字节直接memcpy/memmove拷贝数据,它完全不感知 C++ 对象的生命周期:

  • 旧地址的对象不会调用析构函数,可能造成资源泄漏
  • 新地址的内存不会调用构造 / 移动构造函数,对象状态无效
  • 含虚函数、动态成员、非平凡拷贝的类,字节拷贝会直接破坏对象布局

结论:只有 POD(平凡标准布局)类型可以勉强安全使用realloc,对绝大多数 C++ 类来说,直接realloc都是严重的未定义行为。

2. C++ 「安全扩容」的标准做法

C++ 的扩容逻辑是「分配新内存 → 迁移对象 → 销毁旧对象 → 释放旧内存」,这也是std::vector等容器的底层扩容原理:

cpp

运行

// 模拟 vector 扩容的核心逻辑(简化版) T* old_data; // 旧内存指针 size_t old_size; // 旧对象数量 // 1. 分配新的原始内存 T* new_data = static_cast<T*>(operator new(new_capacity * sizeof(T))); // 2. 逐个迁移对象(移动构造) for (size_t i = 0; i < old_size; ++i) { new (&new_data[i]) T(std::move(old_data[i])); // 新地址构造 old_data[i].~T(); // 旧地址析构 } // 3. 释放旧内存 operator delete(old_data); old_data = new_data;

这个过程等价于「C++ 安全版 realloc」,但它处理的是对象,而非纯字节。

3. 纯内存场景能否混用 realloc?

如果你只是用operator new分配了原始字节缓冲区(从未构造任何对象),理论上默认实现下可以用realloc调整大小,但这属于未定义行为—— 标准不保证operator new底层一定是malloc

工程上的最佳实践:要么全程用malloc/free/realloc处理纯字节,要么全程用 C++ 分配接口,不要混用。


四、其他分配函数的对应关系

1. new [] /delete [] 与 malloc

new T[n]是数组分配,流程为:

  1. 调用operator new[]分配内存(默认底层同样是malloc
  2. 循环调用 n 次默认构造函数 对应的delete[]会先循环调用析构函数,再通过operator delete[]释放内存(底层free)。

2. placement new 与 malloc

placement new(定位放置 new)不分配任何内存,它的作用是在一块已有的内存上调用对象的构造函数:

cpp

运行

#include <new> void* buf = malloc(sizeof(T)); // 内存可以来自 malloc、栈、内存池 T* obj = new (buf) T(); // placement new:在 buf 上构造对象

它和 malloc 没有必然调用关系,只是常配合 malloc 手动管理对象生命周期。

3. 对齐内存分配

  • C11 提供aligned_alloc用于超对齐内存分配
  • C++17 新增带std::align_val_t参数的operator new重载,底层通常调用aligned_allocposix_memalign,而非普通malloc

五、总结:完整调用关系图谱

默认实现下的完整调用链

plaintext

new / new[] 表达式 ↓ 先分配内存,再调用构造 operator new / operator new[] ↓ 默认调用 malloc / aligned_alloc ↓ 系统调用 brk / mmap(内核内存申请)

plaintext

delete / delete[] 表达式 ↓ 先调用析构,再释放内存 operator delete / operator delete[] ↓ 默认调用 free ↓ 系统调用 brk / munmap(内核内存释放)

核心结论

  1. 默认场景同源:C++ 的new底层默认就是malloc,只是在之上增加了异常处理、对象构造析构等 C++ 语义。
  2. realloc 无直接对应:C++ 没有原生 realloc,因为对象不能按字节随意搬迁;扩容需通过「重新分配 + 对象迁移」实现。
  3. 可完全解耦:通过重载operator new或自定义分配器,C++ 内存分配可以完全脱离 malloc。
谢谢
http://www.jsqmd.com/news/1251289/

相关文章:

  • AI大模型开发:高薪程序员的技术转型指南
  • 地陪行业复购率低迷?深度拆解会员运营的底层逻辑
  • MSP430寻址模式深度解析:从原理到嵌入式开发实战优化
  • AI提示词精简法则:高效交互的关键技巧
  • AI Agent Harness实现内容合规自动化检查的技术解析
  • Claude的AgentSkill扩展:AI能力边界突破与实践
  • 千笔与云笔AI写作工具横向评测:降AIGC技术对比
  • 抖音GIF动图怎么去水印?2026动图水印去除方法实测 - 免费软件工具方法教程
  • 2026年山东区域选玻璃钢夹砂管厂哪家好实用选购指南 - 品牌优推
  • 郑州本地金饰变现 首饰黄金回收店铺电话多少正确查询方法 - 品牌优推
  • 【题解-信息学奥赛一本通】1361:产生数(Produce)
  • C++:如何指定应用开机自启动
  • Linux终端命令rust command not found
  • WinUI 3 LiquidGlassBrush玻璃效果笔刷实战:原理、集成与性能优化
  • 阳江选购靠谱国产集成电路源头厂家实用参考指南 - 品牌优推
  • AI工具提升论文写作效率:从文献综述到查重降重
  • 后端转行AI Agent开发必看:2026 Agent岗位缺人但挑人,这3类人慎入!
  • 论文查重困境与智能降重技术解析
  • AI技术动态:Gemini 1.5与Copilot企业应用解析
  • 宠物用品行业豆包推广公司联系方式GEGEO.CN - 品牌深度评测
  • 台州本地防水补漏精选TOP5推荐:正规漏水检测维修公司上门师傅推荐:厕所/棚顶/屋面/飘窗/阳台/地下室/厨房渗漏水精准测漏维修(2026最新) - 即刻修防水
  • MSP430FE42x在单相电能计量中的超低功耗与高精度设计实践
  • AI工程师学习路径与大模型实战指南
  • 从传统开发转型AI大模型工程师的实战指南
  • 知识城全屋定制哪家靠谱:派福装饰高端定制 - MXyuyu
  • SAR ADC评估板实战指南:从硬件配置到性能测试全解析
  • 基于模型预测人工势场的船舶运动规划方法,考虑复杂遭遇场景下的COLREG附Matlab代码
  • 2026年还在找免费好用的抖音去水印教程?这篇实测全指南帮你整理好了 - 耶斯去水印
  • 基于计算机视觉的仓储安全风险预警系统设计与实践
  • 2026家电装饰面板模内注塑厂家选择维度评测 - 起跑123