『C++成长记』内存管理

🔥博客主页:小王又困了

📚系列专栏:C++

🌟人之为学,不日近则日退

❤️感谢大家点赞👍收藏⭐评论✍️

目录

一、C/C++内存分布

二、内存管理方式

📒2.1C语言内存管理方式 

📒2.2C++内存管理方式 

🎀2.2.1new/delete操作内置类型

🎀2.2.1new和delete操作自定义类型

三、operator new与operator delete函数

四、new和delete的实现原理

📒4.1内置类型

📒4.2自定义类型

五、定位new表达式(placement-new)

📒5.1概念

📒5.2使用格式

📒5.3使用场景

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

 七、内存泄漏

📒7.1什么是内存泄漏

📒7.2内存泄漏的危害

📒7.3内存泄漏分类

📒7.4检测内存泄漏

📒7.5避免内存泄漏


一、C/C++内存分布

    在C和C++中,内存分布主要包括以下几个部分:

代码段(Code Segment):

  • 包含程序的机器代码,也称为文本段。
  • 是只读的,存储程序的执行代码。
  • 在程序运行时不可修改

数据段(Data Segment):

  • 包括初始化的全局变量静态变量
  • 在程序开始运行时分配,并在整个程序的执行周期内保持不变
  • 数据区(Data Section): 存储已经初始化的全局变量和静态变量。
  • BSS段(Block Started by Symbol): 存储未初始化的全局变量和静态变量。

堆(Heap):

  • 由程序员分配和释放的内存区域。
  • 动态分配的内存(例如使用newmalloc函数)存储在这里。
  • 堆的大小在程序运行时可以动态调整。

栈(Stack):

  • 存储函数的局部变量和函数调用的上下文信息。
  • 由编译器自动分配和释放。
  • 栈的大小在程序运行时是固定的。

堆栈区域之间的空间(Heap-Stack Gap):

  • 用于防止堆和栈之间的冲突。
  • 可以防止堆溢出覆盖栈数据或栈溢出覆盖堆数据。

 小Tips:这些区域在内存中的布局可能因操作系统和编译器而异,但通常遵循相似的结构。在运行时,栈的地址通常位于较高的内存地址,而堆的地址通常位于较低的内存地址。代码段和数据段的位置取决于可执行文件的加载方式,通常在较高的内存地址。 

二、内存管理方式

📒2.1C语言内存管理方式 

    C语言提供了一些基本的内存管理函数和操作符,使程序能够有效地分配和释放内存。以下是C语言中常用的内存管理方式:

📝malloc() 和 free():

  • malloc()函数用于动态分配指定大小的内存空间。
  • free()函数用于释放先前由malloc()分配的内存空间。
  • 例子:
// 分配10个整数大小的内存空间
int *ptr = (int *)malloc(10 * sizeof(int));
if (ptr != NULL) 
{
    // 使用内存
    // ...
    // 释放内存
    free(ptr);
}

📝 calloc() 和 realloc():

  • calloc()函数用于分配指定数量和大小的内存空间,并将其初始化为零。
  • realloc()函数用于更改之前分配的内存块的大小。
  • 例子:
// 分配并初始化为0,10个整数大小的内存空间
int *ptr = (int *)calloc(10, sizeof(int));
if (ptr != NULL)
{
    // 使用内存
    // ...

    // 重新分配内存空间为20个整数大小
    ptr = (int *)realloc(ptr, 20 * sizeof(int));

    // 使用重新分配后的内存
    // ...

    // 释放内存
    free(ptr);
}

📝静态分配:

  • 静态分配是在编译时为变量分配内存空间。
  • 包括全局变量、静态变量和局部变量(在函数外部定义的变量是静态的)。
  • 内存分配和释放由编译器自动处理。

📝自动变量:

  • 在函数内部定义的变量通常是自动变量。
  • 自动变量的内存分配和释放由程序的执行控制流决定。

📝指针和动态内存分配:

  • 使用指针进行内存分配和释放是C语言中动态内存管理的重要部分。
  • 必须小心管理分配的内存,以避免内存泄漏和悬空指针问题。

 小Tips:C语言中的内存管理是相对低级的,程序员需要手动分配和释放内存。不正确的内存管理可能导致内存泄漏、悬空指针或者堆栈溢出等问题。因此,编写健壮和高效的代码需要仔细管理内存的分配和释放。

📒2.2C++内存管理方式 

    C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理

🎀2.2.1new/delete操作内置类型

void Test()
{
  // 动态申请一个int类型的空间
  int* ptr1 = new int;
  
  // 动态申请一个int类型的空间并初始化为10
  int* ptr2 = new int(10);
  
  // 动态申请10个int类型的空间,不初始化
  int* ptr3 = new int[10];
  // 动态申请10个int类型的空间,并初始化前三个
  int* ptr4 = new int[10]{1,2,3};
  delete ptr1;
  delete ptr2;
  delete[] ptr3;
  delete[] ptr4;
}

小Tips:用new开辟空间是不会进行初始化的。如果我们想动态申请并初始化,类型后面要跟圆括号 () ,动态申请多个连续空间,类型后面跟的是方括号 [ ] ,如果要对这多个连续的空间初始化,可以在 [ ] 的后面跟 { }  { } 里面是初始化的数据,初始化的值不够,后面会默认初始化为0。申请和释放单个元素的空间,使用newdelete操作符,申请和释放连续的空间,使用new[ ]delete[ ]

🎀2.2.1new和delete操作自定义类型

class A
{
public:
    A(int a = 0)
        : _a(a)
    {
        cout << "A():" << this << endl;
    }

    ~A()
    {
        cout << "~A():" << this << endl;
    }
private:
    int _a;
};

int main()
{
    //通过new动态申请一块空间,存储A类型变量
    A* p1 = new A(1);
    delete p1;

    //通过new动态申请3个连续空间,存储3个A类型的数据
    A* p2 = new A[3];
    delete[] p2;
    return 0;
}

小Tips:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而mallocfree不会。如果自定义类型没有默认构造函数,我们在new的时候就需要人为的传参进行初始化,并且申请了几块空间就要初始化几个,不像动态申请的内置类型数组,可以只初始化前面一部分,后面默认是0。

A* p6 = new A[3]{ 1, 2, 3 };//通过隐式类型转换去初始化
A* p6 = new A[3]{ A(1), A(2), A(3) };//通过匿名对象去初始化

三、operator new与operator delete函数

    newdelete是用户进行动态内存申请和释放的操作符operator newoperator delete系统提供的全局函数new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
     if (_callnewh(size) == 0)
     {
         // report no memory
         // 如果申请内存失败了,这里会抛出bad_alloc 类型异常
         static const std::bad_alloc nomem;
         _RAISE(nomem);
     }
return (p);
}

//operator delete: 该函数最终是通过free来释放空间的

void operator delete(void *pUserData)
{
     _CrtMemBlockHeader * pHead;
     RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
     if (pUserData == NULL)
         return;
     _mlock(_HEAP_LOCK);  /* block other threads */
     __TRY
         /* get a pointer to memory block header */
         pHead = pHdr(pUserData);
          /* verify block type */
         _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
         _free_dbg( pUserData, pHead->nBlockUse );
     __FINALLY
         _munlock(_HEAP_LOCK);  /* release other threads */
     __END_TRY_FINALLY
     return;
}

//free的实现

#define   free(p)               _free_dbg(p, _NORMAL_BLOCK)

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

小Tips:operator newoperator delete不是运算符重载。operator newoperator deletemallocfree在功能上相似,都是到堆上申请空间,然后释放空间,它们的区别在于,前者失败了会抛异常,而后者失败了是返回错误码,通过返回值来表示这一块有问题。

四、new和delete的实现原理

📒4.1内置类型

    如果申请的是内置类型的空间,newmallocdeletefree基本类似,不同的地方是:new在申请空间失败时会抛异常,malloc会返回NULL。  newdelete申请和释放的是单个元素的空间,new[]delete[]申请的是连续空间。

📒4.2自定义类型

📖new的原理

  1. 调用operator new函数申请空间。
  2. 在申请的空间上执行构造函数,完成对象的构造。

📖delete的原理

  1. 在空间上执行析构函数,完成对象中资源的清理工作。
  2. 调用operator delete函数释放对象的空间。

📖new T[N]的原理 

  1. 调用operatr new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请。
  2. 在申请的空间上执行N次构造函数。

📖delete[]的原理

  1.  在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理。
  2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间。

五、定位new表达式(placement-new)

📒5.1概念

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

📒5.2使用格式

  • new (place_address) type
  • new (place_address) type(initializer-list)

place_address必须是一个指针,initializer-list是类型的初始化列表

📒5.3使用场景

    定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如 果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。

int main()
{
 // p1现在指向的是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没
有执行
    A* p1 = (A*)malloc(sizeof(A));
    new(p1)A;  // 注意:如果A类的构造函数有参数时,此处需要传参
    p1->~A();
    free(p1);

    A* p2 = (A*)operator new(sizeof(A));
    new(p2)A(10);
    p2->~A();
    operator delete(p2);
    return 0;
}

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

📖共同点:

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

📖不同点:

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

七、内存泄漏

📒7.1什么是内存泄漏

    内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

📒7.2内存泄漏的危害

    长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死

📒7.3内存泄漏分类

C/C++程序中一般我们关心两种方面的内存泄漏:

  • 堆内存泄漏(Heap leak)

    堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一 块内存,用完后必须通过调用相应的free或者delete删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak

  • 系统资源泄漏

    指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

📒7.4检测内存泄漏

    在vs下,可以使用windows操作系统提供的_CrtDumpMemoryLeaks() 函数进行简单检测,该函数只报出了大概泄漏了多少个字节,没有其他更准确的位置信息。

int main()
{
    int* p = new int[10];
    // 将该函数放在main函数之后,每次程序退出的时候就会检测是否存在内存泄漏
    _CrtDumpMemoryLeaks();
    return 0;
}

因此写代码时一定要小心,尤其是动态内存操作时,一定要记着释放。但有些情况下总是防不胜 防,简单的可以采用上述方式快速定位下。如果工程比较大,内存泄漏位置比较多,不太好查时一般都是借助第三方内存泄漏检测工具处理的。

📒7.5避免内存泄漏

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这是理想状态,但是如果碰上异常时,就算注意释放了,还是可能会出现问题。需要下面提到的智能指针来管理才有保证。
  2. 采用RAII思想或者智能智能指针来管理资源。
  3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
  4. 出问题了使用内存泄漏检测工具检测。ps:很多工具都不够靠谱,或者收费昂贵。
     

总结:内存泄漏非常常见,解决方案主要分为以下两种:

  1. 事前预防性型,如智能指针等。
  2. 事后查错型,如泄漏检测工具。

🎁结语: 

     本次的内容到这里就结束啦。希望大家阅读完可以有所收获,同时也感谢各位读者三连支持。文章有问题可以在评论区留言,博主一定认真认真修改,以后写出更好的文章。你们的支持就是博主最大的动力。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue2使用 element表格展开功能渲染子表格

默认样式 修改后 样式2 <el-table :data"needDataFollow" border style"width: 100%"><el-table-column align"center" label"序号" type"index" width"80" /><el-table-column align"cent…

CTF CRYPTO 密码学-3

题目名称&#xff1a;反编译 题目描述&#xff1a; 分析 题目给出一个pyc后缀的文件&#xff0c;需要使用uncompyle6模块去还原成py文件 uncompyle6简介 uncompyle6 是一个 Python 反编译器&#xff0c;它能够将 Python 字节码&#xff08;.pyc 文件&#xff09;转换回源代码&…

定制键盘设计

方案1 stm32方案 参考 智辉君的键盘 方案2 沁恒方案 CH9328与ch9329区别&#xff1a;一个是单向&#xff0c;一个是双向。 ch9329是ch9328的升级款。 原理篇4、CH9328使用-CSDN博客https://blog.csdn.net/qq_44817843/article/details/112124822

容器部署的nextcloud配置onlyoffice时开启密钥

容器部署的nextcloud配置onlyoffice时开启密钥 配置 进入onlyoffice容器 docker exec -it 容器id bash编辑配置vi /etc/onlyoffice/documentserver/local.json enable设置为true&#xff0c;并配置secret 重启容器&#xff0c;并将配置的密钥填入nextcloud密钥页面 docker r…

力扣hot100 零钱兑换 背包 滚动数组

Problem: 322. 零钱兑换 文章目录 &#x1f388; 思路&#x1f496; Code &#x1f388; 思路 &#x1f468;‍&#x1f3eb; 大佬题解 &#x1f496; Code ⏰ 时间复杂度: O ( n ) O(n) O(n) class Solution {public int coinChange(int[] coins, int amount){int INF …

「爱下馆子」真的会寿命更短吗?

​“爱下馆子”真的会寿命更短吗&#xff1f; 随着现代生活节奏的加快&#xff0c;越来越多的人选择在外就餐&#xff0c;也就是我们常说的“下馆子”。然而&#xff0c;最近的一项研究发现&#xff0c;每天至少两顿在外就餐的人&#xff0c;全因死亡率增加了49%。这一发现引发…

Python图像处理【18】边缘检测详解

边缘检测详解 0. 前言1. 图像导数2. LoG/zero-crossing2.1 Marr-Hildteth 算法 3. Canny 与 holistically-nested 算法3.1 Canny 边缘检测3.2 holistically-nested 边缘检测 小结系列链接 0. 前言 边缘是图像中两个区域之间具有相对不同灰级特性的边界&#xff0c;或者说是亮度…

Configure Virtual Serial Port Driver串口模拟器VSPD

背景 串口通讯想必做硬件开发和软件的人来说都相当了解&#xff0c;以前的电脑&#xff0c;基本标配都包含一个串口。但现在的电脑&#xff0c;基本都没有配置串口了&#xff0c;如果要使用串口的功能&#xff0c;基本就要用一个USB转串口的硬件模块。 虚拟串口&#xff08;虚…

不同打包工具下的环境变量配置方式对比

本文作者为 360 奇舞团前端开发工程师 天明 前言 在现代的JavaScript应用程序开发中&#xff0c;环境变量的配置是至关重要的。不同的应用场景和部署环境可能需要不同的配置&#xff0c;例如开发、测试和生产环境。最常见的需求是根据不同的环境&#xff0c;配置如是否开启sour…

Transformer从菜鸟到新手(七)

引言 上篇文章加速推理的KV缓存技术&#xff0c;本文介绍让我们可以得到更好的BLEU分数的解码技术——束搜索。 束搜索 我们之前生成翻译结果的时候&#xff0c;使用的是最简单的贪心搜索&#xff0c;即每次选择概率最大的&#xff0c;但是每次生成都选择概率最大的并不一定…

C++实战Opencv第一天——win11下配置vs,opencv环境和运行第一个c++代码(从零开始,保姆教学)

OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的计算机视觉和机器学习软件库&#xff0c;它提供了大量的通用算法和功能&#xff0c;用于处理图像和视频数据。C 通常提供比 Python 更高的执行速度&#xff0c;对于需要高性能处理的任务&#x…

【XR806开发板试用】留言板功能开发

开发板简介 XR806开源鸿蒙开发板是一款基于XR806芯片设计&#xff0c;高度集成WiFi/BLE/常用外设&#xff0c;可供开发者进行方案评估、DIY或小规模产品研发&#xff0c;可广泛应用于智能家居、智能楼宇、智能城市和工业互联等领域。 搭载OpenHarmony系统&#xff08;已通过O…

Linux中的yum源仓库和NFS文件共享服务

一.yum简介 1.1 yum简介 yum&#xff0c;全称“Yellow dog Updater, Modified”&#xff0c;是一个专门为了解决包的依赖关系而存在的软件包管理器。类似于windows系统的中电脑软件关键&#xff0c;可以一键下载&#xff0c;一键安装和卸载。yum 是改进型的 RPM 软件管理器&am…

使用CloudFlare-Woker搭建简易网站

使用CloudFlare-Woker搭建简易网站 1、首先到CloudFlare官网登录或注册自己的账号&#xff1a;Cloudflare 中国官网 | 智能化云服务平台 | 免费CDN安全防护 | Cloudflare (cloudflare-cn.com) 注册一个账号 2、登录账号后进入仪表盘网页&#xff0c;选择Workers & Pages页…

2019年认证杯SPSSPRO杯数学建模C题(第二阶段)保险业的数字化变革全过程文档及程序

2019年认证杯SPSSPRO杯数学建模 基于统计建模的车险业数字变革研究 C题 保险业的数字化变革 原题再现&#xff1a; 车险&#xff0c;即机动车辆保险。保险自身是一种分散风险、消化损失的经济补偿制度&#xff0c;车险即为分散机动车辆在行驶过程中可能发作的未知风险和损失…

uni-app引用矢量库图标

矢量库引用 导入黑色图标 1.生成连接&#xff0c;下载样式 2.导入项目&#xff08;字体样式&#xff09; 3.引入css样式 4.替换font-face 5.使用图标&#xff08;字体图标&#xff0c;只有黑色&#xff09; 导入彩色图标 1.安装插件 npm install -g iconfont-tools2.…

修复系统和修复常见安卓问题的 10 个应用

我们都喜欢我们的 Android 智能手机&#xff0c;对吧&#xff1f;有很多值得喜欢的地方。 Android 手机易于使用且通常无故障&#xff0c;但毕竟它只是一台机器&#xff0c;偶尔也会出现问题。面对现实吧&#xff0c;我们的智能手机并不完美。用户经常遇到的一些常见 Android …

使用scipy处理图片——滤镜处理

大纲 black_tophatwhite_tophatconvolvecorrelategaussian_filtergaussian_laplacemaximum_filtermedian_filterminimum_filterpercentile_filterprewittrank_filtersobelspline_filteruniform_filter基础代码代码仓库 在《使用numpy处理图片——模糊处理》一文中&#xff0c;我…

【python】py-spy 实时显示python进程内的线程堆栈CPU消耗 python CPU消耗分析

安装 pip install py-spy AI调用源码&#xff0c;红色调用时&#xff0c;python进程CPU 100% 启动程序&#xff0c;输入问题&#xff0c;观察CPU top sudo .local/bin/py-spy top --pid 7150 可以看到&#xff0c;此时与显卡交互占用了绝大部分CPU&#xff0c;有点死循环检测…

HNU-模式识别-作业1-视频监控系统

模式识别-作业1 计科210X 甘晴void 202108010XXX 【评分&#xff1a;98/100】 题目&#xff1a; 查阅相关技术资料&#xff0c;根据自己家庭相应的情况&#xff0c;设计一个视频监控系统。要求&#xff1a; 系统功能说明系统布线图及说明系统软硬件配置说明 饱和式自家用…