前言
主要就是两点:
- C++的内存管理和C语言其实大差不差,需要了解一下栈,堆,静态区(数据段),常量区(代码段),这几个区间,需要了解这几个区间存放的都是什么
- 需要区分几个动态开辟的空间
- 【C语言,malloc,realloc,】
- 【C++,new,delete,operator new,operator delete】
内存分布情况
概念概述(主要存放的内容)
一、栈又叫堆栈:
在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。二、堆区 (heap):
内存映射段是高效的 I/O 映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(Linux 如果没学到这块,现在只需要了解一下)。
一般由程序员分配释放,若程序员不释放,程序结束时可能由 OS 回收。分配方式类似于链表。
主要存放的是:动态分配的对象,大型数据结构,需要长期存在的数据,实现动态数据结构三、数据段 (静态区):
堆用于程序运行时动态内存分配,堆是可以上增长的。
主要存放的是:(static) 存放全局变量、静态数据。程序结束后由系统释放。四、代码段:
代码段 -- 可执行的代码 / 只读常量。
主要存放的是:存放函数体(类成员函数和全局函数)的二进制代码五、对比观察图
指针在内存里面的存放
一、局部指针变量 (存放在栈区)
在函数内部定义的指针变量,如果只是指向局部变量或者作为临时用途,通常存放在栈区。例如在某个函数内部定义一个指针指向另一个局部变量:
二、全局指针变量 (存放在静态存储区)
如果定义的是全局指针变量,那么它存放在静态存储区。
三、动态分配的指针 (存放在堆区)
通过动态内存分配函数 (如 malloc、calloc、realloc 等) 分配的内存空间的指针存放在堆区。
题目精讲
题目
图解
精讲
C++内存管理方式(关键词new和delete的使用)
概念概述
C 语言内存管理方式在 C++ 中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此 C++ 又提出了自己的内存管理方式:通过 new 和 delete 操作符进行动态内存管理。
new的使用+delete的使用
- new类似于malloc和realloc,都是开辟一个空间,只是new可以在开辟空间的时候选择是否需要进行初始化
- delete类似于free,但是delete这个关键字是可以连续释放动态内存的
图解:
代码解释(这里我是解释放在下面,代码放在上面):
利用malloc创建一个空间(对比使用):
//malloc创建一个空间 cout << "malloc创建一个空间:" << endl; int* p1 = (int*)malloc(sizeof(int) * 4); if (p1 == NULL) { exit(1); } cout << p1; //释放空间 free(p1); printf("\n\n");
- 这里我们首先创建一个空间,使用malloc创建,使用free进行销毁,用来和接下来的代码进行对比。
new创建内置类型一个空间+delete销毁内置类型一个空间
//new(初始化整形为3) cout << "new(初始化整形为3):" << endl; int* p2 = new int(3); cout << *p2 << endl; //delete(销毁开辟空间) delete p2; printf("\n\n");
- 利用new创建一个空间,delete销毁一个空间
- 语法,int(类型)* p2(指针变量) = new(关键字) int(3)(初始化);
- new创建空间之后可以初始化
new创建内置类型多个空间+delete销毁内置类型多个空间
//new(创建多个空间) cout << "new(创建多个空间):" << endl; int* p3 = new int[10] {1, 2, 3, 4}; for (int i = 0; i < 10; i++) { cout << p3[i] << "/"; } //delete释放多个对象 delete[10] p3; printf("\n\n");
- 这里是创建多个空间
- int* p3 = new int[10] {1, 2, 3, 4};//这里是可以进行初始化的,1,2,3,4但是这里初始化是只是初始化前四位的,因为只是前四位给了数值进行初始化
- 销毁的时候,我们是可以直接进行多个销毁的delete[(个数)]+ 指针变量
new创建多个自定义类型的空间+delete销毁多个自定义类型的空间
#include <iostream> using namespace std; class A { public: A(int a = 1, int b = 1, int c = 1) :_a(a) ,_b(b) ,_c(c) {} //打印函数 void _print() { cout << _a << "/" << _b << "/" << _c << endl; } private: int _a; int _b; int _c; }; //动态内存申请的使用 //new,delete //operator new,operator delete int main() { //创建自定义类型,初始化每个自定义类型的_a cout << "创建自定义类型,初始化每个自定义类型的_a:" << endl; A* p4 = new A[10]{ 2,2,22,22,2,2,2,22,2,2 }; for (int i = 0; i < 10; i++) { p4[i]._print(); } delete[10] p4; printf("\n"); return 0; }
- 这里new就体现的比malloc更便携的地方,这里是自定义类型的创建空间,不仅可以创建空间,还可以创建多个空间,并且进行初始化,但是这里只是对_a进行初始化
- 这里需要注意的是, 类必须有默认构造函数,不然会导致开辟空间的失败、
- 实在不想写默认构造也可以,就是有点麻烦
- delete的销毁同理
new创建多个自定义类型的空间+delete销毁多个自定义类型的空间(对任意数值进行初始化):
//创建自定义类型,初始化所有你需要的数值 cout << "创建自定义类型,初始化所有你需要的数值:" << endl; A* p5 = new A[10]{ {1,2,3},{3,4,5} ,{4,5,6}, {2,2,2}, {2,22,2}, {2}, {2,2}, {2,2,2}, {}, {} }; for (int i = 0; i < 10; i++) { p5[i]._print(); } delete[10] p5;
- 这里我想说的只有一点,就是初始化的时候我们是需要用花括号的{{}{}{}{}};
代码总结
#include <iostream> using namespace std; class A { public: A(int a = 1, int b = 1, int c = 1) :_a(a) ,_b(b) ,_c(c) {} //打印函数 void _print() { cout << _a << "/" << _b << "/" << _c << endl; } private: int _a; int _b; int _c; }; //动态内存申请的使用 //new,delete //operator new,operator delete int main() { //malloc创建一个空间 cout << "malloc创建一个空间:" << endl; int* p1 = (int*)malloc(sizeof(int) * 4); if (p1 == NULL) { exit(1); } cout << p1; //释放空间 free(p1); printf("\n\n"); //new(初始化整形为3) cout << "new(初始化整形为3):" << endl; int* p2 = new int(3); cout << *p2 << endl; //delete(销毁开辟空间) delete p2; printf("\n\n"); //new(创建多个空间) cout << "new(创建多个空间):" << endl; int* p3 = new int[10] {1, 2, 3, 4}; for (int i = 0; i < 10; i++) { cout << p3[i] << "/"; } //delete释放多个对象 delete[10] p3; printf("\n\n"); //创建自定义类型,初始化每个自定义类型的_a cout << "创建自定义类型,初始化每个自定义类型的_a:" << endl; A* p4 = new A[10]{ 2,2,22,22,2,2,2,22,2,2 }; for (int i = 0; i < 10; i++) { p4[i]._print(); } delete[10] p4; printf("\n"); //创建自定义类型,初始化所有你需要的数值 cout << "创建自定义类型,初始化所有你需要的数值:" << endl; A* p5 = new A[10]{ {1,2,3},{3,4,5} ,{4,5,6}, {2,2,2}, {2,22,2}, {2}, {2,2}, {2,2,2}, {}, {} }; for (int i = 0; i < 10; i++) { p5[i]._print(); } delete[10] p5; return 0; }
new+delete和malloc+free之间的对比使用
1,malloc这里可以不需要初始化,但是new这里是需要初始化的
,2,从链表就可以看出,简单化了,malloc还需要强制类型转化一下,再sizeof一下
operator new和operator delete的深入讲解
前言
关于operator new和operator delete我们需要明确一个概念,这两个都是一个函数,和malloc,free一样都是一个函数,但是这里需要明确的是,这里只是类似,不是一样。
operator new语法结构
语法结构
#include<iostream> int main() { //标准的分配内存的空间形式 //分配一个内置类型,int内置类型的空间 void* ptr1 = operator new(sizeof(int)); //分配一个数组形式的内存空间 void* ptr4 = operator new[](10 * sizeof(int)); //这样的形式也是可以使用的,但是可能会出现问题,因为operator new是一个没有初始化,也就是未定义的内存空间 //这样分配内存容易导致错误访问,所以还是建议使用标准化来分配内存空间 //void* ptr2 = operator new(10 * sizeof(int)); //void* ptr3 = operator new[](sizeof(int)); //void* ptr4 = operator new[](10 * sizeof(int)); return 0; }
operator delete语法结构
#include<iostream> int main() { //标准的分配内存的空间形式 //分配一个内置类型,int内置类型的空间 void* ptr1 = operator new(sizeof(int)); //分配一个数组形式的内存空间 void* ptr4 = operator new[](10 * sizeof(int)); //这样的形式也是可以使用的,但是可能会出现问题,因为operator new是一个没有初始化,也就是未定义的内存空间 //这样分配内存容易导致错误访问,所以还是建议使用标准化来分配内存空间 //void* ptr2 = operator new(10 * sizeof(int)); //void* ptr3 = operator new[](sizeof(int)); //void* ptr4 = operator new[](10 * sizeof(int)); //operator new和delete这里是函数,所欲我们销毁的时候是函数的形式销毁,销毁的语法结构 //释放单个内存空间 //释放内存空间、标准化释放内存空间 operator delete(ptr1); operator delete[](ptr4); return 0; }
operator new+operator delete原理讲解
关于operator new
- operator new我们可以看出来,其实new是operator new的一个封装,因为new在使用的时候会调用operator new
- operator new的底层实现上面是调用malloc来实现开辟空间的
关于operator delete
- 从operator delete我们可以看出来,delect本质也是对operator delete函数的封装,再严谨的讲解就是,是对free的封装,free是对free_dbg(p,_NORMAL_BLOCK)的封装
- 所以我们可以更清晰的看出,operator delete是一个函数,不是关键字
- delete是关键字,不是函数
operator new+operator delete和new+delete的深入对比
一、内置类型
- 对于内置类型,new 和 malloc、delete 和 free 基本类似。不同在于:new/delete 申请和释放单个元素空间,new []/delete [] 申请和释放连续空间;new 申请空间失败会抛异常,malloc 失败返回 NULL。
- 抛异常(就是告诉你哪里有错,并且继续运行程序)
二、自定义类型 new 的原理
- 调用 operator new 函数申请空间,底层类似 malloc(malloc 不抛异常)。
- 在申请的空间上执行构造函数完成对象构造。
三、自定义类型 delete 的原理
- 在空间上执行析构函数清理对象资源,本质类似 free 的调用。
- 调用 operator delete 函数释放对象空间。
四、new T [N] 的原理
- 调用 operator new [] 函数,实际在其中调用 operator new 完成 N 个对象空间申请。
- 在申请的空间上执行 N 次构造函数。
五、delete [] 的原理
- 在释放的对象空间上执行 N 次析构函数,清理 N 个对象资源。
- 调用 operator delete [] 释放空间,实际在其中调用 operator delete。
operator new+operator delete和new+delete使用时候的注意事项
不要交错使用,很容易导致资源使用出现问题
- operator new只是开辟空间,不会进行初始化的
- operator delete是只是销毁空间,不会清理资源的
- new,在开辟空间的时候会初始化并且构建资源
- delete,销毁空间的时候会调用构造函数销毁资源
- 知道,尽量不要交错使用就可以
#include<iostream> using namespace std; class A { public: A(int capacity = 4, int size = 0) :_Capacity(capacity) , _size(size) , _arr(nullptr) { //创建空间 _arr = new int[_Capacity]; printf("A()"); } ~A() { //这里释放我们是需要匹配方括号,这里是释放数组形式的内容,自适应找到需要释放的内存 delete[] _arr; _Capacity = 4; _size = 0; printf("~A()"); } private: int* _arr; int _Capacity; int _size; }; //operator new创建空间,new构造 //我们需要使用 operator delete先销毁空间,再使用delete销毁资源 //并且是不能直接使用delete来进行销毁空间的,因为我们创建的空间是未定义的,new构造之后我们是会申请资源甚至空间的 //如果直接用delect销毁不使用operator delete销毁就会导致空间没有销毁 //如果只是使用operator delete销毁空间,就会导致资源没有销毁 //并且此时还应该先试用delete来销毁资源 再销毁空间 int main() { A* p = new A[10]; delete[] p; printf("\n"); void* ptr1 = operator new[](10 * sizeof(A)); //初始化,我们也可以进行定位new进行初始化,定位new里面我们会进行讲解 for (int i = 0; i < 10; ++i) { new (static_cast<A*>(ptr1) + i) A(i * 10); } // 使用这些 A 对象 // 销毁这些对象 for (int i = 0; i < 10; ++i) { (static_cast<A*>(ptr1) + i)->~A(); } operator delete[](ptr1); return 0; }
- operator new创建空间,new构造
- 我们需要使用 operator delete先销毁空间,再使用delete销毁资源,并且是不能直接使用delete来进行销毁空间的,因为我们创建的空间是未定义的,new构造之后我们是会申请资源甚至空间的
- 如果直接用delect销毁不使用operator delete销毁就会导致空间没有销毁
- 如果只是使用operator delete销毁空间,就会导致资源没有销毁
- 并且此时还应该先使用delete来销毁资源 再使用operator delete销毁空间
定位new表达式(placement-new) (了解)
定位new主要使用的区域在于内存池,所以这里作为了解进行学习
一、在特定内存位置构造对象:
- 可以在预先分配好的内存区域中创建对象,而不依赖于默认的内存分配机制。例如,使用
operator new
或其他方式分配了一块内存后,可以使用定位new
在这块内存上构造对象。- 语法形式为:
new (pointer) Type(args...)
,其中pointer
是指向已分配内存的指针,Type
是要构造的对象类型,args...
是构造函数的参数。- 当使用普通的
new
和delete
操作符时,delete
会自动调用对象的析构函数并释放内存。但是对于通过定位new
创建的对象,由于没有通过常规的内存分配机制,仅仅使用delete
或者operator delete
来释放内存不会自动调用析构函数。- 此时我们发现可以用operator new开辟空间,new构造没有那么麻烦了,我们可以直接定位,可以直接看下面代码,但是这里有问题的就是不支持显示构造,但是支持显示析构
#include<iostream> using namespace std; class A { public: A(int capacity = 4, int size = 0) :_Capacity(capacity) , _size(size) , _arr(nullptr) { //创建空间 _arr = new int[_Capacity]; printf("A()"); } ~A() { //这里释放我们是需要匹配方括号,这里是释放数组形式的内容,自适应找到需要释放的内存 delete[] _arr; _Capacity = 4; _size = 0; printf("~A()"); } private: int* _arr; int _Capacity; int _size; }; int main() { //正常函数的调用 A* p = new A[10]; delete[] p; printf("\n"); //定位的使用,operator new创建一个空间,new定位进行构造 void* ptr1 = operator new[](10 * sizeof(A)); new(ptr1) A[10];//定位不支持显示构造,必须有默认构造 //delete[] ptr1;//定位是需要显示调用析构函数来进行释放资源的。直接使用delect自动释放资源是不能实现的 for (int i = 0; i < 10; i++) { //static_cast强制类型转化关键字//<A*>转化为A类型//(ptr1) + i)->~A();循环显示调用析构函数 (static_cast<A*>(ptr1) + i)->~A(); } operator delete[](ptr1); return 0; }
二、与内存池等技术结合使用:
- 在一些高性能场景下,为了避免频繁的内存分配和释放开销,可以使用内存池预先分配一大块内存,然后在需要创建对象时使用定位
new
在内存池中选取合适的位置构造对象。- 这样可以提高内存分配的效率,减少内存碎片的产生。
三、资源管理和对象生命周期控制:
- 通过定位
new
,可以更精细地控制对象的构造和析构时机,特别是在一些复杂的资源管理场景中。- 例如,可以在特定的资源初始化后,在与之相关的内存位置构造对象,确保资源和对象的生命周期紧密关联。
四、注意事项:
- 使用定位
new
构造的对象,在销毁时需要手动调用析构函数,而不能直接使用delete
来释放内存,因为delete
会尝试释放由默认内存分配机制分配的内存,而不是定位new
所使用的内存。- 例如:
new (ptr) Type(args...);
构造的对象,在销毁时应该使用ptr->~Type();
来调用析构函数。
malloc/free 和 new/delete 的区别