从C语言到C++⑨(第三章_C&C++内存管理)详解new和delete+面试题笔试题(上):https://developer.aliyun.com/article/1513661
3. operator new与operator delete函数详解
3.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) { // 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