C++从入门到实战(十二)详细讲解C++如何实现内存管理

C++从入门到实战(十二)详细讲解C++如何实现内存管理

C++从入门到实战(十二)详细讲解C++如何实现内存管理


前言

  • 在上一篇博客中,我们探讨了 C/C++ 语言的内存分布模型,并对比了 C 与 C++ 内存管理的核心差异,初步认识了 C++ 中new与delete的基本概念,为理解 C++ 内存管理体系奠定了基础
  • 本文将在此基础上,深入解析 C++ 内存管理的核心机制,从底层原理到实践细节展开系统讲解,帮助读者全面掌握这一重要知识模块。
我的个人主页,欢迎来阅读我的其他文章
https://blog.ZEEKLOG.net/2402_83322742?spm=1011.2415.3001.5343
我的C++知识文章专栏
欢迎来阅读指出不足
https://blog.ZEEKLOG.net/2402_83322742/category_12880513.html?spm=1001.2014.3001.5482

一、C++内存管理方式

1. new/delete操作内置类型

  • 在 C 语言里有它自己的内存管理办法,但用起来比较麻烦,而且有些情况处理不了。
  • C++ 为了解决这些问题,引入了new和delete操作符来进行动态内存管理
#include<iostream>usingnamespace std;voidTest(){int* ptr4 =newint;// 动态申请一个int类型的空间int* ptr5 =newint(10);//动态申请一个int类型的空间并初始化为10int* ptr6 =newint[3];//动态申请3个int类型的空间delete ptr4;delete ptr5;//delete ptr6;//错误在 C++ 中,new 和 delete、new[] 和 delete[] 是成对出现的操作符,它们的使用需要遵循特定的配对规则://如果使用 delete 而非 delete[] 来释放通过 new[] 分配的数组内存,会产生严重的后果://内存泄漏:delete 只会释放数组首元素的内存,而不会释放数组中其他元素的内存,这就导致了部分内存无法被回收,造成内存泄漏。//未定义行为:使用 delete 释放数组内存属于未定义行为,这意味着程序可能会出现各种不可预测的问题,比如程序崩溃、数据损坏等delete[] ptr6;}
  • 在 C++ 中,new 和 delete、new[] 和 delete[] 是成对出现的操作符,它们的使用需要遵循特定的配对规则:
  • 如果使用 delete 而非 delete[] 来释放通过 new[] 分配的数组内存,会产生严重的后果:例如上面代码中的int* ptr6 = new int[3]与delete ptr6
内存泄漏:delete 只会释放数组首元素的内存,而不会释放数组中其他元素的内存,这就导致了部分内存无法被回收,造成内存泄漏。
未定义行为:使用 delete 释放数组内存属于未定义行为,这意味着程序可能会出现各种不可预测的问题,比如程序崩溃、数据损坏等
在这里插入图片描述

2. 异常与内存管理的联系(简单了解)

  • 在使用new申请内存时,如果系统没有足够的内存可供分配,就可能会抛出异常。
  • 所以在进行内存管理时,要考虑到这种可能出现的异常情况,确保程序的健壮性。
#include<iostream>usingnamespace std;voidTest(){try{// 动态申请一个 int 类型的空间int* ptr4 =newint;// 动态申请一个 int 类型的空间并初始化为 10int* ptr5 =newint(10);// 动态申请 3 个 int 类型的空间int* ptr6 =newint[3];// 使用分配的内存*ptr4 =5; cout <<"Value of ptr4: "<<*ptr4 << endl; cout <<"Value of ptr5: "<<*ptr5 << endl;for(int i =0; i <3;++i){ ptr6[i]= i; cout <<"Value of ptr6["<< i <<"]: "<< ptr6[i]<< endl;}// 释放内存delete ptr4;delete ptr5;delete[] ptr6;}catch(const bad_alloc& e){// 捕获内存分配失败的异常 cerr <<"Memory allocation failed: "<< e.what()<< endl;}}intmain(){Test();return0;}
在这里插入图片描述
#include<iostream>usingnamespace std;voidFunc(){int i =1;while(1){int* p1 =newint[1024*1024]; cout << i <<"->"<< p1 << endl; i++;}}intmain(){try{Func();}catch(const std::exception& e){ cout << e.what()<< endl;}}
在这里插入图片描述
  • 这段代码定义了一个名为 Func 的函数,该函数在一个无限循环中不断尝试分配大约1MB的内存
  • 由于没有适当的内存释放,这将最终导致内存耗尽,从而抛出 std::bad_alloc 异常
  • 在 main 函数中, Func 被调用并被包裹在一个 try-catch 块中,以捕获并处理可能抛出的异常。
然而,由于 std::bad_alloc 异常没有被直接捕获( catch 块中捕获的是 std::exception 的引用),所以实际上这段代码可能不会按预期工作,因为 std::bad_alloc 可能在到达 catch 块之前就已经导致程序终止

3. new和delete操作自定义类型

  • 在 C++ 里,new 和 delete 除了能操作基本数据类型,还可以操作自定义类型。
自定义类型是程序员自己定义的类型,像类、结构体等.
  • 以下是new 和 delete 操作基本数据类型的简单代码
#include<iostream>usingnamespace std;classPerson{public:Person(constchar* _name,int _age){ name = _name; age = _age; cout <<"Person 构造函数被调用,名字: "<< name <<", 年龄: "<< age;};~Person(){ cout <<"Person 析构函数被调用: "<< name <<", 年龄: "<< age;}private:constchar* name;//一个指向常量字符的指针类型int age;};intmain(){// 使用 new 创建 Person 对象 Person* person =newPerson("Alice",25);// 使用 delete 释放对象内存delete person;return0;}
在这里插入图片描述
这段代码定义了一个 Person 类,包含姓名和年龄两个属性,还有构造函数和析构函数。在 main 函数里,使用 new 操作符创建一个 Person 对象,然后使用 delete 操作符释放对象的内存。

二、 operator new与operator delete函数(重点)

1. new和delete操作符与operator new和operator delete函数的关系

  • 前面我们讲到new和delete是用来动态申请和释放内存的操作符。比如,你想创建一个int类型的变量,就可以用new操作符来申请内存:
int* ptr =newint;
  • 当你不再需要这块内存时,就得用delete操作符释放它:

这里的new操作符在底层会调用operator new全局函数来申请内存空间

在这里插入图片描述
delete ptr;

delete操作符在底层会调用operator delete全局函数来释放内存空间

在这里插入图片描述

2. operator new函数的工作原理

  • operator new函数的作用是申请内存空间,它实际上是借助malloc函数来实现的。

下面是它的工作步骤:

  • 尝试申请内存:调用malloc函数去申请指定大小的内存空间。
  • 检查申请结果:
    • 成功:若malloc申请内存成功,就直接返回这块内存的指针。
    • 失败:若malloc申请内存失败,会尝试执行用户设置的空间不足应对措施。
    • 用户设置了应对措施:继续尝试申请内存。
    • 用户未设置应对措施:抛出std::bad_alloc类型的异常

下面是operator new函数的简化代码:

void*operatornew(size_t size){void*p;while((p =malloc(size))==0){if(用户设置的应对措施函数(size)==0){// 申请内存失败,抛出异常staticconst std::bad_alloc nomem;throw nomem;}}return p;}

3. operator delete函数的工作原理

  • operator delete函数的作用是释放内存空间,它最终是通过free函数来实现的。
下面是它的工作步骤:
  • 检查指针是否为空:若传入的指针为空,直接返回,不做任何操作。
  • 释放内存:调用free函数释放这块内存空间。

下面是operator delete函数的简化代码:

voidoperatordelete(void*pUserData){if(pUserData ==NULL)return;free(pUserData);}
  • new操作符在底层调用operator new函数来申请内存空间,operator new函数又借助malloc函数来申请内存
  • delete操作符在底层调用operator delete函数来释放内存空间,operator delete函数最终通过free函数来释放内存
  • 若malloc申请内存失败,operator new函数会尝试执行用户设置的应对措施,若没有设置则抛异常

三、 定位new表达式(placement-new) (了解即可)

1. 定位 new 表达式的概念

一般而言,使用new操作符时,它会做两件事

  • 一是为对象分配内存;二是调用对象的构造函数来初始化这块内存。
  • 而定位 new 表达式有所不同,它是在已经分配好的原始内存空间里调用构造函数来初始化对象。

2. 定位 new 表达式的使用格式

在C++中,定位 new 的语法如下

new(address) Type 

在address指向的内存空间创建一个type类型的对象

new(address) Type[size]
  • 在address指向的内存空间创建一个type类型的对象,并且用size里的值来初始化对象
  • 这里的address得是一个指针,size是类型的初始化列表

3. 定位 new 表达式的使用场景

  • 在实际运用中,定位 new 表达式通常和内存池配合使用。
  • 内存池分配的内存并未初始化,要是分配的是自定义类型的对象,就得使用定位 new 表达式来显式调用构造函数进行初始化
#include<iostream>intmain(){// 分配一块原始内存char* rawMemory =newchar[sizeof(int)];// 使用定位new在原始内存上构造一个int对象int* intPtr =new(rawMemory)int(42);// 输出构造的int对象的值 std::cout <<"Value of int object: "<<*intPtr << std::endl;// 显式调用析构函数(对于基本类型,这一步不是必需的,但对于自定义类型是必需的) intPtr->~int();// 释放原始内存delete[] rawMemory;return0;}
#include<iostream>classMyClass{public:MyClass(int value):data(value){ std::cout <<"Constructor called with value: "<< data << std::endl;}~MyClass(){ std::cout <<"Destructor called for value: "<< data << std::endl;}private:int data;};intmain(){// 分配一块原始内存char* rawMemory =newchar[sizeof(MyClass)];// 使用定位new在原始内存上构造一个MyClass对象 MyClass* myObjPtr =new(rawMemory)MyClass(10);// 显式调用析构函数 myObjPtr->~MyClass();// 释放原始内存delete[] rawMemory;return0;}

四、malloc/free和new/delete的区别

1. 相同点

malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。

  • 不过,他们的不同点是

2. 不同点

一个是函数,一个是操作符

  • malloc/free 是 C 语言的库函数:需要包含头文件 <stdlib.h>,用函数的方式调用(比如 malloc(size))。
  • new/delete 是 C++ 的操作符:是 C++ 语言内置的功能,用法更简洁(比如 new int)

new 会 “初始化”,malloc 不会

malloc 申请的内存是 “脏的”:里面可能是随机的垃圾值。比如

int* p =(int*)malloc(sizeof(int));// *p 的值不确定,可能是任意数
  • new 申请的内存会被 “初始化”
    • 对内置类型(如 int、double),new int 不会初始化,但 new int() 会初始化为 0;
    • 对自定义类型(如类),new 会自动调用构造函数,完成对象的初始化(比如给成员变量赋值)。

空间大小:new 自动计算,malloc 要手动算

  • malloc 需要自己算大小:必须用 sizeof 计算需要的字节数,比如申请 5 个 int 的空间:
int* p =(int*)malloc(5*sizeof(int));// 手动算 5*4=20 字节
  • 如果算错(比如漏掉 sizeof 或乘错数),就会出 bug
  • new 自动知道要多大:直接写类型和数量即可,比如:
int* p =newint[5];// 自动申请 5 个 int 的空间,不用算字节数

new 不用强转,malloc 需要

malloc 返回 void* 指针:使用时必须强制转换类型,比如:

int* p =(int*)malloc(sizeof(int));// 必须强转成 int*

new 直接返回对应类型的指针:比如 new int 直接返回 int*,不需要强转:

int* p =newint;// 直接是 int* 类型,不用强转

错误处理:malloc 返 NULL,new 抛异常

  • malloc 申请失败返回 NULL:必须检查是否为 NULL,否则解引用(比如 *p)会导致程序崩溃:
int* p =(int*)malloc(sizeof(int));if(p ==NULL){// 必须判空!// 处理内存不足的情况}
  • new 申请失败会抛出异常:默认会抛出 std::bad_alloc 异常,需要用 try-catch 捕获(或者用 new(nothrow) 版本返回 NULL,但不常用):
try{int* p =newint;// 失败会抛异常,不会返回 NULL}catch(std::bad_alloc& e){// 处理异常}

自定义类型:new/delete 会 “照顾” 对象,malloc/free 不会

malloc/free 只负责搬砖:

  • malloc 只会分配一块足够大的内存,但不会调用类的 构造函数(比如初始化成员变量);
  • free 只会释放内存,但不会调用类的 析构函数(比如释放对象内部申请的资源)。
    这样会导致对象没被正确初始化或清理,造成错误或内存泄漏。

new/delete 会 “盖房子” 和 “拆房子”:

  • new 分配内存后,会自动调用类的构造函数,初始化对象(比如给成员变量赋值);
  • delete 释放内存前,会自动调用类的析构函数,清理对象内部的资源(比如释放成员指针指向的内存)。

以上就是这篇博客的全部内容,下一篇我们将继续探索C++中模板初阶更多精彩内容。

我的个人主页,欢迎来阅读我的其他文章
https://blog.ZEEKLOG.net/2402_83322742?spm=1011.2415.3001.5343
我的C++知识文章专栏
欢迎来阅读指出不足
https://blog.ZEEKLOG.net/2402_83322742/category_12880513.html?spm=1001.2014.3001.5482
非常感谢您的阅读,喜欢的话记得三连哦
在这里插入图片描述

Read more

Git Clone 太慢?开发者的血泪史和终极加速方案【2025最新版!!!】

Git Clone 太慢?开发者的血泪史和终极加速方案【2025最新版!!!】

一、引言 作为一个开发者,git clone 速度慢 这件事真的让我抓狂过无数次。尤其是当我兴致勃勃地想要拉取一个开源项目、或者临时修个 Bug 的时候,git clone 却卡在那里,几分钟过去了 一点进度条都没动,真的想砸键盘。 更离谱的是,有时候 别人五秒拉完的代码,我得等五分钟,甚至 直接 clone 失败,真的痛不欲生。 这篇文章,我就来聊聊 git clone 为什么会这么慢,以及 如何用最简单、最有效的方法加速,不让自己被折磨得怀疑人生。 二、为什么 git clone 会这么慢? 在你疯狂敲键盘、怒骂 GitHub 服务器之前,我们得先搞清楚 问题的根源。 导致 git clone 速度慢的

By Ne0inhk
GTC2026前瞻(二)Agentic AI 与开源模型篇+(三)Physical AI 与机器人篇

GTC2026前瞻(二)Agentic AI 与开源模型篇+(三)Physical AI 与机器人篇

(二)Agentic AI 与开源模型篇 Agentic AI与开源模型:英伟达想定义的,不只是“更聪明的模型”,而是“能持续工作的数字劳动力” 如果说过去两年的大模型竞赛,核心问题还是“谁能生成更像人的答案”,那么到了 GTC 2026,问题已经明显变了。英伟达把 Agentic AI 直接列为大会四大核心主题之一,官方对这一主题的定义也很明确:重点不再是单轮问答,而是让 AI agent 能够推理、规划、检索并执行动作,最终把企业数据转化为可投入生产的“数字劳动力”。这说明,Agentic AI 在英伟达的语境里,已经不是一个前沿概念,而是下一阶段 AI 商业化的主战场。(NVIDIA) 一、GTC 2026真正的变化,是 AI 开始从“会回答”走向“会做事”

By Ne0inhk
开源AI编程新标杆,OpenCode全维度解析,重塑开发者高效工作流

开源AI编程新标杆,OpenCode全维度解析,重塑开发者高效工作流

在AI编程工具爆发式发展的今天,开发者们一边享受着AI辅助带来的效率飞跃,一边面临着商业工具的厂商锁定、隐私泄露、功能受限等痛点。就在这样的行业背景下,由anomalyco团队打造的OpenCode横空出世,这款100%开源的AI编程代理,以“终端优先、多模型支持、隐私安全、开箱即用”为核心理念,打破了商业工具的垄断壁垒,为开发者提供了一款透明、灵活、可定制的高效编程辅助解决方案。 不同于Claude Code、GitHub Copilot等商业产品,OpenCode采用MIT开源协议,将所有代码完全开放,开发者不仅可以免费使用,还能根据自身需求修改源码、二次开发,从根本上避免了厂商锁定的风险。更值得一提的是,它支持75+大语言模型提供商,可本地运行且不依赖云端服务,既能满足普通开发者的日常编码需求,也能适配金融、医疗等隐私敏感行业的严格要求。本文将从安装部署、使用方法、技术架构、功能特性、工程组成等多个维度,对OpenCode进行全面且通俗的解析,带大家深入了解这款开源AI编程代理的核心魅力,看看它如何重塑开发者的工作流。 一、OpenCode安装部署:从零到一,新手也能轻松上

By Ne0inhk
2026全球开源大模型TOP10榜单+主流模型深度解析

2026全球开源大模型TOP10榜单+主流模型深度解析

【前言】2026年,开源大模型迎来爆发式发展,中国力量持续领跑,MoE架构成为绝对主流,模型发展从“通用全能”向“场景专精”深度转型。本文结合Hugging Face最新榜单及权威机构评估,整理出2026年全球开源大模型TOP10排行榜,深度解析主流模型的技术亮点、性能表现与适用场景,并从技术架构、训练数据、指令遵循、微调能力四大维度,全面评估当前开源大模型的技术发展水平,为开发者选型、企业落地提供参考。 一、2026全球开源大模型TOP10排行榜 本次榜单基于下载量、LMSYS盲测、工程化落地成本、商用友好度、社区活跃度五大核心维度,结合Hugging Face最新发布的开源大模型榜单及多个权威评测机构综合评估整理而成,覆盖全球主流开源模型,精准反映当前开源大模型的综合竞争力。 排名 模型名称 机构 架构 核心参数 主打能力 适用场景 1 Qwen 3.5 阿里 MoE 397B 总 / 17B 激活

By Ne0inhk