C++图文并茂分析讲解内存管理
作者:龟龟不断向前
1.了解一些基本的内存段(图演示)
验证栈是向下生长的
#include<iostream> using namespace std; int main() { int a = 3; int b = 4; int c = 5; int d = 6; cout <<"a:"<< &a << endl; cout << "b:"<<&b << endl; cout << "c:"<<&c << endl; cout << "d:"<<&d << endl; return 0; }
验证堆一般是向上生长的(不一定)
#include<iostream> using namespace std; int main() { int num = 10; while (num--) { int *p1 = (int*)malloc(sizeof(int)); int *p2 = (int*)malloc(sizeof(int)); cout <<"p1"<< p1 << endl; cout <<"p2"<<p2 << endl; cout << endl; free(p1); } return 0; }
一般情况下,p1
的地址是比p2
的地址高的(因为堆一般是向上生长的),但是有时候是不一定的。
巩固内存管理知识点
答案
温馨提示:题目中的指针是局部指针变量,是在栈上的,但是它指向的内容(解引用)可能是堆区或者常量区的。,可以画画图理解理解
2.c++申请动态内存的新玩儿法new,delete
回顾c语言动态内存管理的方式
malloc
和calloc
和realloc
malloc
堆上动态开空间calloc
堆上动态开空间+初始化成0等价于malloc
+memset
realloc
指针已有的空间扩容
原题增容–后面又足够的空间
异地增容–后面没有足够的空间
开辟内置类型的空间
//C++开辟动态内存的新玩法 //语法演示: #include<iostream> using namespace std; int main() { //申请一个int的动态空间 int* p1 = (int*)malloc(sizeof(int)); *p1 = 1; int* p2 = new int(2);//这里是初始化 free(p1); delete p2; //申请一个10各int的动态数组 int *p3 = (int*)malloc(sizeof(int)* 10); for (int i = 0; i < 10; i++) { p3[i] = i + 1; } int *p4 = new int[10]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};//初始化 free(p3); delete[]p4; return 0; }
跟c语言的版本相比,c++的版本,初始化时显得比较方便,而且语法比较简洁。当然了,更大的优越性还在后面。
开辟自定义类型的空间(请用vs2013以上版本测试代码)
#include<iostream> using namespace std; struct ListNode { int _val; ListNode* _next; ListNode* _prev; //构造函数 ListNode(int val = 0) :_val(val) , _next(nullptr) , _prev(nullptr) { cout<<"ListNode(int val = 0)"<<endl; } ~ListNode() { cout << "ListNode()" << endl; } }; int main() { //申请一个结点的空间 ListNode* pa = (ListNode*)malloc(sizeof(ListNode)*4); ListNode* pb = new ListNode(1);//不用去用sizeof去计算空间大小,很方便 free(pa); delete pb; //申请4个结点的空间--当然了一般不会这么玩儿,我们只是看看效果 ListNode* pc = (ListNode*)malloc(sizeof(ListNode)* 4); ListNode* pd = new ListNode[4]{1, 2, 3, 4}; free(pc); delete[]pd; return 0; }
学过c语言版本的数据结构的小伙伴都知道,我们push_back一个结点时,需要先实现一个buynewnode
的函数(创建一个结点,并且对其进行初始化)。而new这个操作符,在创建结点的同时,已经帮我们实现了结点的初始化。调用了构造函数,而且delete
还调用了析构函数。
总结一下
- c++中,如果是申请内置类型对象或者数组,
malloc
和new
没有太大区别 - 如果时自定义类型,区别很大,new和delete时开空间+初始化,析构清理+释放空间,
malloc
和free
仅仅时开空间+释放空间 - 建议在c++中,无论时内置类型还是自定义类型的申请释放,尽量使用new和delete。
3. 32位平台下可以开辟多大的内存
我:cpu过来遭罪
cpu:你不要过来啊
上述程序,我们开了1.8 G左右的内存,加上需要堆上的小内存,最后的综合有2 G的空间
如何开辟4G的内存
将项目属性修改一下,改成64位平台即可,64位平台有2^34 G的空间(虚拟内存)在这我们不做细说,因为我也不太了解Linux。
4.了解operator new和operator delete
new
其实是operator new
+ 构造函数
delete
其实是operator delete
+构造函数
new
和delete
是用户进行动态内存申请和释放的操作符,operator new
和operator delete
是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间
大家可以将operator new和operator delete理解成和malloc
和free一样用法的函数。
唯一不同的地方是,operator new和malloc
开辟空间失败的处理方式不一样,malloc
是返回NULL空指针,而operator是抛异常,下面代码让大家看看效果,不必深究,以后会有介绍。
struct ListNode { int _val; ListNode* _next; ListNode* _prev; //构造函数 ListNode(int val = 0) :_val(val) , _next(nullptr) , _prev(nullptr) { cout << "ListNode(int val = 0)" << endl; } ~ListNode() { cout << "ListNode()" << endl; } }; void f() { // 他的用法跟malloc和free是完全一样的,功能都是在堆上申请释放空间 // 失败了处理方式不一样,malloc失败返回NULL,operator new失败以后抛异常 ListNode* p1 = (ListNode*)malloc(sizeof(ListNode)); free(p1); ListNode* p2 = (ListNode*)operator new(sizeof(ListNode)); operator delete(p2); void* p3 = malloc(0x7fffffff); if (p3 == NULL) { cout << "malloc fail" << endl; } void* p4 = operator new(0x7fffffff); ListNode* p5 = new ListNode(2); cout << "继续" << endl; } // // int main() { try { f(); } catch (exception& e) { cout << e.what() << endl; } return 0; }
malloc
失败返回NULL,程序还会继续向下执行,但是operator new失败,就会报异常,f函数后面的没有在继续执行,然后走进catch语句中。
5.operator new和operator delete的类函数重载
struct ListNode//目的是提高效率 { ListNode* _next; ListNode* _prev; int _val; // 类中重载专属operator new 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; } ListNode(int val) :_next(nullptr) , _prev(nullptr) , _val(val) {} }; int main() { ListNode* p = new ListNode(1); delete p; return 0; }
如果你自己在类里面写了operator new
和operator delete
,那么编译器就不会去调用系统提供的了,这是一种提高效率的方式。
我们是可以通过反汇编来看效果的
6.定位new–placement-new
通过上述的学习我们知道,malloc
,和operator
是不会去调用构造函数的,new
会去调用构造函数,而且构造函数是只允许构造出对象时调用,而你的对象创建出来之后是无法调用构造的。
但是如果我们operator new
了一块空间(未初始化),但是又想要调用其构造函数,该怎们办呢?
class A { public: A(int a = 0) :_a(a) { cout << "A(int a = 0)" << endl; } ~A() { cout << "~A()" << endl; } private: int _a; }; int main() { A* pa = (A*)operator new(sizeof(A)); //pa->A();//error错误调用方式,构造函数只允许构造时进行调用 new(pa)A; // 定位new,placement-new,显示调用构造函数初始化这块对象空间 A* pb = (A*)operator new(sizeof(A)); new(pb)A(3); A* pc = new A; new(pc)A(3); // 等于 delete p pa->~A(); // 析构函数可以显示调用 operator delete(pa); pb->~A(); operator delete(pb); delete pc; return 0; }
大家要知道定位new哦,他是一种对已经创建出来的对象,还能继续调用其构造函数的方式。
7.`malloc`/`free`和`new`/`delete`的区别
常考面试题
8.再次理解内存泄漏
首先大家先想一想这个问题:内存泄漏是指针丢了还是内存丢了?
内存管理中,内存是用指针去维护的,当你的动态内存空间还没有释放时,你已经把指针弄丢了,那么你将无法控制这段空间,进而导致内存泄漏。
什么是内存泄漏,内存泄漏的危害
什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而 造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会 导致响应越来越慢,最终卡死。
到此这篇关于C++图文并茂分析讲解内存管理的文章就介绍到这了,更多相关C++内存管理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!