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

news2024/11/26 23:49:47

目录

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

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

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

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

a. new的优势

b. new的不足

c. delete的优势

d. 总结

3. 怎么使用new和delete?

a. 对于内置类型

b. 对于自定义类型 

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

4. new和delete的实现原理

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

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

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

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


〇、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.coloradmin.cn/o/1631792.html

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

相关文章

python学习笔记----循环语句(四)

一、while循环 为什么学习循环语句 循环在程序中同判断一样&#xff0c;也是广泛存在的&#xff0c;是非常多功能实现的基础&#xff1a; 1.1 while循环语法 while 条件表达式:# 循环体# 执行代码这里&#xff0c;“条件表达式”是每次循环开始前都会评估的表达式。如果条件…

【血泪教训】Altium Designer隐藏覆铜层导致PCB电路板未加工隐藏层

Altium Designer隐藏覆铜层导致PCB电路板未加工隐藏层 血泪教训&#xff01;&#xff01;&#xff01; 事情经过是这样的 测试板PCB Layout完成后&#xff0c;隐藏铺铜层&#xff0c;方便check&#xff0c;隐藏操作如下图所示&#xff0c;选择“隐藏所有”或“隐藏选中铺铜”…

Redis基本數據結構 ― String

Redis基本數據結構 ― String 介紹常用命令範例1. 為字串鍵設值/取得字串鍵的值2. 查看字串鍵的過期時間3. 如何為key設置時間?4. 如何刪除指定key?5. 如何增加value的值?6. 獲取value值的長度 介紹 字串鍵是Redis中最基本的鍵值對類型&#xff0c;這種類型的鍵值對會在數據…

【python技术】使用akshare抓取东方财富所有概念板块,并把指定板块概念的成分股保存excel 简单示例

最近有个想法&#xff0c;分析A股某个概念成分股情况进行分析&#xff0c;第一反应是把对应概念板块的成分股爬取下来。说干就干 下面是简单示例 import akshare as ak import pandas as pddef fetch_and_save_concept_stocks(name):# 获取指定股票概念的成分股&#xff0c;并…

机器学习每周挑战——百思买数据

最近由于比赛&#xff0c;断更了好久&#xff0c;从五一开始不会再断更了。这个每周挑战我分析的较为简单&#xff0c;有兴趣的可以将数据集下载下来试着分析一下&#xff0c;又不会的我们可以讨论一下。 这是数据集&#xff1a; import pandas as pd import numpy as np impo…

Leetcode-面试题 02.02. 返回倒数第 k 个节点

目录 题目 图解 代码 面试题 02.02. 返回倒数第 k 个节点 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/kth-node-from-end-of-list-lcci/description/ 题目 实现一种算法&#xff0c;找出单向链表中倒数第 k 个节点。返回该节点的值。 注意&…

保证接口幂等性的多种实现方式(数据库方案)

1. 幂等性的概念 接口幂等性是指在软件工程和Web服务领域中&#xff0c;一个接口&#xff08;通常是HTTP API&#xff09;无论被调用一次还是多次&#xff0c;其对系统产生的副作用应该是相同的&#xff0c;即结果保持一致&#xff0c;不会因为多次请求而有所不同。换句话说&am…

ES练习项目-酒店搜索

目录 1 需求分析2 酒店搜索和分页2.1 请求和响应分析2.2 定义实体类&#xff0c;接收请求参数的JSON对象2.3 编写controller&#xff0c;接收页面的请求2.4 编写业务实现&#xff0c;利用RestHighLevelClient实现搜索、分页 3. 酒店结果过滤3.1 请求和响应分析3.2 修改请求参数…

上位机图像处理和嵌入式模块部署(树莓派4b设置ftp下载)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 作为一个开发板&#xff0c;最好支持ftp下载&#xff0c;这样文件的上传和下载都会比较方便。虽然目前为止&#xff0c;利用mobaxterm和ssh也能实现…

算法学习(5)-图的遍历

目录 什么是深度和广度优先 图的深度优先遍历-城市地图 图的广度优先遍历-最少转机 什么是深度和广度优先 使用深度优先搜索来遍历这个图的过程具体是&#xff1a; 首先从一个未走到过的顶点作为起始顶点&#xff0c; 比如以1号顶点作为起点。沿1号顶点的边去尝试访问其它未…

用Jenkins实现cherry-pick多个未入库的gerrit编译Android固件

背景: 在做Android固件开发的时候,通常我们可以利用gerrit-trigger插件,开发者提交一笔的时候自动触发jenkins编译,如果提交的这一笔的编译依赖其他gerrit才能编译过,我们可以在commit message中加入特殊字段,让jenkins在编译此笔patch的时候同时抓取依赖的gerrit代码下…

selenium在Pycharm中结合python的基本使用、交互、无界面访问

下载 下载与浏览器匹配的浏览器驱动文件&#xff0c;这里一定注意的是&#xff0c;要选择和浏览器版本号相同的驱动程序&#xff0c;否则后面会有很多问题。 &#xff08;1&#xff09;浏览器&#xff08;以google为例&#xff09;版本号的查询&#xff1a; 我这里的版本号是1…

RT-Thread V5.2.0版本尝鲜

文章目录 配置界面的更新新旧内核生成的二进制文件大小差异旧版本V5.1.0内核旧版本V5.2.0内核 配置界面的更新 尝试将手头RT-Thread工程的OS部分源码进行了更新&#xff0c;发现不少新的变化 配置界面变得更醒目&#xff1a; 配置变更保存提醒界面更新&#xff1a; 新旧内核…

Android Widget开发代码示例详细说明

因为AppWidgetProvider扩展自BroadcastReceiver, 所以你不能保证回调函数完成调用后&#xff0c;AppWidgetProvider还在继续运行。 a. AppWidgetProvider 的实现 /*** Copyright(C):教育电子有限公司 * Project Name: NineSync* Filename: SynWidgetProvider.java * Author(S…

【Node.js工程师养成计划】之原生node开发web服务器

一、使用node创建http服务器 var http require(http);// 获取到服务器实例对象 var server http.createServer() server.listen(8080, function() {console.log(http://127.0.0.1:8080); })server.on(request, function(req, res){console.log(request);res.write(6666666688…

VitePress 构建的博客如何部署到 github 平台?

VitePress 构建的博客如何部署到 github 平台&#xff1f; 1. 新建 github 项目 2. 构建 VitePress 项目 2.1. 设置 config 中的 base 由于我们的项目名称为 vite-press-demo&#xff0c;所以我们把 base 设置为 /vite-press-demo/&#xff0c;需注意前后 / export default…

【数据结构与算法(C语言)】1. 线性表的顺序存储

文章目录 前言一. 线性表插入和删除1. 元素的插入2. 元素的删除 二. 代码三. 优缺点 前言 线性表的顺序存储结构&#xff0c;指的是用一段地址连续的存储单元依次存储线性表的数据结构 一. 线性表插入和删除 1. 元素的插入 插入位置之后的数据都向后移一位&#xff0c;上图中元…

chrome 安装devtools

chrome 安装devtools 下载安装 链接&#xff1a;https://github.com/vuejs/devtools 选择对应版本&#xff1a; 安装yarn 下载 npm install -g yarn --registryhttps://registry.npmmirror.com进入下载的目录安装依赖 yarn install --registryhttps://registry.npmmirror.…

ASP.NET汽车销售管理系统的设计与开发

摘 要 随着人们生活水平的不断提高&#xff0c;人们对汽车的消费和需求也越来越旺盛。很多汽车销售公司的业务环节仍然运用人工记账的传统方法&#xff0c;既容易出错又会导致账目混乱&#xff0c;查询和统计起来也非常不方便&#xff0c;费时又费力&#xff0c;严重时会给公…

stm32单片机开发二、定时器-内部时钟中断和外部时钟中断、编码器

定时器本质就是一个计数器 案例&#xff1a;定时器定时中断 内部时钟中断 Timer_Init(); //定时中断初始化 /*** 函 数&#xff1a;定时中断初始化* 参 数&#xff1a;无* 返 回 值&#xff1a;无*/ void Timer_Init(void) {/*开启时钟*/RCC_APB1PeriphClockCmd(RCC…