「C++ 内存管理篇 1」C++动态内存分配

〇、C语言的动态内存分配方式

        关于C语言的动态内存分配方式,简单来讲就是使用四个库函数:malloc、calloc、 realloc、free对堆区的内存进行灵活的分配和回收。有兴趣的话可以看看这篇文章:  「C语言进阶1」动态内存分配


一、C++的动态内存分配方式

1. 什么是C++的动态内存分配?

        动态内存分配是指在程序运行时,系统根据需要动态地申请和释放内存空间。
        C++中的动态内存分配是通过new和delete操作符来实现的。
        所以C++的动态内存分配简单来讲就是通过new和delete操作符对堆区的内存进行动态内存管理。

---------------------------------------------------------------------------------------------------------------------------------

2. 为什么需要C++的动态内存分配?

        C语言动态内存分配方式在C++中可以继续使用,但有些地方使用起来比较麻烦,比如创建动态对象时:如果选择使用malloc来创建动态对象,那就只是在堆上开辟了空间,没有初始化对象,我们又要想方设法对这个动态对象进行初始化。

        有没有办法在创建动态对象的同时对其完成初始化操作呢?所以C++又提出了自己的动态内存管理方式:通过操作符new和delete对堆区的内存进行进行动态内存管理。

a. new的优势

  • 创建和初始化一体:
            开辟空间和初始化在同一语句内,可以在开辟空间后按需求同时完成初始化操作,不像malloc只是完成空间的开劈,需要另起一行来初始化。
  • 操作统一:
            内置类型和自定义类型使用new创建和初始化动态变量的方法没有区别。
  • 对自定义类型会自动调用构造函数:
            对自定义类型,malloc只会分配内存空间,不会调用对象的构造函数。而new会在分配内存后自动调用对象的构造函数进行初始化。

  • 自动计算需要的内存大小
            malloc函数需要指定要分配的内存大小(以字节为单位),而new操作符会根据所需变量的类型自动计算所需的内存大小。
  • 类型安全:
            new操作符会进行类型检查,并返回类型正确的指针。而malloc函数,返回的是void*指针,需要自己转换为正确的类型。
  • 分配内存失败会进行异常处理:
            new操作符在分配内存失败时会抛出std::bad_alloc异常,可以通过异常处理机制来处理内存分配失败的情况。而malloc函数在分配内存失败时会返回NULL,需要手动检查返回值并处理。

  • 内存对齐
            malloc函数返回的内存地址是任意的,可能不满足特定的对齐要求。而new操作符会返回已对齐的内存地址,以确保对象的成员变量按照正确的对齐方式存储。


        综上所述,new操作符在C++中提供了更安全、更简洁、更易于管理的内存分配方式,与C++的面向对象特性和智能指针等高级功能紧密结合,使得内存管理更加高效和可靠。

b. new的不足

        new不支持扩容,一旦你使用 new(或 new[])分配了一定大小的内存,这块内存的大小就是固定的,你不能直接改变它的大小。如果你需要更大的内存空间,你需要手动进行内存管理,包括释放旧的内存块并分配一个新的、更大的内存块。

c. delete的优势

        相比于free,delete在释放动态对象的空间前,还会调用析构函数清理对象。 

d. 总结

        综上所述,为了更简洁、安全、方便的开辟和释放动态对象,C++引入了操作符new和delete来进行动态内存管理,它们不仅能完成开辟和释放空间的操作,对于自定义类型还会自动调用构造和析构函数完成初始化和清理工作,同时它们也更简便、更安全。

---------------------------------------------------------------------------------------------------------------------------------

3. 怎么使用new和delete?

a. 对于内置类型

//使用new动态的申请内存创建int变量,不初始化
int* a1 = new int;
//使用new动态的申请内存创建int变量,使用括号初始化
int* a2 = new int(2);//使用new动态的申请内存创建int数组,不初始化
int* b1 = new int[10];
//使用new动态的申请内存创建int数组,使用{}初始化(C++11后引入)
int* b2 = new int[10]{ 1, 2 ,3, 4 };//delete是关键字直接用就可以
//使用delete释放单个的动态变量
delete a1;
delete a2;
//使用delete[]释放连续的动态数组
delete[] b1;
delete[] b2;

        注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],一定要匹配起来使用。因为newnew[]以及deletedelete[]在内部执行的操作是不同的,因此它们不能互换使用。例如,如果你使用new分配了一个数组,但使用delete而不是delete[]来释放它,那么只有数组的第一个元素会被正确释放,其余的元素将保持未释放的状态,从而导致内存泄漏。

b. 对于自定义类型 

对于自定义类型,如果不显示初始化,那么会调用其默认构造函数进行初始化。

class A {
private:int _a;int _b;
public:A(int a = 1, int b = 1): _a(1), _b(0){_a = a;_b = b;}
};// 用new创建一个自定义类型对象不初始化,编译器会调用默认构造函数
A* p0 = new A;
// 用new创建一个自定义类型对象并显示初始化
A* p1 = new A(1, 2);// 用delete销毁自定义类型对象
delete p0;
delete p1;   

C++11后,可以用以下四种方式在new时对类数组初始化:

// 创建一个自定义类型对象数组(不显示初始化,编译器调用默认构造函数初始化)
A* p2 = new A[2];// 创建一个自定义类型对象数组并初始化(C++11后支持)
A* p3 = new A[2]{ 1, 2 }; // 每个数对应一个对象,初始化对应对象的第一个成员
A* p4 = new A[2]{ (1,2,3,4,5)};  // 每个()对应一个对象,用()中的最后一个数初始化对应对象的第一个成员
A* p5 = new A[2]{ {1,2}, {3,4} }; // 每个{}对应一个对象,按顺序初始化对象成员
A* p6 = new A[2]{ A(1, 2), A(3, 4)}; // 每个构造函数对应一个对象delete[] p2;
delete[] p3;
delete[] p4;
delete[] p5;
delete[] p6;

c. 为什么new不需要检查失败?

 malloc和new对于开辟空间失败的处理方式不同:
        malloc是一个函数,失败返回NULL;new是一个操作符,仅在动态内存分配成功时返回一个指针,分配失败只会抛异常不会返回空指针(除非使用了std::nothrow参数)。使用try{}catch{}语句来捕获并处理异常。


4. new和delete的实现原理

        new和delete是用户进行动态内存申请和释放的操作符,new在底层是调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。operator new 和operator delete并不是在重载new和delete运算符,而是系统提供的全局函数用来申请和释放空间。以下是对该语句进行反汇编的结果:


当然new也不仅仅只是调用operator new全局函数来申请空间,还会调用构造函数,申请空间失败时还会抛异常:


所以在c++中更推荐使用new来动态申请空间,一是能同时调用构造函数,二是出错时抛异常,这样符合C++的失败机制。

5. new[]和delete[]的实现原理

        operator new 实际也是通过malloc来申请空间,如果 malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施 就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。


        new[ ]的底层是先调用operator new[ ],再调用 operator new,完成N个对象的空间申请,最后调用N次构造函数。
        而delete[ ]的底层是先调用N次析构函数,完成对N个对象的清理,然后调用operator delete[ ],再调用 operator delete来释放空间。


7. 重载operator new与operator delete(了解)

        一般情况下不需要对 operator new 和 operator delete进行重载,除非在申请和释放空间时候有某些特殊的需求。比如:在使用new和delete申请和释放空间时,打印一些日志信息,可以简单帮助用户来检测是否存在内存泄漏。或是改用内存池,而不直接从堆上申请空间。

        new一个类时,看有没有自己的专属operator new,有,优先使用专属operator new,否则使用默认的全局operator new。


当频繁调用new时,想提高效率,不再走默认operator new中的malloc,而是自己定制一个内存池。使用内存池的优势在于,一次性向堆申请一大块空间A,以后要开辟空间直接在 A中拿即可,省时省力,而每次使用malloc,都要先申请,然后在堆中找一块合适的空间。


8. 定位new表达式(placement-new) (了解)

定位new的作用?

        定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象


使用格式:

        new (p) type或者new (p) type(initializer-list)

        p必须是一个指针,type(initializer-list)是就是构造函数。意思是在p指向的空间创建一个对象。


使用场景:

        定位new允许开发者在预先分配的内存中构造对象,而不是让new运算符自动在堆上分配内存。定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。

#include <iostream>  
#include <new> // 包含定位new的头文件  class MyClass {  
public:  MyClass(int value) : value_(value) {  std::cout << "MyClass constructed with value " << value_ << std::endl;  }  ~MyClass() {  std::cout << "MyClass destroyed" << std::endl;  }  void printValue() const {  std::cout << "Value: " << value_ << std::endl;  }  private:  int value_;  
};  int main() {  // 分配足够的内存来存储MyClass对象  char buffer[sizeof(MyClass)];  // 使用定位new在buffer中构造对象  MyClass* obj = new (buffer) MyClass(42);  // 调用对象的成员函数  obj->printValue();  // 手动调用析构函数,因为定位new不会调用delete  obj->~MyClass();  return 0;  
}

9. malloc/free和new/delete的区别

malloc/free和new/delete的共同点是:

  • 都是从堆上申请空间,并且需要用户手动释放。

不同的地方是:

  1. maloc和free是函数,new和delete是操作符。
  2. malloc申请的空间不会初始化,new可以初始化。
  3. malloc的返回值为void*,在使用时必须强转;new不需要,因为new后跟的是空间的类型。
  4. malloc申请空间失败时,返回的是NULL,因此使用时必须判空;new不需要,但是new需要捕获异常。
  5. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[ ]中指定对象个数即可。
  6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。

------------------------END-------------------------

才疏学浅,谬误难免,欢迎各位批评指正。

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

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

相关文章

国产大模型各自优势如何?大家都怎么选?

近日&#xff0c;一份国产大模型排行榜在网上流传&#xff0c;显示文心一言、通义千问、Kimi位居前三名&#xff0c;其中文心一言用户数为2亿&#xff0c;与其他产品拉开明显差距&#xff1b;前三名月访问量总计超过3000万。国内大模型也开始出现分化效应。

Linux部署MySQL

部署MySQL 先停掉虚拟机中的MySQL&#xff0c;确保你的虚拟机已经安装Docker&#xff0c;且网络开通的情况下&#xff0c;执行下面命令即可安装MySQL&#xff1a; docker run -d \ --name mysql \ -p 3306:3306 \ -e TZAsia/Shanghai \ -e MYSQL_ROOT_PASSWORD123 \ mysql 安…

Linux 安装 Docker +Docker Compose + cucker/get_command_4_run_container

TIP&#xff1a;下面演示的 Linux 系统为 CentOS 7.9。 Docker 更新你的系统并安装必要的依赖项&#xff1a; sudo yum update -y sudo yum install -y yum-utils device-mapper-persistent-data lvm2添加 Docker 的官方仓库&#xff1a; sudo yum-config-manager --add-rep…

如何在职场中有效管理时间和任务?好用的待办事项提醒软件

身在职场&#xff0c;时间管理和任务安排是每个人都必须面对的挑战。有效的时间管理不仅能提高工作效率&#xff0c;还能让我们在繁忙的工作中保持清醒的头脑。那么&#xff0c;如何在职场中有效管理时间和任务呢&#xff1f; 制定一个清晰的工作计划是非常必要的&#xff0c;…

springcloud - ribbon 饥饿加载

一、未饥饿加载前 我们的服务者端口是8081&#xff0c;消费者端口是8085 当我们将两个项目都启动的时候&#xff0c;在消费者里日志级别设置未debug&#xff0c;发现找不到8081 二、开启饥饿加载 # 配置饥饿加载,d1为服务名 ribbon.eager-load.enabledtrue ribbon.eager-loa…

Echarts X轴类目名太长时隐藏显示全部

echarts图表X轴 在柱状图中,X轴类目名如果数据太长; echarts会默认进行隐藏部分字段; 如果我们想让每一个类目名都显示出来,需要进行额外的处理X轴类目名太长时,默认只显示一部分类目名 <!DOCTYPE html> <html lang="en"> <head><meta ch…

基于51单片机的超声波测距及温度显示

基于51单片机的超声波测距 &#xff08;仿真&#xff0b;程序&#xff0b;PCB原理图&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.超声波测距传感器HC-SR04、温度传感器DS18B20将检测的数据传给51单片机&#xff1b; 2.LCD1602实时显示测得的距离和温…

javaWeb项目-社区医院管理服务系统功能介绍

项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#xff1a;Vue、ElementUI 关键技术&#xff1a;springboot、SSM、vue、MYSQL、MAVEN 数据库工具&#xff1a;Navicat、SQLyog 1、Java技术 Java语…

【HTML】页面引用Vue3和Element-Plus

在现代前端开发中&#xff0c;Vue 3 和 Element Plus 是非常受欢迎的技术。Vue 3 是一个用于构建用户界面的渐进式 JavaScript 框架&#xff0c;而 Element Plus 是一个基于 Vue 3 的组件库&#xff0c;提供了丰富的 UI 组件&#xff0c;帮助开发者快速构建高质量的前端应用。 …

pyTorch框架部署实践

相关代码链接见文末 1.所需基本环境配置 首先&#xff0c;我们需要一个预先训练好的模型以及相应的配置。接下来&#xff0c;为了实际应用这个模型&#xff0c;我们必须搭建一个功能强大的服务器。这台服务器的核心任务是加载我们的模型&#xff0c;并能够接收用户上传的图片。…

保姆级,Linux中安装搭建Python环境

Linux中安装搭建Python环境 前手准备&#xff1a;在Linux中运行一下代码&#xff1a; yum install wget zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gcc make zlib zlib-devel libffi-devel -y进入Python下载官网https://www.…

[综述笔记]Benchmarking Graph Neural Networks for FMRI analysis

论文网址&#xff1a;[2211.08927] Benchmarking Graph Neural Networks for FMRI analysis (arxiv.org) ⭐不是真正意义上的综述&#xff0c;应该是分析性质的文章 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错…

【电控实物-infantry】

云台电机参数 电机内部参数 相电阻:Rs1.8欧 相电感:Ls5.7810^-3H 转矩常数:Kt 0.741 NM/A 转动惯量:J KG-m^2 电机接收数据&#xff1a;-16384到16384&#xff08;-3A到3A&#xff09; 电机反馈&#xff1a;速度RPM rad/s &#xff08;2πrpm&#xff09;/60 C板陀螺仪&…

【深度学习实战(20)】使用torchsummary打印模型结构

一、安装torchsummary库 pip install torchsummary 二、代码 import torchvision.models as models from torchsummary import summarymodel models.AlexNet() model.to(cuda) summary(model,(3,224, 224))

Base64编码原理和代码实现

1、Base64编码实现原理 第一步&#xff1a; 原理是把每 3 个字节&#xff08;每个字节为 8 位, 3 个字节为 24 位&#xff09;重新划为 4 组&#xff08;每组为 6位&#xff09; 第二步: 重新划分的每组 6 位的字节中&#xff0c;高位补两个 0 为 8 位后作为一个新的 8 位字节…

脚手架搭建项目package.json配置中依赖的版本问题

脚手架搭建项目package.json配置中依赖的版本问题 问题描述&#xff1a;项目刚搭建好&#xff0c;运行没有问题&#xff0c;为什么过一段时间&#xff0c;删除node_modules&#xff0c;或者重新安装包依赖&#xff0c;然后项目某些地方出现莫名的错误&#xff08;依赖库的地方…

Redis篇:缓存击穿及解决方案

1.何为缓存击穿 缓存击穿问题也叫热点Key问题&#xff0c;就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了&#xff08;有可能是正好过期了&#xff09;&#xff0c;无数的请求访问会在瞬间给数据库带来巨大的冲击。 常见的解决方案有两种&#xff1a; 互斥锁 逻…

逐行分析Transformer的程序代码,最后免费附上该代码!!

1. 代码详细解释 1. 第一段代码 这段代码首先定义了一些参数&#xff0c;包括编码器个数、输入维度、句子长度、词嵌入维度等。然后它保存了这些超参数到指定路径。接着&#xff0c;它加载训练和验证数据集&#xff0c;并创建了对应的数据加载器。之后&#xff0c;它定义了一个…

PHP 爬虫如何配置代理 IP(CURL 函数)

在 PHP中 配置代理IP&#xff0c;可以通过设置 CURL 库的选项来实现&#xff0c;代码如下&#xff1a; 当然你要有代理ip来源&#xff0c;比如我用的这个 代理商 &#xff0c;如果想服务稳定不建议找开源代理池&#xff0c;避免被劫持。 <?php // 初始化cURL会话 $ch cu…

xgp会员一年多少钱?xgp一个月多少钱?微软商店xgp会员价格指南

xgp是xbox游戏平台。xgp是类似于steam、epic等&#xff0c;拥有丰富游戏资源的平台。该平 台的全称为“XBox Game Pass”&#xff0c;俗称为“西瓜皮”。xgp是会员订阅模式&#xff0c;开启会员后&#xff0c;所有游戏资源都为你开放。pc版的&#xff0c;第一个月10港币&#x…