从C语言到C++⑨(第三章_C&C++内存管理)详解new和delete+面试题笔试题(中)

简介: 从C语言到C++⑨(第三章_C&C++内存管理)详解new和delete+面试题笔试题

从C语言到C++⑨(第三章_C&C++内存管理)详解new和delete+面试题笔试题(上):https://developer.aliyun.com/article/1513661

3. operator newoperator delete函数详解

3.1 operator newoperator 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)
{
  // 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 来申请空间的。

② operator delete 最终也是通过 free 来释放空间的。

如果 malloc 申请空间成功就直接返回,否则执行用户提供的空间不足的应对措施,

如果用户提供该措施就继续申请,否则就抛异常。

面向过程的语言处理错误的方式:

返回值 + 错误码解决(这个之前博客讲过):

#include <stdio.h>
#include <stdlib.h>
 
int main()
{
  char* p1 = (char*)malloc(1024u * 1024u * 1024u * 2u);
  if (p1 == nullptr) 
  {
    printf("%d\n", errno);
    perror("malloc fail");
    exit(-1);
  }
  else 
  {
    printf("%p\n", p1);
  }
 
  return 0;
}

而面向对象语言处理错误的方式:

一般是抛异常,C++中也要求出错抛异常 —— try catch(后面会细讲)。

#include <iostream>
using namespace std;
 
int main()
{
  char* p2 = nullptr;
  try
  {
    char* p2 = new char[1024u * 1024u * 1024u * 2u - 1];
  }
  catch (const exception& e) 
  {
    cout << e.what() << endl;
  }
  printf("%p\n", p2);
 
  return 0;
}

C++ 提出 new 和 delete,主要是解决两个问题:

① 自定义类型对象自动申请的时候,初始化合清理的问题。

    new / delete 会调用构造函数和析构函数。

② new 失败了以后要求抛异常,这样才符合面向语言的出错处理机制。

(delete 和 free 一般不会失败,如果失败了,就是释放空间上存在越界或者释放指针位置不对)


3.2  重载operator new 与 operator delete(了解)

默认情况下operator new 与 operator delete使用全局库里面

如果我们自己重载operator new 与 operator delete了,

那么编译器就会调我们自己重载的,而不会调原来的:

#include<iostream>
using namespace std;
 
class A
{
public:
  A(int a = 0)
    : _a(a)
  {
    cout << "A():" << this << endl;
  }
 
  ~A()
  {
    cout << "~A():" << this << endl;
  }
 
private:
  int _a;
};
 
// 重载operator delete,在申请空间时:打印在哪个文件、哪个函数、第多少行,申请了多少个字节
void* operator new(size_t size, const char* fileName, const char* funcName, size_t lineNo)
{
  void* p = ::operator new(size);
  cout << "new:" << fileName << "||" << funcName << "||" << lineNo << "||" << p << "||" << size << endl;
  return p;
}
 
 重载operator delete,在释放空间时:打印再那个文件、哪个函数、第多少行释放,不实现这个,不然使用要加()
//void operator delete(void* p, const char* fileName, const char* funcName, size_t lineNo)
//{
//  cout << "delete:" << fileName << "||" << funcName << "||" << lineNo << "||" << p << endl;
//  ::operator delete(p);
//}
 
// 重载operator delete
void operator delete(void* p)
{
  cout << "delete:" << endl;
  free(p);
}
 
#ifdef _DEBUG
     #define new new(__FILE__, __FUNCTION__, __LINE__)
     // #define delete(p) operator delete(p, __FILE__, __FUNCTION__, __LINE__) 不实现这个宏,不然使用要加()
#endif
 
int main()
{
  A* p1 = new A;
  delete p1;
 
  A* p2 = new A[4];
  delete[] p2;
 
  A* p3 = new A;
  delete p3;
 
  A* p4 = new A;
  delete p4;
 
  A* p5 = new A;
  delete p5;
 
    return 0;
}

这里为了好看就不把文件名打印出来了:


类内重载operator new 与 operator delete:

我们知道:new -> operator new + 构造函数,默认情况下operator new使用全局库里面

每个类可以去实现自己专属operator new  new这个类对象,它就会先调自己实现这个operator new

上面我们提到:C语言内存管理方式在有些地方无能为力,而且使用起来比较麻烦

下面代码演示了,针对链表的节点 ListNode 通过重载类专属 operator new / operator delete,

实现链表节点使用内存池申请和释放内存,提高效率:

// new -> operator new + 构造函数
// 默认情况下operator new使用全局库里面
// 每个类可以去实现自己专属operator new  new这个类对象,他就会调自己实现这个operator new
 
// 实现一个类专属的operator new  -- 了解一下
 
#include<iostream>
using namespace std;
 
class A
{
public:
  A(int a = 0)
    : _a(a)
  {
    cout << "A():" << this << endl;
  }
 
  ~A()
  {
    cout << "~A():" << this << endl;
  }
 
private:
  int _a;
};
 
struct ListNode
{
  int _val;
  ListNode* _next;
 
  static allocator<ListNode> alloc;// 内存池
 
  void* operator new(size_t n)
  {
    cout << "operator new -> STL内存池allocator申请" << endl;
    void* obj = alloc.allocate(1);
    return obj;
  }
 
  void operator delete(void* ptr)
  {
    cout << "operator delete -> STL内存池allocator申请" << endl;
 
    alloc.deallocate((ListNode*)ptr, 1);
  }
 
  struct ListNode(int val)
    :_val(val)
    , _next(nullptr)
  {}
};
 
// allocator以后会讲,现在先了解即可
allocator<ListNode> ListNode::alloc;
 
int main()
{
  // 频繁申请ListNode. 想提高效率 -- 申请ListNode时,不去malloc,而是自己定制内存池
  ListNode* node1 = new ListNode(1);
  ListNode* node2 = new ListNode(2);
  ListNode* node3 = new ListNode(3);
 
  delete node1;
  delete node2;
  delete node3;
 
  A* p1 = new A;
 
  return 0;
}


4. new 和 delete 的实现原理

4.1 对于内置类型

如果申请的是内置类型的空间,new 和 malloc,delete 和 free 基本相似。

不同的地方是,new / delete 申请和释放的是单个元素的空间,

new[ ] 和 delete[ ] 申请的是连续空间。

而且 new 再申请空间失败时会抛异常,malloc会返回NULL。

operator new 和 operator delete 就是对 malloc 和 free 的封装。

operator new 中调用 malloc 后申请内存,失败以后,改为抛异常处理错误,

这样符合C++面向对象语言处理错误的方式。

4.2 对于自定义类型

new 的原理:

① 调用 operator new 函数申请空间。

② 在申请空间上执行构造函数,完成对象的构造。

delete 的原理:

① 在空间上执行析构函数,完成对象中资源的清理工作。

② 调用 operator delete 函数释放对象的空间。


new T[N] 的原理:

① 调用 operator new[] 函数,在 operator new[] 中实际调用

operator new 函数完成 N 个对象空间的申请。

② 在申请的空间上调用 N 次构造函数,对它们分别初始化。

delete[] 的原理:

① 在释放的对象空间上执行 N 次析构函数,完成 N 个对象中资源的清理。

② 调用 operator delete[] 释放空间,实际在 operator delete[] 中调用

operator delete 来释放空间。

从C语言到C++⑨(第三章_C&C++内存管理)详解new和delete+面试题笔试题(下):https://developer.aliyun.com/article/1513663?spm=a2c6h.13148508.setting.23.5e0d4f0eimuY68

目录
相关文章
|
8天前
|
存储 C语言
C语言学习记录——动态内存函数介绍(malloc、free、calloc、realloc)
C语言学习记录——动态内存函数介绍(malloc、free、calloc、realloc)
16 1
|
8天前
|
编译器 C语言 C++
C语言学习记录——位段(内存分配、位段的跨平台、位段的应用)
C语言学习记录——位段(内存分配、位段的跨平台、位段的应用)
11 0
|
2天前
|
存储 算法 编译器
C++面试题其一
C++文件编译与执行的四个阶段 预处理:处理#include、#define等预处理指令。 编译:将源码翻译为目标代码。 汇编:将目标代码转换为机器指令。 链接:将目标文件和库文件合并生成可执行文件。 STL中的vector的实现,是怎么扩容的? vector通过动态数组实现,当容量不足时,分配更大的内存(通常是原来的两倍),复制旧数据到新内存,并释放旧内存。
17 2
|
2天前
|
存储 程序员 编译器
C++面试题其二
extern "C" 用于告诉编译器按照C语言的链接方式处理代码,通常用于C++代码与C代码混合编程,以防止因名字修饰(name mangling)引起的链接错误。例如: extern "C" { void c_function(); } 通过这些问题的深入理解和解答,能够更好地掌握C++编程的核心概念和实际应用,为面试做好充分的准备。
13 1
|
2天前
|
安全 算法 C++
C++面试题其三
继续上篇博客的解答,我们将进一步探讨C++中的一些关键概念和常见面试问题。
6 0
|
8天前
|
程序员 C语言 C++
C语言学习记录——动态内存习题(经典的笔试题)、C/C++中程序内存区域划分
C语言学习记录——动态内存习题(经典的笔试题)、C/C++中程序内存区域划分
9 0
|
8天前
|
C语言
C语言学习记录——动态内存开辟常见的错误
C语言学习记录——动态内存开辟常见的错误
9 1
|
8天前
|
存储 C语言
C语言学习记录——通讯录(动态内存)
C语言学习记录——通讯录(动态内存)
7 0
|
8天前
|
C语言
C语言学习记录——通讯录(静态内存)
C语言学习记录——通讯录(静态内存)
13 2
|
8天前
|
编译器 Linux C语言
C语言学习记录——结构体(声明、初始化、自引用、内存对齐、结构体设计、修改默认对齐数、结构体传参)二
C语言学习记录——结构体(声明、初始化、自引用、内存对齐、结构体设计、修改默认对齐数、结构体传参)二
13 1