目录
2.operator new与operator delete(了解)
2.1 operator new函数和operator delete函数
2.2 operator new与operator delete的类专属重载(了解)
1. 对上一节的补充
1.代码分析:
#include <iostream>
using namespace std;
class Stack
{
public:
Stack(int capacity=4)
:_top(0)
, _capacity(capacity)
{
_a = new int[capacity];
}
~Stack()
{
_top=_capacity= 0;
delete[]_a;
_a = nullptr;
}
private:
int* _a;
int _capacity;
int _top;
};
int main()
{
Stack st1;//这种是存在栈上的,随着函数结束而结束。
//想要自己控制就要动态申请
Stack* pt1 = new Stack;//new 是开辟空间+调用构造函数。
delete pt1;//先释放去调用析构函数清理资源,再去释放空间。
return 0;
}
new的执行过程:
delete执行过程:
总结:
1.new是先申请空间再调用析构函数,delete是先调用析构函数清理资源再释放空间。
2.new delete和malloc free 在内置类型上没有区别,只是用法上的区别。在自定义类型上,new会申请空间并去调用构造函数去初始化,delete先去调用析构函数清理资源再去释放空间。malloc只是申请空间不做初始化,free只是释放空间不做资源清理。
3.在处理错误的方式上不同,new失败会直接抛异常(assert 差不多,直接弹窗),而malloc是返回NULL。
2.operator new与operator delete(了解)
2.1 operator new函数和operator delete函数
注意:这两个函数不是对new 和delete的函数重载
2.1.1 operator new函数
1.operator new函数源码:(了解)
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);
}
2.那operator会调用构造函数吗?
#include <iostream>
using namespace std;
class Stack
{
public:
Stack(int capacity=4)
:_top(0)
, _capacity(capacity)
{
_a = new int[capacity];
}
~Stack()
{
_top=_capacity= 0;
delete[]_a;
_a = nullptr;
}
private:
int* _a;
int _capacity;
int _top;
};
int main()
{
Stack* ptr1 = (Stack*)operator new(sizeof(Stack));
return 0;
}
结果:
3.那operator new函数和malloc函数没有区别吗?
#include <iostream>
using namespace std;
int main()
{
//在堆上开辟2G的大小,程序会出错的。
char* ptr1 = (char*)malloc(0x7fffffff);//malloc是直接返回NULL
char* ptr2 = (char*)operator new(0x7fffffff);//直接抛异常
//operator new 和malloc的区别是一个是返回NULL,一个是直接抛异常,若是不捕获异常会终止程序
return 0;
}
结果:
malloc直接返回nullptr
operator new 直接抛出异常,没有捕获异常(catch)会发生程序中断:
总结:通过看operator new的源码,为了符合C++的抛异常,operator new是对malloc的封装,有了抛异常。
2.1.2 operator delete函数
1.operator delete函数源码:(了解)
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 delete也是对free封装,为了与operator new形成配对,这个函数最终是调用free函数释放空间的。与free函数区别不大。
2.1.3 总结
其实operaor new 和operator delete函数不是让我们自己去调用的,它是在执行new和delete时自己调用的。
new Stack 是等价于 operator new + call 构造函数,而不是malloc + call 构造函数,malloc不抛异常。因为C++处理异常是直接抛异常。而operator new是对malloc的封装+抛异常处理。
delete Stack是等价于operator delete +call 析构函数。operator delete 是对free的封装
#include <iostream>
using namespace std;
class Stack
{
public:
Stack(int capacity=4)
:_top(0)
, _capacity(capacity)
{
_a = new int[capacity];
}
~Stack()
{
_top=_capacity= 0;
delete[]_a;
_a = nullptr;
}
private:
int* _a;
int _capacity;
int _top;
};
int main()
{
Stack*ptr1 = new Stack;
char* ptr2 = new char[0x7fffffff];//直接抛异常
return 0;
}
Stack*ptr1 = new Stack; 申请完空间+调用构造函数。
char* ptr2 = new char[0x7fffffff]; 直接抛异常
对抛出的异常进行捕获:
int main()
{
try
{
char* ptr2 = new char[0x7fffffff];//直接抛异常
}
catch (const exception&e)
{
cout << e.what() << endl;
}
return 0;
}
2.2 operator new与operator delete的类专属重载(了解)
在写链表时,每一个新节点都会去找操作系统去申请空间,但是操作系统又很忙,每次都去麻烦操作系统就会效率很低,因此有了类专属重载。使用内存池申请内存,提高效率。池化技术。提前申请好空间,不用每次都去找操作系统。
#include <iostream>
using namespace std;
struct ListNode
{
ListNode* _next;
ListNode* _prev;
int _data;
void* operator new(size_t n)
{
void* p = nullptr;
p = allocator<ListNode>().allocate(1);//内存池-STL 空间配置器
cout << "memory pool allocate" << endl;
return p;
}
void operator delete(void* p)
{
allocator<ListNode>().deallocate((ListNode*)p, 1);
cout << "memory pool deallocate" << endl;
}
};
class List
{
public:
List()
{
_head = new ListNode;
_head->_next = _head;
_head->_prev = _head;
}
void PushBack(int val)
{
ListNode* newnode = new ListNode;//去调用struct ListNode里的 void* operator new(size_t n) ,正常是调用全局的operator new
newnode->_data = val;
ListNode*tail = _head->_prev;
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
}
~List()
{
ListNode* cur = _head->_next;
while (cur != _head)
{
ListNode* next = cur->_next;
delete cur;//去调用struct ListNode void operator delete(void* p)函数
cur = next;
}
delete _head;//去调用struct ListNode void operator delete(void* p)函数
_head = nullptr;
}
private:
ListNode* _head;
};
int main()
{
List l;
l.PushBack(1);
l.PushBack(2);
return 0;
}
3.new和delete实现的原理
1.内置类型
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是new/delete申请和释放是单个元素的空间,new[] 和delete[]申请的是连续空间,而且new在申请失败时会抛异常,malloc会返回NULL。
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来释放空间。
注意:operator new[ ]函数和operator delete[ ] 底层分别封装的operator new 和operator delete 性质是一样的。应该是为了与new T[N]和delete[ ]形成配对才出现的operator new[ ]函数和operator delete[ ]。
#include <iostream>
using namespace std;
class Stack
{
public:
Stack(int capacity=4)
:_top(0)
, _capacity(capacity)
{
_a = new int[capacity];
}
~Stack()
{
_top=_capacity= 0;
delete[]_a;
_a = nullptr;
}
private:
int* _a;
int _capacity;
int _top;
};
int main()
{
Stack* p1 = new Stack[10];//它会去调用构造函数初始化
Stack* p2 = (Stack*)operator new[](sizeof(Stack)* 10);//它不会去调用构造函数
system("pause");
return 0;
}
4.定位new表达式(placement-new)(了解)
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果
是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
#include <iostream>
using namespace std;
class Test
{
public:
Test(int a=0)
: _data(a)
{
cout << "Test():" << this << endl;
}
~Test()
{
cout << "~Test():" << this << endl;
}
private:
int _data;
};
int main()
{
// pt现在指向的只不过是与Test对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
Test* p1 = (Test*)malloc(sizeof(Test));//没有调用构造函数。
//构造函数是不支持显式调用的,一般都是new完后直接自己调动
new(p1)Test;//我们可以这样手动调用构造函数。不传参数
new(p1)Test(10);//可以传参
//Test* p2 = new Test; new等价于
Test* p2 = (Test*)operator new(sizeof(Test));
new(p2)Test;
//delete p2;等价于
p2->~Test();//析构函数可以显示调用
operator delete(p2);
return 0;
}
5.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在释放空间前会调用析构函数完成空间中资源清理
6.内存泄漏
什么内存泄漏?
动态申请的内存,不使用了,又没有主动释放,就存在内存泄漏。
官方:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
代码分析:
#include <iostream>
using namespace std;
int main()
{
char* p1 = new char[1024 * 1024 * 1024];//1G
//这段代码已经是内存泄漏了。
return 0;
}
上面的程序已经造成了内存泄漏,但是没有出现卡死,这是为什么呢?
因为内存泄漏的进程正常结束,进程结束时这些内存会还给系统,不会有什么大伤害。
那什么时候会造成伤害呢?
出现内存泄漏的进程非正常结束,比如僵尸进程。或者守护进程。通俗来说就是长期运行的程序,系统会越来越慢,甚至卡死宕机。比如:服务器程序
下面代码存在异常安全问题:
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;
}
内存泄漏的分类:
C/C++程序中一般我们关心两种方面的内存泄漏:
1.堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内
存, 用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有
被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
2.系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致
系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
如何一次在堆上申请4G的内存?
在32位下:
#include <iostream>
using namespace std;
int main()
{
char* p1 = new char[0x7fffffff];//2G
printf("%p\n",p1);
system("pause");
return 0;
}
会出错:
在64位下: