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

2024-01-21

???? 博客主页: 小王又困了

???? 系列专栏: 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):

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

栈(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.1 new/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。申请和释放单个元素的空间,使用 new delete 操作符,申请和释放连续的空间,使用 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 会调用析构函数,而 malloc free 不会。如果自定义类型没有默认构造函数,我们在 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函数

new delete 是用户进行 动态内存申请和释放的操作符 operator new operator 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 new operator delete 不是运算符重载。 operator new operator delete malloc free 在功能上相似,都是到堆上申请空间,然后释放空间,它们的区别在于, 前者失败了会抛异常,而后者失败了是返回错误码 ,通过返回值来表示这一块有问题。

四、new和delete的实现原理

????4.1内置类型

如果申请的是内置类型的空间, new malloc delete free 基本类似,不同的地方是: new 在申请空间失败时会抛异常, malloc 会返回NULL。 new delete 申请和释放的是单个元素的空间, 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的区别

????共同点:

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

????不同点:

  • malloc free 函数 new delete 操作符
  • 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. 事后查错型,如泄漏检测工具。

???? 结语:

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

『C++成长记』内存管理 的相关文章

随机推荐

  • 如何用结构体替代数组实现学生信息的录入与比较

    这里是一个有关学生学号 成绩信息的录入 输出成绩最高的学生信息 供参考学习 include
  • 如何把题库做成答题小程序?

    随着移动互联网的普及 越来越多的人开始使用手机进行学习 而微信小程序作为一种轻量级的应用 受到了越来越多人的青睐 那么 如何把题库做成答题小程序呢 下面就来详细介绍一下 一 搭建题库小程序 首先准备好我们的营业执照 然后选择一个合适的搭建工
  • 2024年华数杯国际赛A题:放射性废水处理建模 思路模型代码解析

    2024年华数杯国际赛A题 放射性废水处理建模 Radioactive Wastewater from Japan 一 问题描述 2011年3月 日本东海岸发生了地震 引发了福岛第一核电站事故 导致三个核反应堆熔毁 并在一场巨大海啸中冲毁了
  • 38条Web测试经验分享

    1 页面链接检查 每一个链接是否都有对应的页面 并且页面之间切换正确 可以使用一些工具 如LinkBotPro File AIDCS HTML Link Validater Xenu等工具 LinkBotPro不支持中文 中文字符显示为乱码
  • JUC的常见类

    目录 Callable ReentrantLock Semaphore CountDownLatch JUC 即 java util concurrent 其中存放了一些进行多线程编程时有用的类 Callable Callable是一个接口
  • INETRES.dll文件丢失导致程序无法运行问题

    其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题 如果是新手第一时间会认为是软件或游戏出错了 其实并不是这样 其主要原因就是你电脑系统的该dll文件丢失了或没有安装一些系统软件平台所需要的动态链接库 这时你可以下载这个INETRES
  • 线程安全的集合类

    Java中提供了许多集合类 其中有的是线程安全的 有的是线程不安全的 线程安全的集合类有 1 Vector Vector类实现了一个 动态数组 与ArrayList相似 但Vector是同步访问的 2 Stack Stack是Vector的
  • Selenium小技巧!

    Chrome DevTools 简介 Chrome DevTools 是一组直接内置在基于 Chromium 的浏览器 如 Chrome Opera 和 Microsoft Edge 中的工具 用于帮助开发人员调试和研究网站 借助 Chro
  • ir50_qc.dll文件丢失导致程序无法运行问题

    其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题 如果是新手第一时间会认为是软件或游戏出错了 其实并不是这样 其主要原因就是你电脑系统的该dll文件丢失了或没有安装一些系统软件平台所需要的动态链接库 这时你可以下载这个ir50 qc
  • SRC漏洞挖掘经验+技巧篇

    一 漏洞挖掘的前期 信息收集 虽然是前期 但是却是我认为最重要的一部分 很多人挖洞的时候说不知道如何入手 其实挖洞就是信息收集 常规owasp top 10 逻辑漏洞 重要的可能就是思路猥琐一点 这些漏洞的测试方法本身不是特别复杂 一般混迹
  • InkEd.dll文件丢失导致程序无法运行问题

    其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题 如果是新手第一时间会认为是软件或游戏出错了 其实并不是这样 其主要原因就是你电脑系统的该dll文件丢失了或没有安装一些系统软件平台所需要的动态链接库 这时你可以下载这个InkEd d
  • 【信道估计】【MIMO】【FBMC】未来移动通信的滤波器组多载波调制方案(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码及文章
  • 课表排课小程序怎么制作?多少钱?

    在当今的数字化时代 无论是购物 支付 点餐 还是工作 学习 都离不开各种各样的微信小程序 其中 课表排课小程序就是许多教育机构和学校必不可少的工具 那么课表排课小程序怎么制作呢 又需要多少钱呢 制作课表排课小程序并不是一件复杂的事情 只需要
  • IPBusEnumProxy.dll文件丢失导致程序无法运行问题

    其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题 如果是新手第一时间会认为是软件或游戏出错了 其实并不是这样 其主要原因就是你电脑系统的该dll文件丢失了或没有安装一些系统软件平台所需要的动态链接库 这时你可以下载这个IPBusEn
  • 微信怎么申请小程序商城?一步步教你完成

    随着移动互联网的发展 越来越多的企业和个人开始关注微信小程序 微信小程序是一种不需要下载安装即可使用的应用 它实现了应用 触手可及 的梦想 用户扫一扫或者搜一下就能打开应用 其中 小程序商城因其便捷性和实用性 受到了众多商家的青睐 那么 如
  • ir41_qc.dll文件丢失导致程序无法运行问题

    其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题 如果是新手第一时间会认为是软件或游戏出错了 其实并不是这样 其主要原因就是你电脑系统的该dll文件丢失了或没有安装一些系统软件平台所需要的动态链接库 这时你可以下载这个ir41 qc
  • inetcomm.dll文件丢失导致程序无法运行问题

    其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题 如果是新手第一时间会认为是软件或游戏出错了 其实并不是这样 其主要原因就是你电脑系统的该dll文件丢失了或没有安装一些系统软件平台所需要的动态链接库 这时你可以下载这个inetcom
  • iPhone应用商店即将开放,最晚3月7日前!

    iPhone的 侧载 功能马上就要来了 据彭博社马克 古尔曼 Mark Gurman 在最新一期的 Power On 中报道 为了应对欧盟即将实施的 数字市场法案 DMA 苹果正准备在未来几周内将应用商店 一分为二 在欧洲区域开放应用侧载功
  • 关于整型提升与截断的一道题目

    关于整型提升与截断 可以看我的博客 C语言 整型提升 c语言整形提升 CSDN博客 C语言 截断 整型提升 算数转换练习 c语言unsigned CSDN博客 一 题目 二 题解 char a 101截断 由于101是整型数据 需要32比特
  • 『C++成长记』内存管理

    博客主页 小王又困了 系列专栏 C 人之为学 不日近则日退 感谢大家点赞 收藏 评论 目录 一 C C 内存分布 二 内存管理方式 2 1C语言内存管理方式 2 2C 内存管理方式 2 2 1new delete操作内置类型 2 2 1ne