时间:2022-11-19 10:51:10 | 栏目:C代码 | 点击:次
在学习C语言阶段的时候,创建一个变量,编译器会为它分配一块内存。而创建一个C++对象的时候,编译器会为这个对象分配内存,并且调用合适的构造函数进行初始化。
那么编译器的内存分配方式是怎样的呢?
内存分配可以有以下的几种方式
有时候我们并不知道程序中的对象确切地需要多少内存空间,动态内存分配则很好地处理了这种需求。
C库中提供了函数malloc,以及它的变种函数realloc、calloc来动态地申请内存空间。使用函数free来释放动态申请出的内存空间。
int* ptr1 = (int*)malloc(sizeof(int));
使用malloc需要指定空间大小,并且要强制类型转化,因为它只是简单地分配了一块空间,返回的是void*,而C++中不允许将空类型的指针赋予给其他类型的指针。另外,如果你申请一块内存之后,没有对这个指针进行正确的初始化,有可能会导致程序运行失败,并且如果忘记释放动态申请的内存空间,则会造成内存泄露等危害……
在创建一个C++对象时,编译器会做这两件事:
1.为对象分配内存。
2.编译器自动调用构造函数初始化该内存。
构造函数不支持显式地调用,意味着如果使用malloc函数创建一个对象,那么这个对象将不能够调用构造函数,仅仅只是开辟了一块空间。但是我们必须要确保对象被初始化,因为未初始化对象是大部分程序出错的主要原因。总而言之,C中的动态内存管理无法满足C++中动态对象的需求。
所以提出了new和delete.关键字
int main(void) { //基本内置类型 //开辟一个int类型的空间 int* ptr2 = new int; //开辟多个int类型空间 int* ptr3 = new int[5]; //开辟一个int类型并初始化为1 int* ptr4 = new int(1); delete ptr2; delete[] ptr3; delete ptr4; return 0; }
new和delete的使用方式:
1.开辟一个空间: new 类型; 对应释放: delete 对象;2.开辟多个空间:new 类型[个数] 对应释放:delete[] 对象;3.开辟并初始化: new 类型(初始化数据) 对应释放:delete 对象 1.开辟一个空间: new 类型; 对应释放: delete 对象; 2.开辟多个空间:new 类型[个数] 对应释放:delete[] 对象; 3.开辟并初始化: new 类型(初始化数据) 对应释放:delete 对象
内置类型
对于内置类型,new和delete与C的内存管理函数做了差不多的事情,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。
自定义类型
new表达式:
delete表达式:
operator new( ) 和operator delete( )这两个内存分配函数是系统提供的全局函数,实际上是对malloc和free的各种行为进行了封装。
new、delete 和 malloc、free的区别有哪些呢?
C++允许重载new和delete,以实现我们自己的存储分配方案。但是注意重载operator new和operator delete时,仅仅只能改变原本的内存分配方式。同重载其他的运算符一样,可以分为重载成全局和针对特定类的内存分配函数。
重载全局
重载一个全局的new和delete会导致默认版本完全不能被访问。
重载operator new的要求:
重载operator delete的要求
为什么重载operator delete的时候,参数是一个void*?
这是因为它是在调用析构函数后得到的指针。
//重载operator new //1.必须有一个size_t的参数,该参数将接收要申请开辟空间的大小 //2.返回值是一个void* //3.返回一个指向对象的指针,该对象的长度等于或大于所申请的长度 //4.如果分配失败。不仅仅要返回一个0,还需产生一个异常信息 void* operator new(size_t sz) { cout << "new %d Bytes" << sz << endl; void* p = nullptr; //不需要强制类型转换,因为malloc返回的就是void* p = malloc(sz); if (nullptr == p) { cout << "new fail\n" << endl; } return p; } //重载operator delete //1.返回值为void //2.参数是一个指向由operator new()返回的void*的指针 void operator delete(void* rp) { cout << "operator delete" << endl; free(rp); }
重载类专属
//重载ListNode专属的operator new struct ListNode { ListNode* _next; ListNode* _prev; int _data; void* operator new(size_t n) { void* p = nullptr; p = allocator<ListNode>().allocate(1); 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; } ~List() { ListNode* cur = _head->_next; while (cur != _head) { ListNode* next = cur->_next; delete cur; cur = next; } delete _head; _head = nullptr; } private: ListNode* _head; }; int main() { List l; return 0; }
定位new表达式:它的作用是在已分配的原始内存空间中用构造函数初始化一个对象
使用的格式:
new (place_address) type 或者 new (place_address) type (initializer-lost) place_address
必须是一个指针,initializer-list
是初始化列表。
//例如 class Test { public: Test() : _data(0) { cout << "Test():" << this << endl; } ~Test() { cout << "~Test():" << this << endl; } private: int _data; }; int main(void) { // pt现在指向的只不过是与Test对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行 Test* pt = (Test*)malloc(sizeof(Test)); //定位new new(pt) Test; // 注意:如果Test类的构造函数有参数时,此处需要传参 return 0; }
内存泄露的含义:
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄露的两大分类:
1.堆内存泄露(Heap leak)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
2.系统资源泄露
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
内存泄露的危害:
在平时写一些小测试的时候,并没有觉得内存泄露的危害特别大,但是在长期运行的程序中出现内存泄漏,影响非常的大,出现内存泄露可能会导致响应越来越慢,最终出现卡死的现象。
内存泄露的解决方案分两种:
1.事先预防 。
2. 事后查错
如何事先预防?
1.养成良好的编码习惯,申请了内存要记得释放。
2.采用RAII思想或者智能指针来管理资源。
3.规范使用内部实现的私有内存管理库。