复习C语言中的动态内存管理方式
void test1() { int* p1 = (int*)malloc(sizeof(int)); free(p1); // 1.malloc/calloc/realloc的区别: // malloc - 只分配内存,不初始化 // calloc - 分配内存并初始化为0,参数是(元素个数, 每个元素大小) // realloc - 重新分配内存,可以扩大或缩小已有内存块 int* p2 = (int*)calloc(4, sizeof(int)); // 分配4个int并初始化为0 int* p3 = (int*)realloc(p2, sizeof(int) * 10); // 将p2的内存扩大到10个int // 这里不需要free(p2)! // 因为realloc成功后,原来的p2指向的内存已经被释放或移动 // 如果realloc返回新指针,原指针p2不应该再被使用 free(p3); // 只需要释放新的指针p3 }malloc是分配内存,不会初始化
calloc是分配内存初始化为0,参数(元素个数,每个元素的大小)
realloc是重新分配内存,可以扩大或者缩小已有的内存
在C++中兼容我们的C语言,所以C语言的动态内存管理在C++也可以使用
学习C++中的动态内存管理的方法
C语言内存管理方式在C++中可以继续使用,但是有一些地方不一定能做到,并且使用起来会有一些麻烦,因此,C++引出了自己的内存管理方式:通过new和delete操作符进行动态内存管理
但是,我们需要注意的是,malloc/free是库函数,而new/delete是操作符
void test1() { //C语言 malloc等是库函数 int* p1 = (int*)malloc(sizeof(int)); free(p1); //C++ new/delete是操作符 // 动态申请一个int类型的空间 int* ptr4 = new int;//写法 // 动态申请一个int类型的空间并初始化为10 int* ptr5 = new int(10);//写法 delete ptr4; delete ptr5; }那么,new/delete和malloc/free有没有什么区别呢?
如果动态申请的对象是内置类型,那么用malloc和new没有什么区别,如果动态申请的对象是自定义类型,那么就有区别,比如
class A { public: A(int a=0) :_a(a) { cout<<"A()"<<endl; } ~A() { cout<<"~A()"<<endl; } private: int _a; }; int main() { //C语言自定义类型开辟空间 A* p3 = (A*)malloc(sizeof(A));//没有初始化 free(p3); //C++自定义类型开辟空间 A* p4 = new A;//调用构造函数初始化 delete p4;//调用析构函数 }new和delete不仅仅会开空间和释放空间,还会调用构造函数和析构函数
接下来我们用数据去看,malloc和new的区别:
我们使用malloc没有初始化
使用了new进行了初始化
那我们想给new的对象传参应该怎么写呢?
A* p4 = new A(10);//调用构造函数初始化 //我们也可以new数组: A *ptr1 = new A[10];//new了10个对象,调用了10次构造函数 delete[] ptr1;需要注意的是:在new数组的时候,delete需要+[]
总结:
对于内置类型用malloc和new没有什么区别,但是使用自定义类型就有区别,用malloc和new的区别是:new和delete会去调用构造函数和析构含糊,一定要匹配的去使用,不然会造成崩溃,在C++中,也建议去使用new和delete,malloc和free能做到的,那么new和delete也能做到,相反,new和delete能做到的,malloc和free不一定能够做到
operator new 与 operator delete函数
new和delete是用户进行动态内存申请和释放的操作符,operator new 和 operator delete 是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间.
注意:operator new 与 operator delete函数是全局的库函数,他们并不是new和delete的重载
我们使用new一个新对象T的时候,编译器有两个做法
1、申请内存,调用operator new(底层其实是将malloc封装)
2、调用库函数
在delete的时候,编译器会发生
1、调用T的析构函数
2、调用operator delete(底层其实是将free封装)
我们平时写malloc,会这样子去写
int main() { //malloc失败,返回NULL char* p1 = (char*)malloc((size_t)2*1024*1024*1024); if(p1==NULL) { printf("malloc fail\n"); } else { printf("malloc success\n"); } return 0; }那么new操作符在new失败的时候会怎么去处理呢?
int main() { char* p2 = new char[0x7fffffff]; //并没有执行 if(p2==NULL) { printf("new fail\n"); } else { printf("new success\n"); } return 0; }可以看到,new失败的时候,并没有去执行if语句
我们说过,malloc和new不一样,new在申请空间失败的时候,会抛出异常,下面我们看一段抛出异常的代码
int main() { try { void* p1 = new char[1024 * 1024 * 1024]; cout << p1 << endl; void* p2 = new char[1024 * 1024 * 1024]; cout << p2 << endl; void* p3 = new char[1024 * 1024 * 1024]; cout << p3 << endl; } catch (const exception& e) { cout << e.what() << endl;//抛出异常 } return 0; }operator new其实就是对malloc的封装,如果申请内存失败了,抛出异常,封装malloc+抛出异常
operator delete也是对free进行封装
通过上述两个全局函数的例子
我们可以发现
operator new实际也是通过mallloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则抛出异常。operator delete 最终是通过free来释放空间的
new和delete的实现原理
内置类型
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不一样的地方是:new/delete申请释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败的时候会抛出异常,malloc会返回NULL
自定义类型
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来释放空间
int main() { Stack st; Stack* ps = new Stack; delete ps; retunr 0; }定位new表达式(placement-new)
定位new表达式是在已分配的原始内存空间中调用构造函数初始一个对象
使用格式:
new (place_address) type或者new(place——address)type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
构造函数调用什么时候自动调用呢?
1、创建对象时
2、new一个对象时
class A { private: int _a; public: A(int a = 0)//构造函数 :_a(a) { cout << _a << ":" << "A(int a = 0)构造函数" << endl; } ~A()//析构函数 { cout << _a << ":" << "~A()析构函数" << endl; } }; int main() { A* ptr1 = (A*)malloc(sizeof(A)); //显示调用构造函数用来初始化一块已经开辟好的空间 new(ptr1)A(10); //显示调用析构函数 ptr1->~A(); //释放ptr1指向的空间 free(ptr1); A* ptr2 = (A*)operator new(sizeof(A)); //显示调用构造函数用来初始化一块已经开辟好的空间 new(ptr2)A; //显示调用析构函数 ptr2->~A(); //释放ptr2指向的空间 operator delete(ptr2); return 0; }ptr1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行,显式对一块空间调用构造函数初始化,new(ptr1) A;
如果我们复制一份a数组到另外一块空间b数组,怎么复制呢?
也许你想的是这样,通过一个循环来拷贝:
int main() { A a[5]; //复制一份a数组到另外一块空间b A* pb1 = new A[5]; for(int i =0;i<5;++i) { b[i]=a[i]; } cout<<endl; //代价大 构造+赋值 return 0; }但是这样代价大,经过了构造+赋值重载,那么能不能直接构造呢?通过定位new表达式就可以直接构造:
int main() { A a[5]; //能不能直接构造? A *pb = (A*)malloc(sizeof(A)*5); for(int i =0;i<5;++i) { new(pb+i)A(a[i]); }//只有构造 return 0; }常见面试题
malloc/free和new/delete的区别
1、特点和用法
malloc和free是函数,new和delete是操作符;malloc申请空间时,需要手动计算空间大小并传递,new只需在后面跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可;malloc的返回值是void*,在使用时需要强转,new不需要,因为new后面跟的是空间的类型
2、底层原理区别
申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数和析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
3、处理错误的方式
malloc申请空间失败时,返回NULL,因为使用时需要判空,new不需要,但是new需要捕获异常
内存泄漏
什么是内存泄漏呢?
在堆上申请了的空间,在我们不用了以后也没有释放,就存在内存泄漏,因为你不用了,也没有还给系统,别人也用不了。俗话说:占着茅坑不拉屎
那么我们如何预防内存泄漏呢?
1、智能指针
2、内存泄漏的检测工具