写了5年C++才发现:new背后藏着两个函数,placement new让我能控制其中一个

new。

C++程序员每天都在用,int* p = new int(42);这行代码简单直接,分配内存、构造对象一步到位,但你有没有想过,这一行代码背后到底发生了什么?

很多人以为new是一个操作。错了。new是两个操作,第一个操作分配内存,第二个操作调用构造函数,这两个操作可以拆开、可以分别控制,自定义operator new/delete让你控制第一步,而placement new让你跳过第一步、直接在已有内存上执行第二步——搞懂这个,你对C++内存管理的理解会上一个台阶。


new表达式的真相:两步操作

当你写T* p = new T(args);的时候,编译器实际上做了两件事:

// 你写的代码T*p=newT(42);// 编译器实际做的事(伪代码)void*mem=operatornew(sizeof(T));// 第一步:分配内存T*p=new(mem)T(42);// 第二步:在内存上构造对象

第一步调用operator new函数分配一块大小为sizeof(T)的原始内存,这块内存还没有任何对象、就是一块字节;第二步在这块内存上调用构造函数,把字节变成真正的对象——这两步是可以拆开的。

为什么要拆开?场景很多:比如你想用自己的内存池,内存已经分配好了,只需要在上面构造对象;比如你想追踪所有的内存分配,统计程序用了多少内存;比如你想让某个类的对象只能从特定的内存区域分配——这些需求都需要把分配和构造拆开,分别控制。


placement new:在已有内存上构造对象

placement new是new的一种特殊形式,语法是new(ptr) T(args),它不分配内存,只在ptr指向的内存上构造对象。

#include<new>// placement new需要这个头文件// 先分配一块原始内存(alignas确保对齐)alignas(int)charbuffer[sizeof(int)];// 在这块内存上构造一个int对象int*p=new(buffer)int(42);// 现在buffer这块内存里有一个值为42的int对象

注意对齐问题:上面用了alignas(int)来确保buffer满足int的对齐要求。如果不加alignas,char数组的对齐是1字节,而int通常需要4字节对齐,在某些平台上可能导致未定义行为。对于更复杂的类型(比如包含double或SIMD类型的结构体),对齐问题更严重,务必使用alignas。

注意这里的关键:buffer是一块字节,placement new在上面"放置"了一个int对象,这个int对象占用的就是buffer的内存,不会额外分配,这就是placement new的核心作用——把对象构造和内存分配彻底分离开来。

更常见的用法是配合内存池:

// 简化的内存池示例(生产代码需要更完善的实现)classMemoryPool{char*pool_;size_t offset_=0;size_t size_;public:MemoryPool(size_t size):pool_(newchar[size]),size_(size){}~MemoryPool(){delete[]pool_;}// 别忘了释放void*allocate(size_t bytes){// 生产代码还需要:对齐处理、边界检查、线程安全if(offset_+bytes>size_)returnnullptr;void*ptr=pool_+offset_;offset_+=bytes;returnptr;}};MemoryPoolpool(1024);void*mem=pool.allocate(sizeof(MyClass));MyClass*obj=new(mem)MyClass(args);// 在内存池中构造对象

重要提醒:用placement new构造的对象,不能用delete释放!

为什么?因为delete会调用operator delete去释放内存,但这块内存不是operator new分配的,可能是栈上的buffer、可能是内存池里的一块区域、可能是共享内存,delete去释放这些内存会出大问题——正确的做法是手动调用析构函数:

// 错误!不能用delete// delete obj;// 正确:手动调用析构函数obj->~MyClass();

手动调用析构函数是C++中少数几个需要显式调用析构的场景之一,虽然看起来有点怪,但这是placement new的标准用法。


自定义operator new/delete:全局重载

operator new和operator delete是可以重载的,重载它们,你就控制了new表达式的第一步——内存分配,全局重载会影响所有的new和delete。

#include<cstdlib>#include<iostream>// 全局重载operator newvoid*operatornew(size_t size){std::cout<<"分配 "<<size<<" 字节\n";void*ptr=std::malloc(size);if(!ptr)throwstd::bad_alloc();returnptr;}// 全局重载operator deletevoidoperatordelete(void*ptr)noexcept{std::cout<<"释放内存\n";std::free(ptr);// std::free(nullptr)是安全的,无需额外判空}// 别忘了数组版本void*operatornew[](size_t size){std::cout<<"分配数组 "<<size<<" 字节\n";void*ptr=std::malloc(size);if(!ptr)throwstd::bad_alloc();returnptr;}voidoperatordelete[](void*ptr)noexcept{std::cout<<"释放数组\n";std::free(ptr);}

有了这个,程序中所有的new和delete都会经过你的函数,想做内存追踪、泄漏检测、性能分析都很方便——你可以在这里加计数器、记日志、统计分配大小,所有内存操作尽在掌握。

测试一下:

intmain(){int*p=newint(42);// 输出:分配 4 字节deletep;// 输出:释放内存int*arr=newint[10];// 输出:分配数组 40 字节(或更多)delete[]arr;// 输出:释放数组}

关于new[]的分配大小:上面new int[10]显示分配40字节,但这不是保证的。对于有非平凡析构函数的类型,编译器通常会额外分配几个字节来存储数组元素个数(供delete[]调用正确次数的析构函数)。对于int这种POD类型,某些编译器可能只分配40字节,但换成有析构函数的类,你会看到分配的字节数比sizeof(T) * n更多。

全局重载的注意事项:

  1. operator new分配失败必须抛出std::bad_alloc,不能返回nullptr,因为标准库和很多第三方库都依赖这个行为,如果你返回nullptr而不是抛异常,调用方可能不会检查返回值就直接使用,导致空指针崩溃
  2. operator delete必须能处理nullptr,且不能抛出异常
  3. new和delete、new[]和delete[]必须配对重载,否则可能出现分配用你的函数、释放用标准库函数的情况
  4. 全局重载影响整个程序,包括标准库,要小心副作用

自定义operator new/delete:类内重载

更常见的需求是只为某个类自定义内存分配,这时候用类内重载,它只影响这一个类,不会干扰其他代码。

classPooledObject{staticcharpool_[1024];staticsize_t offset_;public:// 类内operator newstaticvoid*operatornew(size_t size){std::cout<<"从内存池分配 "<<size<<" 字节\n";if(offset_+size>sizeof(pool_)){throwstd::bad_alloc();}void*ptr=pool_+offset_;offset_+=size;returnptr;}// 类内operator deletestaticvoidoperatordelete(void*ptr)noexcept{std::cout<<"归还到内存池(实际不释放)\n";// 简单的内存池实现,这里不真正释放}intdata;PooledObject(intd):data(d){}};// 静态成员定义charPooledObject::pool_[1024];size_t PooledObject::offset_=0;

使用起来和普通的new一样:

intmain(){// 自动调用类内的operator newPooledObject*obj=newPooledObject(42);// 自动调用类内的operator deletedeleteobj;}

类内重载特别适合给特定类做内存池优化,比如游戏引擎里的粒子系统,一帧可能创建销毁上万个粒子对象;比如数据库里的B+树节点,查询时需要频繁分配释放节点;比如网络服务器的请求对象,每秒处理几万个请求——这些对象有个共同特点:数量多、大小固定、频繁创建销毁,用标准的malloc/free每次都要走一遍系统分配器,开销太大,用内存池预分配一大块内存、按需分发,能把性能提升好几倍。


总结:什么时候需要这些技术

三种场景:

1. 内存池优化

对象数量多、创建销毁频繁时,用placement new配合预分配的内存池,避免频繁调用系统分配器,在游戏引擎、数据库、高频交易系统这类对性能敏感的场景,内存池优化能把分配速度提升几倍甚至几十倍,这是实打实的性能提升。

2. 内存追踪和调试

想知道程序分配了多少内存、有没有泄漏、哪个模块分配最多,全局重载operator new/delete,加上计数器和日志就行,比用Valgrind这类外部工具轻量得多,而且可以集成到生产环境的监控系统里。

3. 特殊内存要求

对象需要放在特定位置时,placement new是唯一选择——共享内存里构造对象要用它,DMA缓冲区里构造对象要用它,需要特定对齐的内存也要用它,这些场景下你没法用普通的new,因为你无法控制new把对象放在哪里。


new表达式不是一个操作,是两个:先调用operator new分配内存,再调用构造函数创建对象。placement new让你跳过第一步,直接在已有内存上执行第二步。

理解这一点,你就掌握了C++内存管理的核心——分配和构造是可以分离的,你可以分别控制它们,这种控制能力是C++区别于其他语言的重要特性,也是C++能在系统编程、游戏引擎、高频交易等领域占据主导地位的原因之一。

这个知识点不难,但很多人写了好几年C++都没搞清楚。现在你知道了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1160616.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

8继承多态

3为什么需要继承&#xff0c;继承的意义是什么 ![[Pasted image 20251210212105.png]] 所以想说明什么 ![[Pasted image 20251210212458.png]] public class Dog { public String name; public int age; public void eat() { System.out.println(this.name"正在吃饭&q…

Spring Boot的约定优于配置:智能管家的“隐形”艺术

文章目录一、什么是约定优于配置&#xff1f;智能管家的设计哲学二、Spring Boot如何实现约定&#xff1f;自动配置的魔法引擎2.1 SpringBootApplication的三层秘密2.2 自动配置的执行流程&#xff1a;Spring Boot的“思考”过程2.3 条件化装配&#xff1a;智能管家的“分寸感”…

大家一直催更的Agent学习路线来喽!

大家好&#xff01;这周出差了两天&#xff0c;稍微有点忙&#xff0c;所以Agent学习路线出得稍微晚了一点&#xff0c;希望这份学习路线能够帮助大家更好地理解和实现Agent技术&#xff0c;在学习和应用中有所收获 Agent的技术原理 1、技术发展路线&#xff1a;API->LLM-&…

Oracle 19c入门学习教程,从入门到精通,Oracle体系结构 —— 知识点详解(2)

Oracle体系结构 一、需求理解 基于Oracle 19c第2章“Oracle体系结构”的核心内容&#xff08;涵盖逻辑/物理存储结构、服务器结构、数据字典等&#xff09;&#xff0c;整理一份包含Oracle安装过程、体系结构相关核心语法知识点及使用方法的教程&#xff0c;每个知识点配套带…

守护能源与数据的安全防线:从UL 2075标准解析储能及数据中心氢探技术的演进

守护能源与数据的安全防线&#xff1a;从UL 2075标准解析储能及数据中心氢探技术的演进一、UL 2075&#xff1a;为高风险场景设立的专业门槛UL 2075标准通过以下核心测试保障设备可靠性&#xff1a; $$ \text{稳定性} f(\text{温度}, \text{湿度}, \text{电压}) $$# 极端环境测…

C++类型判断

一、编译期类型判断&#xff08;静态类型检查&#xff09;这类判断在编译阶段完成&#xff0c;零运行时开销&#xff0c;主要用于模板编程、类型萃取等场景。1. typeid 运算符&#xff08;基础&#xff09;typeid 可以获取类型信息&#xff0c;返回 std::type_info 对象&#x…

Python 内置 venv 虚拟环境工具完全指南(附 uv 工具无缝升级教程)

Python venv 虚拟环境基础操作创建虚拟环境命令格式如下&#xff0c;需指定目标目录路径&#xff1a;python -m venv /path/to/your/env激活虚拟环境的脚本路径因操作系统而异&#xff1a;Windows: \path\to\env\Scripts\activateUnix/macOS: source /path/to/env/bin/activate…

2026机器视觉同轴光源品牌甄选指南:解锁高精度检测的照明密钥

在智能制造与工业自动化飞速发展的今天&#xff0c;机器视觉系统已成为现代工业的“智慧之眼”。而同轴光源作为这一“眼睛”的核心照明系统&#xff0c;其性能直接决定了视觉检测的精度与可靠性。面对2026年工业检测对精度、效率和稳定性提出的更高要求&#xff0c;选择一款真…

如何使用`typeid`判断指针或引用所指对象的实际类型?

核心前提&#xff1a;typeid判断实际类型的条件typeid能否识别指针 / 引用指向的实际类型&#xff0c;唯一的关键是&#xff1a;被判断的类是否是多态类&#xff08;包含至少一个虚函数&#xff0c;通常是虚析构函数&#xff09;。非多态类&#xff1a;typeid只能识别编译期的声…

C++ RAII封装结构体成员变量自动加锁性能开销分析

在C中通过RAII&#xff08;Resource Acquisition Is Initialization&#xff09;机制封装结构体成员变量的自动加锁/解锁操作&#xff0c;其性能开销需从锁机制成本、编译器优化空间、运行时场景适配三个维度进行系统性分析&#xff1a; 一、RAII加锁封装的核心机制 以典型实现…

凤希AI提出FXPA2P:下一代点对点AI服务架构-2026年1月14日

思考与发现在今日对产品技术细节进行打磨与升级的同时&#xff0c;一个更为宏观和前瞻性的构想逐渐清晰。基于对当前AI应用依赖中心化云服务所暴露的成本、效率与隐私问题的深刻洞察&#xff0c;我们正式提出 FXPA2P 这一商业概念与技术实施模式。FXPA2P&#xff0c;即 FengXi …

智能指针的生命周期控制

在C中&#xff0c;函数内创建的智能指针通过参数返回时&#xff0c;其生命周期管理遵循资源所有权转移和引用计数的智能指针语义&#xff0c;具体行为取决于智能指针类型&#xff08;如std::unique_ptr、std::shared_ptr&#xff09;和传递方式&#xff08;返回值/输出参数&…

AI原生应用开发:相似度匹配的模型压缩技巧

AI原生应用开发:相似度匹配的模型压缩技巧 关键词:相似度匹配、模型压缩、AI原生应用、知识蒸馏、模型量化、参数剪枝、轻量级模型 摘要:在AI原生应用(如智能推荐、跨模态搜索、对话系统语义理解)中,相似度匹配模型是核心组件。但这类模型常因参数量大、计算复杂度高,难…

6款AI论文降重神器实操教程:AI率从72%降至13%

一、AI论文降重工具快速对比&#xff1a;哪款最适合你&#xff1f; 作为学生或科研人员&#xff0c;你是否曾遇到以下痛点&#xff1a; 用ChatGPT写的论文AI检测率高达70%&#xff0c;被导师打回重写&#xff1f;降重时逐句改写&#xff0c;耗时又容易破坏逻辑&#xff1f;找…

Python + uiautomator2 手机自动化控制教程

安装 uiautomator2 库通过 pip 安装 uiautomator2 库&#xff0c;确保 Python 环境已配置。pip install uiautomator2初始化设备连接使用设备的 IP 地址或序列号连接手机&#xff0c;确保手机已开启 USB 调试模式。import uiautomator2 as u2 d u2.connect("192.168.1.10…

Python 学生管理系统实战:从基础功能到数据持久化(附完整源码)

学生管理系统基础功能实现学生管理系统的核心功能包括添加、删除、修改和查询学生信息。使用Python内置数据结构如字典和列表可以快速实现这些基础功能。students []def add_student():name input("输入学生姓名: ")age int(input("输入学生年龄: "))st…

【Python库和代码案例:第一课】Python 标准库与第三方库实战指南:从日期处理到 Excel 操作

Python 标准库实战datetime 模块处理日期from datetime import datetime, timedelta# 获取当前时间 now datetime.now() print(f"当前时间: {now}")# 时间加减操作 next_week now timedelta(days7) print(f"一周后时间: {next_week}")# 时间格式化 form…

数独优化求解C库tdoku-lib的使用

tdoku-lib是基于优化求解器tdoku改造的动态库和静态库&#xff0c;它的存储库地址 https://github.com/hackerzhuli/tdoku-lib 1.拉取源代码 rootDESKTOP-59T6U68:/mnt/c/d# git clone https://github.com/hackerzhuli/tdoku-lib.gitCloning into tdoku-lib... remote: Enumer…

AI原生应用云端推理的故障排查与恢复

AI原生应用云端推理的故障排查与恢复:让智能服务“不掉线”的秘密 关键词:AI原生应用、云端推理、故障排查、恢复机制、AIOps 摘要:当你用手机拍照识别植物品种时,当智能客服秒级回复你的问题时,当电商APP精准推荐商品时——这些“丝滑”体验的背后,是AI原生应用在云端高…

dlx求解数独duckdb插件的编写和使用

1.将网上下载的dlx求解c程序添加int sudoku(const char *s,char *r)函数处理81个字符长的数独题目字符串 #include <cstdio> #include <cstring> #include <ctime> int cnt0; const int XSIZE 3; const int SIZE XSIZE * XSIZE; const int MAX_C SIZE *…