C++内存管理基础

news2024/12/22 23:06:17

文章目录

  • 前言
    • 1. C/C++内存分布
    • 2. C语言中动态内存管理方式
    • 3. C++中动态内存管理
      • 3.1 new/delete操作内置类型
      • 3.2 new和delete操作自定义类型
    • 4. operator new与operator delete函数
      • 4.1 operator new与operator delete函数(重点)
    • 5. new和delete的实现原理
      • 5.1 内置类型
      • 5.2 自定义类型
    • 6. 定位new表达式(placement-new)(了解即可)
    • 7. 常见面试题
      • 7.1 malloc/free和new/delete的区别
      • 7.2 内存泄露
        • 7.2.1 什么是内存泄漏,内存泄漏的危害
        • 7.2.2 内存泄漏分类(了解即可)
        • 7.2.3 如何检测内存泄漏(了解即可)
        • 7.2.4如何避免内存泄漏
  • 后记

前言

本文将讲述C++内存管理的相关知识。

🕺作者: 迷茫的启明星
专栏:《C++初阶》

😘欢迎关注:👍点赞🙌收藏✍️留言

🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的很重要,有问题可在评论区提出,感谢阅读!!!

持续更新中~

1. C/C++内存分布

我们由问题来引出话题。

首先,先来看一下这段代码:

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
    static int staticVar = 1;
    int localVar = 1;
    int num1[10] = { 1, 2, 3, 4 };
    char char2[] = "abcd";
    const char* pChar3 = "abcd";
    int* ptr1 = (int*)malloc(sizeof(int) * 4);
    int* ptr2 = (int*)calloc(4, sizeof(int));
    int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
    free(ptr1);
    free(ptr3);
}

请回答下列问题:

  1. 选择题:
    选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
    globalVar在哪里?____ staticGlobalVar在哪里?____
    staticVar在哪里?____ localVar在哪里?____
    num1 在哪里?____
    char2在哪里?____ *char2在哪里?___
    pChar3在哪里?_____ *pChar3在哪里?____
    ptr1在哪里?____ *ptr1在哪里?____

  2. 填空题:

    sizeof(num1) = ;

    sizeof(char2) = ; strlen(char2) = ;

    sizeof(pChar3) = ; strlen(pChar3) = ;

    sizeof(ptr1) = ;

  3. sizeof 和 strlen 区别?

是不是对你来说有那么点挑战,有几个还不确定?

我讲述一下易错的几个点:

  1. 选择题

    下面字符串的判断容易搞混,char2和*char2是字符数组,是在栈上的。

    pCharp是指向常量区的一个指针,指向“abcd”,

    解引用的*pChar也就是“abcd”就在常量区

    ptr1是指针哦,注意分辨,它申请的空间是在堆上面的

所以*ptr1就是在堆上

  1. 填空题

    数组num1开辟了十个空间哦,注意看它中括号里面的值,如果一个数组定义时,中括号里面没有值,就只看后面花括号内的值。

    字符串大小判断需要记得还有一个‘\0’隐藏起来了,使用sizeof时‘\0’也会算在结果中的,而strlen则不管‘\0’,只算’\0’前面的字符。

    还要注意的是,求大小的是“指针”还是“字符串or数组”?

    是指针则根据环境的不同,它的大小是4 or 8

以下就是答案了

  1. 选择题:
    选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
    globalVar在哪里?C staticGlobalVar在哪里?C
    staticVar在哪里?C__ localVar在哪里?A
    num1 在哪里?A__
    char2在哪里?A__ *char2在哪里?A___
    pChar3在哪里?A_ *pChar3在哪里?D
    ptr1在哪里?A__ *ptr1在哪里?B

  2. 填空题:

    sizeof(num1) = 40;

    sizeof(char2) = 5 ; strlen(char2) = 4 ;

    sizeof(pChar3) = 4 or 8; strlen(pChar3) = 4 ;

    sizeof(ptr1) = 4 or 8 ;

  3. sizeof 和 strlen 区别?

    sizeofstrlen 是两个在 C/C++ 中常用的关键字和函数,它们有着不同的功能和用途。

    sizeof 是一个运算符,用于求取数据类型或变量所占用的字节数。它可以用于任何数据类型,包括内置类型、结构体、联合体等。与 sizeof 相关的表达式的结果是一个无符号整数,表示该类型或变量所占用的字节数。例如:

    int a;
    size_t size = sizeof(int);
    

    上述代码中,第一行定义了一个整型变量 a,第二行使用 sizeof 运算符求取 int 类型所占用的字节数,并将其赋值给一个无符号整数变量 size

    strlen 是一个函数,用于求取一个字符串的长度,即其中非空字符的个数。它只能用于字符串类型,即以空字符(\0)结尾的字符数组。当 strlen 函数遇到空字符时,它就会停止统计字符数量,返回字符串的长度。例如:

    char str[] = "hello world";
    size_t len = strlen(str);
    

    上述代码中,第一行初始化了一个字符数组,其中包含了一个以空字符结尾的字符串;第二行使用 strlen 函数求取了这个字符串的长度,并将其赋值给一个无符号整数变量 len

图示:

在这里插入图片描述

说明:

  1. 栈又叫堆栈–非静态局部变量/函数参数/返回值等等,栈是向下增长的。
  2. 内存映射段是高效的I/O映射方式,用于装载一个动态内存库。用户可使用系统接口创建共享内存,做进程间通信。(之后会在linux中讲的)
  3. 堆用于程序运行时动态内存分配,堆是可以向上增长的。
  4. 数据段–存储全局数据和静态数据。
  5. 代码段–可执行的代码/只读常量

小结:这一个知识点,我们配合题目,解析,图示很容易理解的,记住就好。

2. C语言中动态内存管理方式

在学习 C++内存管理方式前,我们先回顾一下C语言中动态内存管理方式,它们有:malloc/calloc/realloc/free

看下面代码:
思考注释中的问题

void Test ()
{
    int* p1 = (int*) malloc(sizeof(int));
    free(p1);
    
    // 1.malloc/calloc/realloc的区别是什么?
    int* p2 = (int*)calloc(4, sizeof (int));
    int* p3 = (int*)realloc(p2, sizeof(int)*10);
    
    // 这里需要free(p2)吗?
    free(p3 );
}

我相信只要你学过c语言都不会对它们陌生的,我就来帮你总结一下它们。

1.malloc/calloc/realloc的区别是什么?

malloccallocrealloc 都是动态分配内存的函数。

  • malloc 函数用于申请指定大小的内存块,返回该内存块首地址的指针。它只负责分配内存,并不会对分配的内存进行初始化,所以分配的内存中可能包含垃圾数据。
  • calloc 函数在申请内存的同时会对内存进行清零,可以防止出现未初始化的垃圾值。它的参数为所需要的元素个数每个元素占用字节的大小,返回值是该内存块首地址的指针。
  • realloc 函数用于重新分配已有内存的大小。它接受两个参数:**第一个参数是原先分配的内存块首地址,第二个参数是新分配的内存大小。**如果新分配的大小小于旧大小,则在新内存末尾处丢弃一部分内存;如果新分配的大小大于旧大小,则在新内存末尾处添加一些内存。如果分配成功,则返回该内存块首地址的指针,否则返回 NULL。
  1. 这里需要free(p2)吗?

    在 Test 函数中,p2 是通过 calloc 函数申请并初始化了一个大小为 4 个 int 类型元素的内存块。然后使用 realloc 函数将 p2 指向的内存块扩展至 10 个 int 类型元素大小,并返回了新的内存块地址,同时释放了原先 p2 指向的内存块。这意味着我们不需要再次释放 p2 所指向的内存块,因为它已经被 realloc 函数转移并释放了。最后,我们释放了 p3 所指向的内存块,以避免内存泄漏。

需要注意的是,使用动态内存分配函数时,一定要注意内存的申请和释放的对应关系,否则就容易出现内存泄漏或野指针的问题。

拓展:

  1. malloc的实现原理?

    malloc 是 C 标准库中的一个动态内存分配函数,它的原理是从堆(Heap)区域中申请一块指定大小的内存,并返回一个指向该内存块首地址的指针。下面是 malloc 这个函数大致的实现原理:

    1. 首先,malloc 函数会根据用户请求的内存大小,计算出需要申请的总内存量。在这个过程中,可能会添加一些额外的字节来记录内存块的大小和其他信息。这样做的目的是为了在释放内存时能够知道需要释放多少内存和维护当前内存块的状态。

    2. 接下来,malloc 函数会调用底层的系统函数(如 sbrk 或 mmap),向操作系统申请一块连续的虚拟内存空间,这块空间将作为内存池,用于存储用户请求的内存块。

    3. 为了记录内存块的大小、是否被占用等信息,malloc 函数会将每一个内存块都分成两部分:头部(Header)和数据区域。头部通常包含以下信息:

      • 前后内存块的大小
      • 当前内存块的大小
      • 内存块是否占用
    4. 当有新的内存请求时,malloc 函数会按照一定的算法在内存池中寻找一块足够大的未被占用的内存块,并返回该内存块首地址的指针。

    5. 如果没有足够大的内存块可供使用,malloc 函数将会调用系统函数扩展内存池的大小,以便存储更多的内存块。对于类 Unix 系统,sbrk 函数可以从操作系统请求内存池的空闲空间;而对于现代操作系统,mmap 函数则常常用于扩展内存池。

    总结:malloc 函数的主要原理是从操作系统中申请一块虚拟内存的空间,并将其作为内存池,接着使用成本较低的数据结构(如链表)管理内存池中的内存块,以满足用户动态内存分配的需求。

3. C++中动态内存管理

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

3.1 new/delete操作内置类型

看下面代码:

注释中就是new/delete的使用方法

void Test()
{
     // 动态申请一个int类型的空间
     int* ptr4 = new int;
    
     // 动态申请一个int类型的空间并初始化为10
     int* ptr5 = new int(10);
    
     // 动态申请10个int类型的空间
     int* ptr6 = new int[3];
    
     delete ptr4;
     delete ptr5;
     delete[] ptr6;
}

图示:

在这里插入图片描述

它和前文讲C语言的malloc/free有区别吗?

  • 针对内置类型,new/delete跟malloc/free没有本质的区别,只有用法的区别
  • new/delete 用法简化了

注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],注意:匹配起来使用。

3.2 new和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/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数
    A* p1 = (A*)malloc(sizeof(A));//C
    //1、堆上申请空间  2、调用构造函数初始化
    A* p2 = new A(1);
    free(p1);//C
    // 1、调用析构函数清理对象中资源 2、释放空间
    delete p2;
    
    // 内置类型是几乎是一样的
    int* p3 = (int*)malloc(sizeof(int)); // C
    int* p4 = new int;
    free(p3);//C
    delete p4;
    
    A* p5 = (A*)malloc(sizeof(A)*10);//C
    A* p6 = new A[10];
    free(p5);
    delete[] p6;
    
    //在C++11中还有一种初始化的方式,先了解即可,之后会再讲的
   	A* p7 = new A[2]{1,2};
	A* p8 = new A[2]{ A(1), A(2) };
    delete[] p7;
    delete[] p8;
    return 0;
}
//结论:new/delete 是为自定义类型准备的。
// 不仅在堆中申请出来,还会调用构造和析构初始化和清理

注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会

4. operator new与operator delete函数

4.1 operator new与operator delete函数(重点)

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

operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
    // 尝试分配size大小字节
    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);
}

这是 C++ STL 库中定义的单个对象分配操作符 new 的默认实现。在 C++ 中,当我们需要分配一块内存来存储一个对象时,可以使用这个 new 运算符来完成分配内存的工作。

具体来说,这段代码的主要功能是从堆上申请一块指定大小的内存,并在内存不足时抛出 bad_alloc 异常(该异常类型是 C++ 标准库内置的一种异常类型,表示内存申请失败)。其具体流程如下:

  1. 首先,该函数会调用 malloc 函数,尝试在堆上申请一块大小为 size 字节的内存块。

  2. 如果 malloc 函数返回的指针为 nullptr,说明堆内存不足,此时进入 while 循环。

  3. 在循环内部,该函数会调用 _callnewh 函数,尝试调用用户自定义的 new_handler 函数处理内存不足的情况。new_handler 是一个函数指针类型,它指向一个用户自定义的函数,当堆内存不足时,C++ 运行时系统就会自动调用该函数进行内存管理。如果 new_handler 函数可以成功分配内存,则函数将退出循环并返回新分配的内存地址;否则,程序将继续等待内存的释放或调用其他 new_handler

  4. 如果 new_handler 函数返回失败(即返回值为 nullptr),说明堆内存不足,此时函数会抛出 bad_alloc 异常,通知调用方内存分配失败。

  5. 如果 malloc 函数成功地分配了指定大小的内存块,则该函数将返回新分配的内存块首地址的指针。

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

void operator delete(void *pUserData)
{
    _CrtMemBlockHeader * pHead;
    RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
    if (pUserData == NULL)
        return;
    _mlock(_HEAP_LOCK);  /* 释放其他线程 */
    __TRY
        /* 获取指向内存块首地址的指针 */
        pHead = pHdr(pUserData);
    /* 验证块类型 */
    _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
    _free_dbg( pUserData, pHead->nBlockUse );
    __FINALLY
        _munlock(_HEAP_LOCK);  /* 释放其他线程 */
    __END_TRY_FINALLY
    return;
}

这是 C++ 标准库中定义的单个对象释放操作符 delete 的默认实现。在 C++ 中,当我们完成使用一个动态分配的对象后,需要使用 delete 运算符来释放分配的内存空间。

该函数的主要功能是根据参数 pUserData 指针释放内存,并进行必要的内存结构清理和管理。其具体流程如下:

  1. 首先,该函数调用 _CrtMemBlockHeader 结构体指针 pHead,获取指向内存块头部的指针。

  2. 接下来,该函数会调用 _free_dbg 函数,释放指针 pUserData 所指向的内存块。_free_dbg 函数与标准的 free 函数类似,都是用于释放动态内存的函数,但它还会检查内存泄漏、越界访问等情况,并定位出错位置,对调试程序有很大的帮助。

  3. 在释放内存块的同时,该函数还会验证该内存块的类型是否合法,以防止误操作或非法指针释放的出现。

  4. 最后,该函数使用 _mlock_munlock 函数,对堆进行线程同步管理,保证多线程环境下的内存空间分配和释放的正确性和线程安全性。

free 的实现

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

这是 C++ 标准库中的一个宏定义,用于释放动态分配的内存。在 C++ 中,我们可以使用 free 函数来释放通过 malloccalloc 分配的内存在堆上的内存空间。

该宏定义的主要作用是将 free 函数的调用转换为 _free_dbg 函数的调用,并将第二个参数设置为 _NORMAL_BLOCK,以指定使用正常内存块的方式进行内存释放操作。其中 _free_dbg 函数已经在前一个问题中有所提及,它可以定位内存泄漏、越界访问等问题,并对程序进行调试和错误排查。

这样做的好处是,在程序调试时能够更有效地检测到内存错误和不当操作,帮助程序员更快地定位内存问题,提高程序的健壮性和可靠性。同时,由于 free 宏本质上是对 _free_dbg 函数的封装,因此没有额外的性能开销,可以保证程序运行效率的同时,提高程序的调试和维护的便利性。

5. new和delete的实现原理

5.1 内置类型

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

5.2 自定义类型

  • new的原理
  1. 调用operator new函数申请空间
  2. 在申请的空间上执行构造函数,完成对象的构造
  • delete的原理
  1. 在空间上执行析构函数,完成对象中资源的清理工作
  2. 调用operator delete函数释放对象的空间
  • new T[N]的原理
  1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
  2. 在申请的空间上执行N次构造函数
  • delete[]的原理
  1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
  2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

6. 定位new表达式(placement-new)(了解即可)

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new定义表达式进行显示调构造函数进行初始化

class A
{
    public:
    A(int a = 0)
    : _a(a)
    {
    	cout << "A():" << this << endl;
    }
    ~A()
    {
    	cout << "~A():" << this << endl;
    }
    private:
    int _a;
};
// 定位new/replacement 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;
}

解释:

这段代码主要演示了如何通过定位 new 和定位 delete 来分别实现动态分配内存和释放内存的过程。

在 C++ 中,我们可以使用 new 运算符来动态地分配内存,并在分配内存的同时直接构造对象。而使用 delete 运算符可以释放动态分配的内存,并在释放内存的同时直接调用析构函数进行对象的上下文清理。通常情况下,在使用 new 时会自动调用构造函数,使用 delete 时会自动调用析构函数。

但是在一些特殊场景下,我们可能需要手动控制对象的构造和析构过程,以保证程序的正确性和稳定性。这时,就需要使用定位 new 和定位 delete 来完成内存空间的申请和释放过程。

在这段代码中,首先使用 malloc 函数分配了一个大小为 sizeof(A) 的内存块,并将其转换成了类型为 A* 的指针 p1。注意,此时 p1 指向的还只是分配的内存空间,并没有调用构造函数初始化对象。

接着,使用定位 new 运算符 new(p1)A ,在 p1 指向的空间上直接创建了一个 A 类对象。此时,由于没有传入参数,默认调用了构造函数 A::A(int a = 0) ,输出了一条信息表示对象创建成功。

接下来,使用定位 delete 运算符 p1->~A() ,在 p1 指向的空间上直接调用了 A 类对象的析构函数。注意,此时并没有立即释放内存空间,只是对对象进行了上下文清理工作。

最后,通过调用 free(p1) 函数,释放了动态分配的内存空间,完成了整个过程。

接下来,又使用了 operator new 运算符动态分配内存,但与 malloc 函数不同,operator new 更加灵活,并且可以自动管理内存池,提高了程序性能。在这里,它同样被用来分配一个大小为 sizeof(A) 的内存块,并将其转换成类型为 A* 的指针 p2

接着,同样使用定位 new 运算符 new(p2)A(10) ,在 p2 指向的空间上直接创建了一个 A 类对象,并传入了参数 10。此时,构造函数 A::A(int a = 0) 将会带着参数 10 进行调用,输出一条信息表示对象创建成功。

接下来,使用定位 delete 运算符 p2->~A() ,在 p2 指向的空间上直接调用了 A 类对象的析构函数。

最后,通过调用 operator delete(p2) 函数,释放了动态分配的内存空间,并自动回收内存池,完成了整个过程。

7. 常见面试题

7.1 malloc/free和new/delete的区别

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

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

不同的地方是:

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

7.2 内存泄露

7.2.1 什么是内存泄漏,内存泄漏的危害

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

void MemoryLeaks()
{
     // 1.内存申请了忘记释放
     int* p1 = (int*)malloc(sizeof(int));
     int* p2 = new int;
     // 2.异常安全问题
     int* p3 = new int[10];
     Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
     delete[] p3;
}

解释:

首先,在第 4 行和第 5 行,分别使用了 malloc 函数和 new 运算符动态地分配了一个 int 类型的内存块,并将其转换成类型为 int* 的指针 p1p2。但是,在程序执行结束时,没有调用对应的释放函数或运算符,导致了内存泄漏的问题。这样的内存泄漏不仅会占用系统资源,还会导致程序性能下降,严重情况下可能会导致系统崩溃等问题。因此,需要在程序执行结束时,显式调用 free 函数和 delete 运算符,释放动态分配的内存空间,避免内存泄漏的问题。

接着,在第 8 行到第 10 行,使用了 new 运算符动态地分配了一个长度为 10 的 int 类型数组,并将其转换成类型为 int* 的指针 p3。但是,在第 11 行调用了一个函数 Func(),该函数抛出了异常,导致后续的代码未被执行,包括第 12 行的释放 p3 数组的操作。这样就会导致程序存在异常安全问题,即当程序抛出异常时,可能会造成内存泄漏或其他非预期的后果。为了解决这个问题,可以使用 RAII(资源获取即初始化)技术,通过定义局部对象来管理资源,在函数结束时自动调用析构函数,从而确保资源的正确释放。

7.2.2 内存泄漏分类(了解即可)

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

  • 堆内存泄漏(Heap leak)

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

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

7.2.3 如何检测内存泄漏(了解即可)

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

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

// 程序退出后,在输出窗口中可以检测到泄漏了多少字节,但是没有具体的位置
Detected memory leaks!
Dumping objects ->
{79} normal block at 0x00EC5FB8, 40 bytes long.
Data: <         > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

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

在linux下内存泄漏检测:linux下几款内存泄漏检测工具
在windows下使用第三方工具:VLD工具说明
其他工具:内存泄漏工具比较

7.2.4如何避免内存泄漏

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

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

1、事前预防型。如智能指针等。

2、事后查错型。如泄漏检测工具。

后记

我已经快一个没有更新了,前段时间是期中考试四门专业课有点忙,今后继续保持记录和输出,我还会开一个刷题专栏,感谢大家支持!!!

在这里插入图片描述
respect!

下篇见!

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

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

相关文章

hana odata batch

sap 博客有写 odata batch 处理前&#xff0c;先看一张图 In this blog post,we are going to see how to send a Odata Batch Request to the SAP Cloud for Customer system using POSTMAN Tool. Answers to expect from this post? How to use batch request in the POS…

『python爬虫』04. 爬虫需要知道的HTTP协议知识(保姆级图文)

目录 1. HTTP协议是什么&#xff1f;2. HTTP协议结构3. 爬⾍需要的请求头和响应头内容总结 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 1. HTTP协议是什么&#xff1f; HTTP协议, Hyper Text Transfer Protocol…

2023独立站能不能做FP?看完这篇你就懂了

现在已经快2023年中了&#xff0c;2023年已经过去了1/3&#xff0c;但还是有人在问特货产品能不能做独立站&#xff0c;还是有不少人在观望。心动不如行动啊朋友们&#xff01;要是想在跨境独立站做出一番事业来&#xff0c;建议现在立马行动起来&#xff0c;趁早在FP独立站领域…

工厂能耗管理系统linux嵌入式边缘网关

随着工业智能化进程的不断推进&#xff0c;能源能耗管理已成为企业经营中一个重要的环节。而在能源能耗管理场景下&#xff0c;边缘计算机发挥了越来越重要的角色。本文将介绍边缘计算机的功能特点、能源能耗使用对接的设备以及应用前景市场容量&#xff0c;并探讨ARM边缘计算机…

Java使用 Scanner连续输入int, String 异常错误输出原因分析

目录 一、Scanner常用语法 1、sc.nextInt()介绍 2、sc.next()介绍 3、sc.nextLine()介绍 4、sc.hasNext()介绍 二、报错案例 1、使用next()来接收带有空格的字符串会输出异常 2、先输入数字再输入字符串的输出异常 一、Scanner常用语法 Scanner sc new Scanner(System.…

STM32物联网实战开发(2)——回调函数

在第一篇博客中提到了全新的程序框架&#xff0c;我们会大量的使用回调函数&#xff0c;其中包括枚举类型、结构体、函数指针的应用。 回调函数&#xff1a;就是一个通过函数指针调用的函数。如果你把函数的地址传递给中间函数的形参&#xff0c;中间函数通过函数指针调用其所…

【VM服务管家】VM4.0软件使用_1.3全局模块类

目录 1.3.1 通讯管理&#xff1a;通讯管理的心跳管理功能的使用方法1.3.2 全局触发&#xff1a;使用全局触发功能执行流程的方法1.3.3 全局变量&#xff1a;全局变量关联流程中具体模块结果的方法1.3.4 全局脚本&#xff1a;方案加载完成信号发给通信设备的方法1.3.5 全局脚本&…

我做了个GPT3键盘,用了两个月发现它有点傻

自 ChatGPT 出世&#xff0c;各类文本类AI产品层出不穷。甚至接连几日&#xff0c;Producthunt 上新品过半都是AI相关。 这其中部分原因是 OpenAI 公司开放的 GPT3 1API 接口十分易用。只要一个简单的文本请求&#xff0c;就能将现有产品加入AI功能。例如&#xff0c;Notion、…

Docker在Windows系统中的安装方法和使用方法

Docker在Windows系统中的安装方法和使用方法 Docker是一种容器化技术&#xff0c;可以让开发者将应用程序和其依赖项打包成一个可移植的容器&#xff0c;从而实现快速部署和运行。在Windows系统中&#xff0c;Docker可以通过以下步骤进行安装和使用。 优点&#xff1a; Dock…

【VM服务管家】VM4.x算子SDK开发_3.3 模块工具类

目录 3.3.1 位置修正&#xff1a;位置修正算子工具的使用方法3.3.2 模板保存&#xff1a;实现模板自动加载的方法3.3.3 模板匹配&#xff1a; 获取模板匹配框和轮廓点的方法3.3.4 模板训练&#xff1a;模板训练执行完成的判断方法3.3.5 图像相减&#xff1a;算子SDK开发图像相减…

浅谈软件质量与度量

本文从研发角度探讨下高质量软件应具备哪些特点&#xff0c;以及如何度量软件质量。 软件质量的分类 软件质量通常可以分为&#xff1a;内部质量和外部质量。 内部质量 内部质量是指软件的结构和代码质量&#xff0c;以及其是否适合维护、扩展和重构。它关注的是软件本身的…

数据结构 | 常见的数据结构是怎样的?

本文简单总结数据结构的概念及常见的数据结构种类 1’ 2。 更新&#xff1a;2023 / 04 / 05 数据结构 | 常见的数据结构是怎样的&#xff1f; 总览概念分类 常用的数据结构数组链表跳表栈队列树二叉树完全二叉树、满二叉树 平衡二叉树单旋转左旋右旋 红黑树红黑树 V.S 平衡二叉…

2 天:我用文字 AI-ChatGPT 写了绘画 AI-Stable Diffusion 跨平台绘画应用

文本 AI - ChatGPT 和绘画 AI - Stable Diffusion&#xff0c;平地惊雷&#xff0c;突然进入寻常百姓家。 如果时间可以快进&#xff0c;未来的人们对于我们这段时光的历史评价&#xff0c;大概会说&#xff1a; 当时的人们在短时间连续经历了这几种情感。从不信&#xff0c;…

java多线程BlockingDeque的三种线程安全正确退出方法

本文介绍两种BlockingDeque在多线程任务处理时正确结束的方法 一般最开始简单的多线程处理任务过程 把总任务放入BlockingDeque创建多个线程&#xff0c;每个线程内逻辑时&#xff0c;判断BlockingDeque任务是否处理完&#xff0c;处理完退出&#xff0c;还有任务就BlockingDe…

对顶堆模板!!【DS对顶堆】ABC281 E - Least Elements

我想的思路和正解是差不多的 就是滑动窗口&#xff0c;每过去一个用DS维护一下前k个元素和sum 本来想的是用优先队列维护前k个 然后想着multiset维护前k个&#xff0c;但是具体不知道怎么操作 这里用的是multiset维护对顶堆 关于对顶堆&#xff0c;我在寒假的时候总结过 …

【Java笔试强训】(1)

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f9be;&#x1f9be;&#x1f9be; 目录 一、选择题 二、编程题 &#x1f525;组队竞…

Github创建一个新仓库,关联本地数据并上传文件的图文步骤

工作中&#xff0c;我们经常会使用github来承享别人的代码果实&#xff0c;同时我们也会把自己的成果分享给别人&#xff0c;互相帮助。 今天的这篇图文教程非常重要&#xff0c;目标是使用Github来创建一个远程仓库&#xff0c;并和本地仓库对接&#xff0c;同时要做上传新内容…

初始Vue3【Vue3】

1.Vue3简介 2020年9月18日&#xff0c;Vue.js发布3.0版本&#xff0c;代号&#xff1a;One Piece&#xff08;海贼王&#xff09;耗时2年多、2600次提交、30个RFC、600次PR、99位贡献者github上的tags地址&#xff1a;https://github.com/vuejs/vue-next/releases/tag/v3.0.0 …

使用docker容器化部署mysql8.0.27,并更改其默认端口3306为3306全流程记录。

使用docker容器化部署mysql8.0.27,并更改其默认端口3306为3306全流程记录。 1.创建镜像 #查看镜像 docker images|grep mysql #搜索镜像 docker search mysql #拉取镜像 docker pull mysql&#xff1a;latest #运行镜像&#xff0c;--name 后的参数自己命名&#xff0c;我的数…

js逆向之rpc远程调用(你强任你强,我无视一切)

一、找到加密函数位置 二、在其下面注入ws服务 &#xff08;1)注入准备 资源>>替换>>随便选一个空文件夹 &#xff08;2&#xff09;进行注入 进行&#xff08;1&#xff09;操作后可直接编辑js代码了&#xff0c;做以下修改 (function() {var ws new WebSocket(…