一.C/C++中程序内存区域划分
在C/C++中,程序内存区域可以分成四个部分:
1.栈:用来保存非静态的局部变量/函数参数/返回值等等,栈试想下增长的
2.堆:用于程序运行时的动态内存分配,堆是可以向上增长的
3.数据段:存储全局数据和静态数据
4.代码段:保存可执行的代码和只读的常量
二.C语言动态内存的管理方式
C语言我们使用的开辟内存空间的函数有三个:
1.malloc: void *malloc(size_t size); 用来申请size个字节的空间
2.calloc: void *calloc(size_t nmemb, size_t size); 用来申请size*nmemb个字节的空间,并对内存初始化
3.realloc: void *realloc(void *ptr, size_t size); 如果size小于或者等于ptr指向的空间,则保持不变,否则重新分配内存空 间,把之 前的数据搬移到引得空间中
所以:relloc(NULL,size)=malloc(NULL,size)
用来内存释放的只有一个函数:
free: void free(void *ptr);
void test()
{
int* p1 = (int *)malloc(sizeof(int)* 10);
int* p2 = (int *)calloc(4,sizeof(int));
int* p3 = (int *)realloc(p2,sizeof(int)* 6);
free(p1);
free(p3);
//free(p2);在这不应该重复释放,因为p3就是在p2的地址上多申请sizeof(int)* 6个字节,不能重复释放
}
我们在用malloc申请的空间一定要记住:要用free释放,不然就会内存泄漏。
当我们没有释放内存我们可以通过系统给的函数自己检测:
#include<crtdbg.h>
CrtDumpMemoryLeaks();
假如没有内存释放,就会显示如下:
三.C++的动态内存管理方式
C语言的动态内存的管理方式在C++中同样适用,只不过C++有自己的动态内存管理方式:C++中通过new和delete来进行动态管理。
new/delete动态管理对象
newp[]/delete[]动态管理对象数组
void test()
{
int* p1 = new int; 分配一个int的单个数据空间
int* p2 = new int(3); 分配一个int的单个数据空间,(3)是初始化的值
int*p3 = new int[3]; 分配三个int的数据空间
delete p1;
delete p2;
delete[] p3;
}
在C++中:new和delete,new[]和delete[]一定要匹配使用!一定要匹配使用!!一定要匹配使用!!!
malloc/free和new/delete的比较
看代码:
void test()
{
int* p1 = (int*)malloc(sizeof(int));
int* p2 = (int*)malloc(sizeof(int));
int* p3 = new int;
int* p4 = new int;
int* p5 = new int[10];
int* p6 = new int[10];
delete p1;
delete[] p2;
free(p3);
delete[]p4;
free (p5);
delete p6;
}
我们分别用malloc和new申请空间,但是在释放的时候,却用的其他两种释放的形式。那么这样的代码会出错吗?有没有内存泄漏?
通过调试,我们发现代码不仅不会出错,而且并没有出现内存泄漏。
那么我们在加一个类再来调试:
class Test
{
public:
Test()
:_data(0)
{
cout << this << endl;
}
~Test()
{
cout << this << endl;
}
private:
int _data;
};
void test()
{
Test* p1 = (Test*)malloc(sizeof(Test));
Test* p2 = (Test*)malloc(sizeof(Test));
Test* p3 = new Test;
Test* p4 = new Test;
Test* p5 = new Test[10];
Test* p6 = new Test[10];
delete p1;
delete[] p2;
free(p3);
delete[]p4;
free (p5);
delete p6;
}
int main()
{
test();
_CrtDumpMemoryLeaks();
system("pause");
return 0;
}
当我们在运行的时候发现每当遇到delete[]的时候代码就会触发断点二崩溃。
我们按照正常的释放方式运行一遍:
class Test
{
public:
Test()
{
cout << this << endl;
}
~Test()
{
cout << this << endl;
}
private:
int _data;
};
void test()
{
Test* p1 = (Test*)malloc(sizeof(Test));
Test* p2 = (Test*)malloc(sizeof(Test));
Test* p3 = new Test;
Test* p4 = new Test;
Test* p5 = new Test[3];
Test* p6 = new Test[3];
printf("\n");
free(p1);
free(p2);
//delete[] p2;
delete(p3);
delete p4;
//delete[]p4;
delete[]p5;
delete[]p6;
//free (p5);
//delete p6;
}
int main()
{
test();
_CrtDumpMemoryLeaks();
system("pause");
return 0;
}
运行结果:
所以在C++之所以加入了new和delete是因为它们分别需要调用构造函数和析构函数。
四.new和delete的实现原理
1.内存类型
new和delete申请的是单个元素的空间,而new[]和delete[]申请的是一段连续的空间。new在申请空间失败时会抛出异常,而malloc会返回空指针。
2.自定义类型
(1)new的原理
调用operator new函数申请空间,然后再申请的空间上调用构造函数,完成对对象的构造
(2)delete的原理
在申请的空间上调用析构函数,清理对象中的资源,然后调用operator delete函数释放对象的空间
(3)new[]的原理
调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请,然后在申请的空间上执行N次构造函数,完成对N个对象的构造
(4)delete[]的原理
在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理 ,再调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空
五.定位new表达式
定位new表达式就是给已经申请好的空间调用构造函数来初始化一个对象。
看如下代码:我们用malloc申请的空间,用定位new表达式来对对象进行初始化。
class Test
{
public:
Test()
:_data(0)
{
cout << this << endl;
}
~Test()
{
cout << this << endl;
}
private:
int _data;
};
void test()
{
//pt现在指向的只不过是与Test对象相同大小的一段空间,
//还不能算是一个对象,因为构造函数没有执行
Test* pt = (Test*)malloc(sizeof(Test));
new(pt) Test; //定位new表达式的格式
}
int main()
{
test();
_CrtDumpMemoryLeaks();
system("pause");
return 0;
}
六.malloc/free和new/delete的区别
我们都知道这malloc/free和new/delete都是在堆上申请空间,都需要通过用户手动进行释放,但是它们的区别有很多。
1.区别:
(1)malloc/free时函数,而new和delete时操作符
(2)malloc申请的空间不能初始化,而new申请时会调用构造函数对对象进行初始化
(3)malloc申请空间时需要传递申请空间的大小,new只需要跟上类型,由编译器自动识别。
(4)malloc申请的空间需要强制类型转化
(5)malloc申请空间失败时,返回的是空指针,因此使用时必须判空,new不需要,但是new需要捕获异常
(6)malloc申请的空间一定在堆上,而new不一定,因为operator new可以由用户自己实现。
(7)new/delete的效率稍低,因为new/delete是由malloc/free封装而成的
七.面试题
1.创建一个类,该类只能在堆上申请空间
思路:在堆上创建一个类,我们可以把构造函数和拷贝构造函数私有化,然后创建一个静态的函数来完成对对象的创建
class onlyheap
{
public:
static onlyheap* CreateObject()
{
return new onlyheap;
}
private:
onlyheap(){};//构造函数
onlyheap(const onlyheap&);
};
2.创建一个类,该类只能在栈上申请空间
只有使用了new操作符,对象才会创建在堆上,因此只要禁用new运算符就可以实现类对象只能创建在栈上,将operator new设为私有
class StackOnly
{
public:
StackOnly() {}
private:
void* operator new(size_t size);
void operator delete(void* p);
};