【C++】C&C++内存管理(下)

简介: 【C++】C&C++内存管理(下)

4.operator new和operator delete函数


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


new的底层机制

  1. operator new --> malloc
  2. 调用构造函数
class A
{
public:
  A(int* a = nullptr)
    :_a(a)
  {
    cout << "A()" << this << endl;
  }
  ~A()
  {
    cout << "~A()" << this << endl;
  }
private:
  int* _a;
};
void Test()
{
  A* p = new A;
}

b854ea9d07bbd4f34fec09096c4843df.png

通过查看汇编代码,可以看到new的这个操作做了:1. 调用operator new 2. 调用自定义类型的构造函数


注意:operator new看起来像是运算符重载,但是,实际上operator new不是运算符重载,因为在该函数参数中没有自定义类型

//operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
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失败之后的返回NULL封装成抛出异常,执行用户提供的应对措施。operator delete最终也是通过free来释放空间的。


5.new和delete的实现原理


1.内置类型

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


2.自定义类型

1. new的原理

1. 调用operator new函数在内存中申请空间
1. 在申请的空间上执行该类型的构造


2. delete的原理

1. 在空间上执行该类型的析构函数
1. 调用operator delete函数释放空间


3. new T[N]的原理

1. 调用operator new[]函数,operator new[]函数实际上就是封装了operator new函数,执行N次operator new
1. 在申请的空间上执行N次该类型的构造函数


4. delete[]的原理

1. 在申请的空间上执行N次析构函数
1. 调用operator delete[]函数,operator delete[]实际上就是封装了operator delete函数,执行N次operator delete[]


6.定位new表达式(placement-new)


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

使用格式

new(place_address)type或者new(palce_address)type(initializer-list)

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

使用场景

class A
{
public:
  A(int a = 5)
    :_a(a)
  {
    cout << "A()" << this << endl;
  }
  ~A()
  {
    cout << "~A()" << this << endl;
  }
private:
  int _a;
};
void Test_replacement_new()
{
  //new (place_address) type
  A* p1 = (A*)malloc(sizeof(A));
  if (p1 == nullptr)
  {
    perror("malloc fail");
    exit(-1);
  }
  new(p1)A();
  p1->~A();
  free(p1);
  //new (place_address) type(initializer-list)
  A* p2 = (A*)operator new(sizeof(A));
  new(p1)A(10);
  p2->~A();
  free(p2);
}

注:不管是在执行了malloc还是执行operator new之后,p1p2都只是一段大小于A对象大小相同的空间,不是一个对象,因为构造函数还没有执行


7.常见的面试题


1.malloc/free和new/delete的比较

从用法功能和底层两个方面去考虑

  • 共同点:

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

  • 不同点


  1. malloc/free是函数,new/delete是操作符
  2. malloc申请的空间不会初始化,new申请的空间会调用构造函数初始化
  3. malloc申请空间需要手动传要开辟的空间的字节数大小,new申请空间只需要传类型个数
  4. malloc返回值类型是void*,需要进行强转之后使用,new不需要
  5. malloc申请失败返回NULL,用的时候需要进行判空操作,new申请失败抛异常,需要捕获异常
  6. malloc和free只会开辟空间,不会对空间进行操作,new和delete在完成malloc和free功能的基础上,会自动调用构造函数和析构函数。
  7. new的底层也是封装了malloc实现的,delete底层也是封装了free实现的


2.内存泄漏

1. 什么是内存泄漏,内存泄漏的危害

什么是内存泄漏:内存泄漏是指因为某些原因导致的程序没有释放不再使用的内存。内存泄漏不是物理上的消失,而是值操作系统将内存分配给程序之后,失去了对该部分内存的控制,导致操作系统找不到该内存,从而造成的内存浪费

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


2. 内存泄漏分类

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

  • 堆内存泄漏(Heap leak)

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

  • 系统资源泄漏

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


3. 如何检测内存泄漏

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

371d67b1a92f5ef60f21b3e0ac5b528a.png

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


  • 在linux下内存泄漏检测 :Linux下几款C++程序中的内存泄露检查工具
  • 在windows下使用第三方工具: VLD工具说明
  • 其他工具 :内存泄露检测工具比较


4. 如何避免内存泄漏

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

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

  1. 事前预防型。如智能指针等
  2. 事后查错型。如泄漏检测工具
相关文章
|
8天前
|
存储 编译器 C语言
C++中的内存管理
C++中的内存管理
22 0
|
1天前
|
存储 C语言 C++
【C++】C&C++内存管理
【C++】C&C++内存管理
|
1天前
|
安全 C++ 容器
C++ 动态内存
C++ 动态内存
7 0
|
2天前
|
编译器 程序员 C语言
从C语言到C++⑨(第三章_C&C++内存管理)详解new和delete+面试题笔试题(下)
从C语言到C++⑨(第三章_C&C++内存管理)详解new和delete+面试题笔试题
5 0
|
2天前
|
编译器 C语言 C++
从C语言到C++⑨(第三章_C&C++内存管理)详解new和delete+面试题笔试题(中)
从C语言到C++⑨(第三章_C&C++内存管理)详解new和delete+面试题笔试题
9 0
|
2天前
|
存储 程序员 编译器
从C语言到C++⑨(第三章_C&C++内存管理)详解new和delete+面试题笔试题(上)
从C语言到C++⑨(第三章_C&C++内存管理)详解new和delete+面试题笔试题
7 0
|
6天前
|
存储 编译器 Linux
|
8天前
|
存储 Linux C语言
【C++从练气到飞升】07---内存管理
【C++从练气到飞升】07---内存管理
|
8天前
|
存储 编译器 C++
【C++】内存管理和模板基础(new、delete、类及函数模板)
【C++】内存管理和模板基础(new、delete、类及函数模板)
27 1