深蓝学院C++基础与深度解析笔记 第 8 章 动态内存管理

news2024/9/20 5:45:58

第 8 章 动态内存管理

1. 动态内存基础

● 栈内存 V.S. 堆内存
– 栈内存的特点:更好的局部性,用于语言的固有类型,对象自动销毁,由低到高开辟
– 堆内存的特点:运行期动态扩展,需要显式释放,由高到低开辟

● 在 C++ 中通常使用 new 与 delete 来构造、销毁对象
● 对象的构造分成两步:分配内存与在所分配的内存上构造对象;对象的销毁与之类似
● new 的几种常见形式
分配失败是不继续执行下面的代码的,所以在下面判断是否分配成功是检测不出分配失败的

– 构造单一对象 / 对象数组
– nothrow new            //分配失败了不抛出异常
– placement new          //已有内存,不需要额外分配,直接使用即可,谨慎使用new auto               //  

在这里插入图片描述

new 分配失败会抛出异常,
● new 与对象对齐
● delete 的常见用法

– 销毁单一对象 / 对象数组
– placement delete      //vector,只销毁地址,不返还给系统

使用 new 与 delete 的注意事项

– 根据分配的是单一对象还是数组,采用相应的方式销毁,怎么分配怎么销毁
– delete nullptr
– 不能 delete 一个非 new 返回的内存
– 同一块内存不能 delete 多次  // 释放完他还是那块内存,只是不能再用了

● 调整系统自身的 new / delete 行为

– 不要轻易使用

cppreference : operator new, operator new[]
补充:常见的指针错误使用情况:

  1. 空指针引用(Null Pointer Dereference):当一个指针被解引用(即访问指针指向的内存)时,但指针的值为null或未初始化时,就会发生空指针引用错误。这种错误通常会导致程序崩溃或异常终止。

  2. 内存泄漏(Memory Leaks):内存泄漏指的是程序在动态分配内存后,没有释放该内存而导致内存无法再被使用。如果重复发生内存泄漏,程序的内存消耗会逐渐增加,最终可能导致系统资源耗尽。

  3. 误用释放的内存(Incorrect Use of Freed Memory):当一个内存块被释放后,如果程序继续使用该内存块或者对其进行写操作,就会发生误用释放的内存错误。这可能导致数据损坏、崩溃或安全漏洞。

  4. 缓冲区溢出(Buffer Overflow):缓冲区溢出指的是向一个缓冲区写入超过其容量的数据,导致数据溢出到相邻的内存区域。这种错误可能会破坏程序的内存结构,导致未定义的行为和安全漏洞。

  5. 重复释放内存(Double Free):当同一个内存块被多次释放时,就会发生重复释放内存错误。这可能导致程序访问无效的内存或崩溃。

  6. 悬挂指针(Dangling Pointer):悬挂指针是指指针仍然指向有效内存,但该内存已经被其他操作或释放修改。使用悬挂指针可能导致访问无效数据或出现未定义行为。

  7. 野指针(Dangling Pointer):野指针指的是指向已释放或无效内存的指针。当程序中的指针指向一个已经释放的内存块或者指向一个无效的地址时,就称之为野指针。野指针可能产生的原因包括指针没有被正确初始化、指针指向的对象已经被释放、指针超出了其作用域等。当使用野指针时,程序可能会出现未定义行为,包括崩溃、数据损坏或安全漏洞等问题。

  8. 僵尸指针(Zombie Pointer):僵尸指针是指指向已经被释放的内存的指针,但程序仍然继续使用它。当一个指针指向的内存块被释放后,指针没有被及时置为 null 或重新分配,导致指针称为僵尸指针。使用僵尸指针可能会导致类似野指针的问题,因为程序可能会尝试访问已释放的内存。

这些指针错误都是常见的编程错误,可能导致程序的不稳定性、内存泄漏、安全漏洞或数据损坏。在编写程序时,应该注意避免这些错误,并采取适当的指针使用和内存管理策略。

2. 智能指针

● 使用 new 与 delete 的问题:内存所有权不清晰,容易产生不销毁,多销毁的情况
● C++ 的解决方案:智能指针

– auto_ptr ( C++17 删除)
– shared_ptr / uniuqe_ptr / weak_ptr  ( C++11 )

A、shared_ptr——基于引用计数的共享内存解决方案、类模板

Int* x(new int(3));

std::shared_ptr<int> x (new int(3)); 
std::shared_ptr<int> x(new int(3));          // 引用计数1 
std::shared_ptr<int> y = x;                  // 引用计数 2
  • 基本用法:先删除y,计数-1;再删除x,计数-1 ,引用计数为0时候,调用delete 收回地址。
std::shared ptr<int> fun() {
     std::shared_ptr<int> res(new int(3)); return res;
}

int main() 
{
     std::shared_ptr<int>x=fun(); 
     std::cout<< *x << endl;
}
  • 通过智能指针获取普通指针使用:.get() :
void fun2(int*x)
{
     std::cout << *x << Std::endl:
}

int main()
{
     std::shared_ptr<int>x = fun(); 
     fun2(x.get());
}
  • .reset():重新设置:如果已有关联,delete销毁后重新开辟空间;否则直接重新开辟空间
x.reset(new int(4));
void dummy(int*) {}

std::shared_ptr<int> fun() 
{
     static int res =3;
     return std::shared_ptr<int> (&res, dummy); 
}

int main()
{
     std::shared_ptr<int> x = fun(); 
}
  • 指定内存回收逻辑: new和delete相对较慢;内存池较快:从内存中先分割一块出来,使用后在还给内存池,减少new和delete提升性能。:
std::shared_ptr<类型>(&名字, 自定义delete); 
  • std::make_shared :
std::shared_ptr<int> x =std::make_share<int> (3);

在这里插入图片描述
引用计数和对象的内存会开辟到的尽量近一些

– 支持数组:

std: : shared_ptreint> x(new int[5]);     // 删除的时候可能有问题
std::shared_ptr<T[]>                      // C++17 支持
auto x = std::make_shared<int[5]>();      // C++20 支持  

– 注意: shared_ptr 管理的对象不要调用 delete 销毁! 因为它会自动销毁的,delete会导致内存销毁两次。

B、 unique_ptr——独占内存的解决方案,不可以拷贝,可以移动

  • 基本用法:
std : :unique ptreint> x( new int(3));
std: :unique ptreint> y=x;                // 不可以拷贝
std: :unique_ptr<int> y = std: :move(×);  //可以移动

  • 为 unique_ptr 指定内存回收逻辑
std:: unique ptr<int> funo)
       std::unique_ptr<int> res (new int( 3));
       return res;
)
int main(){
     std:: unique_ptr<int> x = fun();
)

** **——防止循环引用而引入的智能指针
– 基于 shared_ptr 构造
– lock 方法


struct str
std: : shared_ptreStr> nei;-str{)
{
std: :cout << "~Str is calledin";
}
};
int main()
{
    std::shared_ptr<Str> x(new Str);
    std::shared ptr<Str> y(new Str);
    x->nei = y;
    y->nei = x;        // 循环引用,产生 环形,无法释放
}

3. 动态内存的相关问题

● sizeof 不会返回动态分配的内存大小,只返回编译器相关大小
● 使用分配器( allocator )来分配内存,只分配,不构造!

std: :allocatorcint> al;
int* ptr = al.allocate(3);
deallocate                   // 内存回收

● 使用 malloc / free 来管理内存,属于C的方式,只分配不构造!不分配对其的内存。

需要参数输入分配大小,

● 使用 aligned_alloc 来分配对齐内存
● 动态内存与异常安全:

  • delete 之前异常可能会产生内存泄漏。解决方式:智能指针 shared_ptr

● C++ 对于垃圾回收的支持 :没有内置的垃圾回收机制,有6个相关函数:
在这里插入图片描述
补充:malloc / free和new/delete区别?

mallocfree是C语言中的内存分配和释放函数,而newdelete是C++语言中的内存分配和释放操作符。它们之间存在以下区别:

  1. 语法差异: mallocfree是函数,需要使用函数调用的语法,而newdelete是操作符,使用类似于关键字的语法。

    // C语言中的malloc和free
    void* malloc(size_t size);
    void free(void* ptr);
    
    // C++语言中的new和delete
    type* new type;
    delete ptr;
    
  2. 类型安全: malloc返回void*指针,需要手动进行类型转换,而new操作符在分配内存时会自动进行类型推断,并返回正确类型的指针。同样,delete操作符也会根据指针的类型自动释放内存。

  3. 构造函数和析构函数调用: new操作符在分配内存后会自动调用对象的构造函数,而delete操作符在释放内存前会自动调用对象的析构函数。这使得使用newdelete的对象能够自动执行构造和析构的操作,方便对象的初始化和清理。

    // 使用new和delete创建和销毁对象
    MyObject* obj = new MyObject();  // 调用MyObject的构造函数
    delete obj;                      // 调用MyObject的析构函数
    
    // 使用malloc和free分配和释放内存
    MyObject* obj = (MyObject*)malloc(sizeof(MyObject));
    free(obj);
    
  4. 数组分配: new操作符可以用于分配动态数组,而malloc无法直接分配动态数组。当需要分配数组时,使用new[],并使用delete[]进行释放。

    // 使用new[]和delete[]创建和销毁动态数组
    int* arr = new int[5];
    delete[] arr;
    
    // 使用malloc和free分配和释放内存
    int* arr = (int*)malloc(5 * sizeof(int));
    free(arr);
    

当内存分配失败时,可以采取以下处理方式:

  1. 检查返回值: 在使用mallocnew进行内存分配后,应该检查返回的指针是否为NULL,这表示内存分配失败。如果返回的指针为NULL,则说明系统无法满足所需的内存空间,此时应该采取相应的错误处理措施。

    // 使用malloc进行内存分配
    int* ptr = (int*)malloc(sizeof(int));
    if (ptr == NULL) {
        // 内存分配失败,进行错误处理
    }
    
    // 使用new进行内存分配
    int* ptr = new int;
    if (ptr == nullptr) {
        // 内存分配失败,进行错误处理
    }
    
  2. 抛出异常: 在C++中,可以使用异常机制来处理内存分配失败的情况。当new操作符无法分配所需的内存时,会抛出std::bad_alloc异常,我们可以通过捕获该异常并进行相应的错误处理。

    try {
        // 使用new进行内存分配
        int* ptr = new int;
        // 内存分配成功
    } catch (const std::bad_alloc& e) {
        // 内存分配失败,进行错误处理
    }
    
  3. 释放已分配的内存: 如果内存分配失败后,已经分配了一部分内存,但无法继续分配所需的额外内存,可以通过手动释放已分配的内存来回收资源。

    int* ptr1 = new int;
    int* ptr2 = new int;
    // ...
    
    int* ptr3 = new int;
    if (ptr3 == nullptr) {
        // 内存分配失败,释放已分配的内存
        delete ptr1;
        delete ptr2;
        // 进行错误处理
    }
    

无论采取哪种处理方式,当内存分配失败时,必须确保对已分配的内存进行适当的释放,以避免内存泄漏和其他潜在的问题。

总的来说,newdelete是C++中更高级、更安全的内存分配和释放方式,它们提供了更多的语法特性和类型安全,并且可以自动处理对象的构造和析构函数。然而,在C语言中只能使用mallocfree进行内存分配和释放。如果在C++中使用mallocfree,则需要手动调用构造和析构函数,而且类型安全性较差。

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

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

相关文章

使用 TailwindCSS 中的 color-mix() 构建自定义调色板

在这篇文章中&#xff0c;我们将了解如何使用 CSS 函数color-mix()和 CSS 变量&#xff0c;通过 TailwindCSS 高效地为 Nuxt 应用程序生成自定义调色板。 先决条件 最好使用以下命令设置 Nuxt 应用程序&#xff1a; npx nuxi init tailwindcss-color-mix 在安装提示期间选择 …

新项目即将启动!小灰做个市场调研

熟悉小灰的小伙伴们都知道&#xff0c;在2019年初&#xff0c;做了整整10年程序员的小灰离开职场&#xff0c;成为了一名自由职业者。 2021年末&#xff0c;小灰注册了自己的公司&#xff0c;名为北京小灰大黄科技有限公司。 公司虽然注册了&#xff0c;但是整个公司只有小灰一…

【C2】文件,时间,多线程,动静态库

文章目录 1.文件&#xff1a;fprint/fgets/fwrite/fread&#xff0c;ftell/rewind/fseek/fflush1.1 文本文件&#xff1a;FILE结构体1.2 二进制文件&#xff1a;没有行概念1.3 文件定位&#xff1a;linux下文本文件模式和二进制文件模式没有区别。fgets和fprintf以行方式读写文…

【Flutter】Flutter 国际化入门 使用 intl 包 格式化日期

文章目录 一、 前言二、 版本信息三、 什么是 intl 包四、 如何安装和使用 intl 包1. 安装 intl 包2. 使用 intl 包进行基本的日期和数字格式化3. 使用 intl 包进行消息翻译 五、 一个简单的使用示例六、 总结 一、 前言 在全球化的今天&#xff0c;为你的 Flutter 应用添加国…

快速上手MATLAB图像处理:100种项目全覆盖

本教程涵盖了MATLAB图像处理的广泛内容。我们学习了图像读取、显示和保存,图像的基本操作(如缩放、裁剪、旋转和翻转),以及图像的基本增强(如亮度调整、对比度调整和颜色空间转换)。本教程还介绍了常见的图像滤波技术(如均值滤波、中值滤波和高斯滤波),图像的直方图均…

JAVA临时文件的使用

目录 什么是临时文件&#xff1f; 临时文件在编程中有各种妙用 java在缓存目录创建临时文件的方式 1 按照指定文件名随机数字共同作为文件名创建 2 按照指定文件名创建 3 通过获取临时文件夹的真实路径 什么是临时文件&#xff1f; 临时文件是在计算机系统中用于临时存储数…

Spring Cloud - Gateway统一网关、断言工厂、过滤器工厂、全局过滤器、跨域问题

目录 一、什么是网关&#xff1f;为什么选择 Gateway? 二、Gateway 网关 2.1、搭建网关服务 1.创建新的module&#xff0c;引入SpringCloudGateway的依赖和nacos的服务发现依赖 2.编写nacos地址和路由配置 2.2、路由断言工厂PredicateFactory 2.3、路由过滤器 GatewayF…

2015年全国硕士研究生入学统一考试管理类专业学位联考逻辑试题——纯享题目版

&#x1f3e0;个人主页&#xff1a;fo安方的博客✨ &#x1f482;个人简历&#xff1a;大家好&#xff0c;我是fo安方&#xff0c;考取过HCIE Cloud Computing、CCIE Security、CISP、RHCE、CCNP RS、PEST 3等证书。&#x1f433; &#x1f495;兴趣爱好&#xff1a;b站天天刷&…

如何删除Git仓库中的敏感文件及其历史记录

本文主要介绍如何使用 git filter-branch 命令删除 Git 仓库中的敏感文件及其历史记录。在 Git 中&#xff0c;我们通常会将敏感信息(如密码、私钥等)存储在 .gitignore 文件中&#xff0c;以防止这些信息被意外提交到仓库。有时候&#xff0c;因为疏忽或私有仓库转公开仓库&am…

SQL15 查看学校名称中含北京的用户

SELECT device_id,age,university FROM user_profile WHERE university LIKE %北京%下划线 代表匹配任意一个字符&#xff1b; % &#xff1a;百分号 代表匹配0个或多个字符&#xff1b; []: 中括号 代表匹配其中的任意一个字符&#xff1b; [^]: ^尖冒号 代表 非&#xff0c;取…

CRM系统通过哪三步增加销售团队协作?

销售团队的协作是企业成功的重要保障。协调一致的销售团队能够提升销售效率&#xff0c;提高销售转化&#xff0c;获得更多业绩收入。那么企业要如何增加销售团队的协作&#xff1f;可以用CRM销售管理系统。 CRM系统如何增加销售团队协作&#xff1a; 1、建立统一的客户数据库…

SAP ABAP 如果某字段没有参数ID,如自开发程序使用的自建表 新建参数ID

1&#xff09;新建参数ID sm30 TPARA 维护 输入ID和描述 2&#xff09; 参数ID和Se11数据元素 绑定

【EasyX】扫雷

目录 扫雷1. 主体功能描述2、主要实现步骤3、效果图 扫雷 本博客介绍利用EasyX加上图片、音乐素材实现一个传统的扫雷小游戏。 1. 主体功能描述 1、全局变量&#xff1a;时间、地图、图片资源、状态&#xff1b; 2、绘图初始化函数drawinit&#xff1a;载入图片资源&#xf…

力扣 701. 二叉搜索树中的插入操作

题目来源&#xff1a;https://leetcode.cn/problems/insert-into-a-binary-search-tree/description/ 思路&#xff1a;只要根据二叉搜索树的特性&#xff0c;将新插入节点的值不断地与树节点值进行比较&#xff0c;然后找到新节点所属的叶子节点位置&#xff0c;插入即好&…

Jetson Nano供电

1.Jetson Nano供电 Jetson Nano开发板有5种供电方式&#xff1a; 5V 2A(micro USB) 5V 3A(GPIO引脚) 5V 4A(DC接口) 5V 6A(所有电源IO反向供电) POE供电 其中&#xff0c; 5V 2A是受限于USB自身&#xff0c;强烈推荐DC 4A供电&#xff0c;满足Jetson Nano大部分使用场景&…

Linux基础_2

目录 一、获取帮助 1、whatis 2、查看命令的帮助 内部命令 外部命令 3、man命令 作用&#xff1a;提供命令帮助的文件 4、info命令 作用&#xff1a;常用于命令参考&#xff0c;GNU工具&#xff0c;适合通用文档参考 5、Linux安装提供的本地文档获取帮助 Applicatio…

ES6的类 vs TypeScript的类:解密两种语言中的面向对象之争

文章目录 ES6 类ES6 类的常见特性1. 构造函数2. 实例方法3. 静态方法4. 继承 TypeScript 类TypeScript 类的特性1. 类型注解2. 访问修饰符3. 类型推断4. 接口实现 ES6 类 ES6&#xff08;ECMAScript 2015&#xff09;引入了类的概念&#xff0c;为 JavaScript 增加了面向对象编…

费马原理与光的反射折射

费马原理&#xff1a;光传播的路径是光程取极值的路径 光的反射 如上图所示&#xff0c;光从P点出发射向x点&#xff0c;反射到Q点。 P 点到 x 点的距离 d 1 x 2 a 2 d1 \sqrt{x^2 a^2} d1x2a2 ​ Q 点到 x 点的距离 d 2 b 2 ( l − x ) 2 d2 \sqrt{b^2 (l-x)^2} d2…

WebAPIs-DOM操作元素属性/自定义属性

Web APIs web APIs 操作页面元素做出各种效果 DOM 文档对象模型 使用js操作页面文档 BOM 浏览器对象模型 使用js操作浏览器 API 应用程序接口 接口&#xff1a;无需关心内部如何实现&#xff0c;只需要调用就可以很方便实现某些功能 作用&#xff1a;使用js提供的接口来操…