复习C语言中的动态内存管理方式
cpp
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是操作符
cpp
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没有什么区别,如果动态申请的对象是自定义类型,那么就有区别,比如
cpp
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的对象传参应该怎么写呢?
cpp
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,会这样子去写
cpp
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失败的时候会怎么去处理呢?
cpp
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在申请空间失败的时候,会抛出异常,下面我们看一段抛出异常的代码
cpp
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来释放空间
cpp
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一个对象时
cpp
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数组,怎么复制呢?
也许你想的是这样,通过一个循环来拷贝:
cpp
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表达式就可以直接构造:
cpp
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、内存泄漏的检测工具