从 malloc/free 到 new/delete:真正理解 C++ 中的内存管理与对象生命周期
文章目录1. 先分清两件事内存放在哪里对象是否已经存在2. C 的动态分配拿到的是内存不是 C 对象的构造过程3. new 与 delete先从内置类型看语法和初始化4. 对自定义类型分配和构造必须连起来看5. new 表达式和 operator new 函数不是同一个东西6. 拓展placement new定位 new7. 为何要严格匹配内存开辟与释放规则8. 小结1. 先分清两件事内存放在哪里对象是否已经存在在 C 中我们常用“栈、堆、数据段、代码段”来描述一个典型程序的内存布局。下面用一段 C 代码来观察变量与它具体指向的内容#includeiostream//定义全局静态变量intglobal_value1;staticintfile_value2;intmain(){//静态变量staticintlocal_static3;//普通变量intlocal_value4;intlocal_array[2]{5,6};//指针变量constchar*textC;//动态开辟内存int*dynamic_valuenewint(7);std::coutglobal_value: global_valuestd::endl;std::coutfile_value: file_valuestd::endl;std::coutlocal_static: local_staticstd::endl;std::coutlocal_value: local_valuestd::endl;std::coutlocal_array[0]: local_array[0]std::endl;std::couttext: textstd::endl;std::cout*dynamic_value: *dynamic_valuestd::endl;deletedynamic_value;}运行示例global_value、file_value和local_static具有静态存储期通常放在静态区数据段local_value、local_array、text和dynamic_value这些局部变量通常位于栈帧中。指针变量和它指向的内容不是同一块东西dynamic_value自身是局部变量*dynamic_value是动态创建的整数对象空间占用在堆上text自身是局部指针而字符串字面量具有静态存储期通常位于只读区域。2. C 的动态分配拿到的是内存不是 C 对象的构造过程先把熟悉的四个函数放在一张表里进行对比C 函数主要作用须记住的边界malloc(bytes)分配指定字节数内容未初始化失败返回空指针calloc(count, bytes)分配并将字节置零字节置零但不等于调用 C 构造函数realloc(ptr, new_bytes)调整既有分配块成功后旧指针失效地址可能改变失败时原块仍有效free(ptr)释放上述函数得到的分配块不调用 C 析构函数free(nullptr)安全下面是一个 C 语言的内存管理示例只对整数数组使用calloc/realloc。#includestdlib.h#includestdio.hintmain(){int*values(int*)calloc(3,sizeof(int));if(valuesNULL){perror(calloc fail!);return1;}int*grown(int*)realloc(values,5*sizeof(int));if(grownNULL){perror(realloc fail!);free(values);return1;}valuesgrown;// 成功后只能继续使用新指针printf(%d %d\n,values[3],values[4]);values[3]30;// 扩出来的字节不能假定已清零values[4]40;printf(%d %d\n,values[3],values[4]);free(values);return0;}运行示例前两次打印时由于未给新分配好的内存空间赋值所以打印随机值正常分配后则输出30 40。如果直接写values realloc(values, ...)失败返回的空指针会覆盖原地址从而导致原块无法释放内存泄漏。在 C 中调用这些函数时若是用来开辟自定义类型的对象时它们不会调用类的构造函数。对已经由new创建的对象也不能用realloc扩容。3.new与delete先从内置类型看语法和初始化new T创建一个T对象new T[n]创建一个含n个对象的数组。相较于手写malloc(n * sizeof(T))new根据类型和元素个数决定所需存储并直接返回相应类型的指针对应地单个对象用delete数组用delete[]。// 完整示例内置类型的不同初始化方式#includeiostreamintmain(){int*anewint;// 值未初始化先赋值再读取int*bnewint(10);// 初始化为 10int*cnewint();// 值初始化得到 0int*dnewint[3];// 三个元素的值未初始化随机值int*enewint[3]();// 三个元素均值初始化为 0int*fnewint[3]{1,2,3};//三个元素按照给定元素初始化int*gnewint[3]{1};//第一个元素按给定元素初始化其余为0*a5;d[0]1;d[1]2;d[2]3;std::cout*a: *astd::endl;std::cout*b: *bstd::endl;std::cout*c: *cstd::endl;for(size_t i0;i3;i){std::coutd[i]:d[i] ;}std::coutstd::endl;std::coutstd::endl;for(size_t i0;i3;i){std::coutf[i]:f[i] ;}std::coutstd::endl;std::coutstd::endl;for(size_t i0;i3;i){std::coutg[i]:g[i] ;}deletea;deleteb;deletec;delete[]d;delete[]e;delete[]f;delete[]g;}运行示例普通new分配失败时通常抛出std::bad_alloc而中断程序而不是像malloc那样返回空指针所以写if (p nullptr)并不能处理普通new的分配失败。后续可以使用异常处理中的try...catch来进行异常的捕捉。4. 对自定义类型分配和构造必须连起来看在C中动态开辟类对象时比整数时多了一段关键过程new自动调用构造函数让对象可用delete自动调用析构函数在对象结束前清理资源。这也是与 C 的内存管理有差别的地方malloc是函数而new是操作符。下面的Cell类在构造时申请一个整数内存在析构时释放它。// 完整示例单个对象与对象数组的生命周期#includeiostreamclassCell{public:explicitCell(intvalue0):_value(newint(value)){std::cout构造 *_valuestd::endl;}~Cell(){std::cout析构 *_valuestd::endl;delete_value;}private:int*_value;};intmain(){Cell*onenewCell(7);deleteone;Cell*threenewCell[3];delete[]three;}运行示例正常执行时先输出一次“构造 7、析构 7”再输出三次“构造 0”和三次“析构 0”。new Cell[3]要能逐个构造因此这里的Cell需要有可不传参数的构造函数默认构造函数delete[]要结束全部数组元素的生命再释放数组占用的存储。如果把delete one改成free(one)析构函数不会执行_value指向的资源无法经由析构函数释放所以就可能导致崩溃、破坏分配器状态。单个元素也可能暂时看不出异常。但还是建议尽量配对使用并且尤其注意释放开辟的数组时使用方括号delete[]。5.new表达式和operator new函数不是同一个东西名字接近职责却不同写法做什么是否构造或析构对象new Cell(7)new表达式取得存储并初始化一个对象调用构造函数delete pdelete表达式结束对象生命并释放存储调用析构函数::operator new(sizeof(Cell))分配函数只取得原始存储不调用构造函数::operator delete(raw)释放函数只释放原始存储不调用析构函数在通常的路径中可以把过程想成new Cell(7) : 取得足够且对齐的存储 - 在其中构造 Cell - 得到 Cell* delete p : 对 Cell 执行析构 - 释放其占用的存储operator new≠new表达式全局operator new常由运行库在底层借助malloc或其他分配器实现但标准并不要求它必须调用malloc。不要混淆调用operator new不会产生对象只是一块未初始化内存直接当成Cell *去使用属于未定义行为。operator new/operator delete可以被类重写类内重载new表达式语法本身不能重载。6. 拓展placement new定位 new如果你已经用operator new拿到裸内存想要手动执行构造函数就用定位 newvoid*raw::operatornew(sizeof(Cell));// 只分配内存没有对象Cell*objnew(raw)Cell(7);// placement new在raw这块内存上调用构造函数释放的时候也要手动两步obj-~Cell();// 手动调用析构::operatordelete(raw);// 再释放原始内存7. 为何要严格匹配内存开辟与释放规则先看以下规则不只看一次运行的表象取得方式正确释放错误配对malloc/calloc/reallocfreedelete、delete[]new Tdeletefree、delete[]new T[n]delete[]free、delete这些错误配对都是未定义行为包括“内置类型数组用delete并没发生崩溃”。对于有析构函数的对象数组后果往往更明显对象析构不完全导致内存泄漏delete并不能按数组规则逐个结束所有元素的生命释放时还可能把不合适的地址交给分配器。那么delete[]又怎样知道该析构多少个对象有的实现会在分配块附近保存元素数目或相关元数据常称为array cookie某些类型、实现或优化下也可能不需要这样的记录。cookie 是编译器实现细节C 标准没有强制要求必须存在、放哪里、多大。但记录的位置、大小、是否存在都不能拿来随意编写可移植代码要根据平台与编译器的选择具体决定。但只要严格遵守配对使用就能很大程度避免一些问题的发生尤其对于一些底层实现很复杂的情况。8. 小结new/delete是表达式分配释放内存同时自动调用构造、析构new[]必须配delete[]错配属于未定义行为。operator new/operator delete只操作裸内存不执行构造析构是new表达式底层调用的函数。malloc/free只管理原始内存不处理对象和new/delete指针严禁交叉释放。对象完整生命周期分配内存 - 构造 - 使用 - 析构 - 释放内存。

相关新闻

选择零代码开发软件,为什么推荐恐象 AI?

选择零代码开发软件,为什么推荐恐象 AI?

随着数字化管理需求普及,零代码开发软件成为很多小团队的首选工具。不用编写代码,短时间搭建线上业务系统,这是零代码开发软件最大的魅力。但市面上很多零代码开发软件,上手门槛高,操作繁琐,想要搭建个性化…

2026/9/30 6:30:55 阅读更多 →
Redis 两级缓存 + Pub/Sub 广播:一次为抗高并发做的缓存改造

Redis 两级缓存 + Pub/Sub 广播:一次为抗高并发做的缓存改造

Redis 两级缓存 Pub/Sub 广播:一次为抗高并发做的缓存改造一、背景:为什么会有这次改动 业务高峰期,相关接口 QPS 很高,而系统里几乎所有读操作都要过一遍 Redis,导致 Redis 频繁被打挂。 问题的本质是:读…

2026/9/30 6:29:55 阅读更多 →
GC5358|24bit‑96kHz 立体声 ADC,高性能音频模数转换优选

GC5358|24bit‑96kHz 立体声 ADC,高性能音频模数转换优选

在 DTV 电视、DVR 录像设备、AV 音频系统、各类多媒体音频产品设计中,音频 ADC 的信噪比、动态范围、采样率直接决定录音与信号采集质量。很多方案想要实现高清立体声采集,外围模拟电路复杂,主控兼容性差。 今天为大家带来GC5358,…

2026/9/30 6:29:55 阅读更多 →

最新新闻

预算紧张时怎么起步?SaaS 与源码的入门门槛对比

预算紧张时怎么起步?SaaS 与源码的入门门槛对比

刚起步的商家,现金流往往是头等大事。SaaS 手机号注册即自动分配商城空间,不用买服务器、不用请技术,几百块就能把店开起来,试错成本很低,跑不通损失也有限。 源码部署看似入门零成本。但真正跑起来,要付服…

2026/9/30 7:21:17 阅读更多 →
一文教会昇腾ATC模型转换工具:把开源模型编译成NPU能跑的格式

一文教会昇腾ATC模型转换工具:把开源模型编译成NPU能跑的格式

昇腾 ATC 模型转换入门指南:把训练模型变成 NPU 能跑的 OM 一句话让Agent变成昇腾专家,昇腾任务轻松搞定。评测入口:请按这个开源仓接入昇腾图谱 https://gitcode.com/agent0/kg-tools 你花几天训练出一个深度学习模型,满怀期待地…

2026/9/30 7:21:17 阅读更多 →
CW32L010自定义Bootloader上位机控制更换UI,背景图,固件升级

CW32L010自定义Bootloader上位机控制更换UI,背景图,固件升级

项目完整教程项目完整飞书文档 https://my.feishu.cn/docx/TXDYdtXg9ovE1QxlFsGcX5FXndh?fromfrom_copylink1. 项目与构建MCU:CW32L010;系统时钟 48 MHz HSI。固件分为应用 FAN_CONTROL/ 与自定义 UART2 Bootloader BOOTLOADER/。构建:CMake…

2026/9/30 7:21:17 阅读更多 →
智能视频监控:从事后回看到实时预警主动防控

智能视频监控:从事后回看到实时预警主动防控

工厂部署了数百路视频摄像头,但绝大多数视频的作用是"出事后调监控回看"。人员违规作业、区域非法闯入、PPE穿戴缺失、烟火异常等行为完全依赖监控室值守人员的肉眼观察。一个人同时盯几十个屏幕,注意力无法持续集中。更关键的是,发…

2026/9/30 7:21:17 阅读更多 →
大模型 API 价格怎么看?除了 Token 单价还要算哪些成本

大模型 API 价格怎么看?除了 Token 单价还要算哪些成本

大模型 API 价格怎么看?除了 Token 单价还要算哪些成本 很多人第一次比较大模型 API 价格,最容易看的就是:每百万 Token 多少钱。这个数字当然重要,但真正把 API 接进 AI Coding、Agent、知识库或者企业内部系统后,很快…

2026/9/30 7:21:17 阅读更多 →
《战略规划写得漂亮,一年后业务上什么都没发生——差的就是这六

《战略规划写得漂亮,一年后业务上什么都没发生——差的就是这六

每年年底,很多企业都会做同一件事:关起门来开三天战略会,把明年的方向、目标、打法写成一份漂亮的PPT。然后,一年过去了。复盘时会发现一个尴尬的事实:方向没错,但业务上什么都没发生。战略躺在纸面上&…

2026/9/30 7:20:17 阅读更多 →

日新闻

Base64 图片头部特征识别:从文件头到格式判断的完整指南

Base64 图片头部特征识别:从文件头到格式判断的完整指南

1. 项目概述:为什么说看懂 base64 图片头部是基本功这几年跟 base64 打交道的机会越来越多,后端接口返回图片、前端渲染验证码、小程序里存小图、还有一些老系统导出报表,动不动就给你一段长到怀疑人生的 base64 字符串。很多人拿到字符串就直…

2026/9/30 0:00:35 阅读更多 →
Java公交站牌广告管理系统:JSP+Servlet+MySQL实战落地指南

Java公交站牌广告管理系统:JSP+Servlet+MySQL实战落地指南

简介:本资源是一份面向Java初学者与课程设计学生的公交站牌广告灯箱管理系统毕业设计文档,聚焦城市公共广告资源信息化管理痛点,提供从需求分析到技术实现的完整方案。文档采用标准学术论文结构,含摘要、英文摘要、目录及五章正文…

2026/9/30 0:00:35 阅读更多 →
用 Redis Lua 构建大模型 API 多租户原子配额治理体系

用 Redis Lua 构建大模型 API 多租户原子配额治理体系

我去年年底接了一个内部 AI 平台的治理需求,背景很直接:公司把 DeepSeek、MiniMax 这类大模型 API 统一封装成内部网关,开放给几个业务团队用。结果第一个月账单出来,额度直接超了 4 倍。仔细查日志,发现原因并不复杂—…

2026/9/30 0:00:35 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/29 8:16:59 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/29 16:41:41 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/29 8:24:48 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/29 19:29:29 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/29 5:58:00 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/29 3:55:56 阅读更多 →