operator new与operator delete函数
一、operator new与operator delete函数 —— 重点
💦 operator new与operator delete函数
new 和 delete 是用户进行动态内存申请和释放的操作符,operator new 和 operator delete 严格来说不是 new 和 delete 的重载 (名字确实容易误导),而是系统提供的全局库函数,new 在底层调用 operator new 全局函数来申请空间,delete 在底层通过 operator delete 全局函数来释放空间。
new A:
- 申请内存 —— 调用 operator new
- 构造函数
delete A:
- 析构函数
- 释放内存 —— 调用 operator delete
new 的反汇编 ❗
❓ 为什么要去调用 operator new 而不是调用其它的呢 ❔
int main()
{
//malloc失败返回空
char* p1 = (char*)malloc(0xffffffff);
if (p1 == NULL)
{
printf("malloc fail\n");
}
else
{
printf("malloc success:%p\n",p1);
}
//new失败抛异常
char* p2 = new char[0x7fffffff];
if (p1 == NULL)
{
printf("malloc fail\n");
}
else
{
printf("malloc success:%p\n", p1);
}
return 0;
}
📝说明
因为 new 和 malloc 它们失败时,处理的方式不一样
malloc 失败了,这里的检查起作用了
new 失败了,这里的检查没起作用,还引发了一个崩溃 —— 抛异常后没有解决
抛异常 ❓
int main()
{
try
{
char* p2 = new char[0x7fffffff];//出错,抛异常,它会跳到捕获异常的位置
}
catch(const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
📝说明
这里就可以看到 new 和 malloc 处理的方式不一样,毕竟一个是面向过程,一个是面向对象
关于什么是异常后面我们会具体学习
所以再看 new 的底层的实现,new 的底层申请内存时是不能让 malloc 去完成的,因为 malloc 失败就直接返回空了,就无法达到让它失败后抛异常的机制,所以其中就产生了 operator new
对于 delete 就不存在失败了抛异常,我们说 malloc 会失败,但没有说 free 会失败 (free 的失败是对越界的空间 free 等),free 失败就不是说抛异常或返回空这样的概念了,这种是属于比较严重的错误,它是直接中止掉程序
operator new 和 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 来申请空间,如果 malloc 申请空间
成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异
常。operator delete 最终是通过 free 来释放空间的。
operator new 就是对 malloc 的封装,目的就是如果申请内存失败了抛异常:
- new = 封装 malloc + 失败抛异常 + 调用构造函数
operator delete 就是对 free 的封装,目的主要还是和 operator new 对应起来
- delete = 调用析构函数 + operator delete
这里具体后面也会学习
直接使用 operator new 和 operator delete ❓
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
//调用构造和析构
A* p1 = new A;
delete p1;
//不会调用构造和析构
A*p2 = (A*)operator new(sizeof(A));
operator delete(p2);
return 0;
}
📝说明
我们直接使用 operator new 和 operator delete 本质上和 malloc 和 free 没有区别
所以平时我们也几乎不会用 operator new 和 operator delete
💦 operator new与operator delete的类专属重载
检测程序有没有 ListNode 的节点有没有释放,如果存在没有释放的节点,请说明是哪里没有释放 ❓
🔑移除链表元素
//重载一个类ListNode,专属的operator new:
struct ListNode
{
int val;
struct ListNode* next;
static int _count;//统计
ListNode(int x)
: val(x)
, next(nullptr)
{}
//new _count就++,delete _count就--
void* operator new(size_t n)
{
++_count;
return ::operator new(n);//::代表是全局的
}
void operator delete(void* p)
{
--_count;
return ::operator delete(p);//::代表是全局的
}
};
int ListNode::_count = 0;
struct ListNode* removeElements(struct ListNode* head, int val)
{
struct ListNode* prev = NULL, *cur = head;
while(cur)
{
if(cur->val == val)
{
prev->next = cur->next;
delete cur;
cur = prev->next;
}
else
{
prev = cur;
cur = prev->next;
}
}
return head;
}
int main()
{
ListNode* n1 = new ListNode(1);
ListNode* n2 = new ListNode(2);
ListNode* n3 = new ListNode(2);
ListNode* n4 = new ListNode(3);
ListNode* n5 = new ListNode(4);
ListNode* n6 = new ListNode(2);
n1->next = n2;
n2->next = n3;
n3->next = n4;
n4->next = n5;
n5->next = n6;
n6->next = nullptr;
ListNode* list = removeElements(n1, 2);
cout << "没有释放的节点数量:" << ListNode::_count << endl;
return 0;
}
📝说明
当 new ListNode 时,那么申请空间就会调用专属的 operator new 和 operator delete
了解一下即可,这个语法实际中价值不大
这里想看具体哪里没有释放那就比较复杂了,后面学了 map 后就可以行号给记录下来
二、new和delete的实现原理
💦 内置类型
如果申请的是内置类型的空间,new 和 malloc,delete 和 free 基本类似,不同的地方是:new/delete 申请和释放的是单个元素的空间,new[] 和 delete[] 申请的是连续空间,而且 new 在申请空间失败时会抛异常,malloc 会返回 NULL。
💦 自定义类型
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 来释放空间
场景使用 ❓
class Stack
{
public:
Stack(int capacity = 4)
: _a(new int[capacity])
, _size(0)
, _capacity(capacity)
{
cout << "Stack(int capacity = 4)" << endl;
}
~Stack()
{
delete[] _a;
_size = _capacity = 0;
cout << "~Stack()" << endl;
}
private:
int* _a;
int _size;
int _capacity;
};
int main()
{
//1
Stack st;
//2
Stack* ps = new Stack;
delete ps;
return 0;
}
📝说明:
六、定位new表达式(placement-new) —— 了解
定位 new 表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
- new(place_address) type 或者 new(place_address) type(initializer-list)
- place_address 必须是一个指针,initializer-list 是类型的初始化列表
如果想对 malloc 开辟的已有的一块空间去调用构造函数 ❓
struct ListNode
{
int val;
struct ListNode* next;
ListNode(int x)
: val(x)
, next(nullptr)
{
cout << "ListNode(int x)" << endl;
}
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
//实例化一个对象构造函数、析构函数自动调用
ListNode node(1);
//new调用构造函数、delete调用析构函数
ListNode* p = new ListNode(2);
delete p;
//显示调用构造函数、析构函数
ListNode* n1 = (ListNode*)malloc(sizeof(ListNode));
new(n1)ListNode(3);
n1->~ListNode();
free(n1);
return 0;
}
📝说明
使用场景:
定位 new 表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义
类型的对象,需要使用 new 的定义表达式进行显示调构造函数进行初始化。
这里举一个好理解的 —— 复制一份 a数组到另一块空间 pb ❗
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A()" << this << endl;
}
A(const A& a)
{
cout << "A(const A& a)" << this << endl;
}
A& operator=(const A& a)
{
cout << "A& operator=(const A& a)" << this << endl;
return *this;
}
~A()
{
cout << "~A()" << this << endl;
}
private:
int _a;
};
int main()
{
//构造+赋值
A a[5];
A* pb = new A[5];
for(int i = 0; i < 5; i++)
{
pb[i] = a[i];
}
delete []pb;
//拷贝构造
A* pd = (A*)malloc(sizeof(A) * 5);
for(int i = 0; i < 5; i++)
{
new(pd + i)A(a[i]);
}
for(int i = 0; i < 5; i++)
{
(pd+i)->~A();
}
return 0;
}
📝说明
由此可见,使用定位 new 表达式的效率更高。
- 点赞
- 收藏
- 关注作者
评论(0)