一、内存划分
C++会把内存分成5块来管理:
栈:非静态的局部变量,函数的参数、返回值,函数栈帧的开辟都在栈上,栈是向下增长的,也就是说越在后面申请的非静态局部变量,地址越小;
堆:malloc/calloc/realloc/free以及new/delet这类动态申请的空间都在堆上开辟,堆是向上增上的;
内存映射段:(Linux笔记中的进程间通信会详细讲解);
数据段:也叫静态区,用来存放静态的和全局的数据;
代码段:也叫常量区,用来存放可执行的二进制代码以及常量。
二、new与delete
内置类型
int main()
{
// new 后跟类型名可以在堆上申请该类型大小的空间
// 并返回该空间的首地址
// delet 后跟 new 的空间的首地址可以释放空间
int* p1 = new int;
cout << "p1未初始化,会是随机值" << endl << *p1 << endl << endl;
delete p1;
// 申请单个空间,在类型后+()可以初始化
double* p2 = new double(2.2);
cout << "p2初始化为2.2" << endl << *p2 << endl << endl;
delete p2;
// 申请一块连续的空间,需要在类型后+[],里面放需要申请的个数
// 连续的空间在释放时 delete 后要跟[],里面不用放数据
int* p3 = new int[5];
cout << "p3的5个元素未初始化,为随机值" << endl;
for (int i = 0; i < 5; ++i)
{
cout << p3[i] << " ";
}
cout << endl << endl;
delete[] p3;
// 申请连续的空间的初始化,需要在[]后+ {}
// {} 里放初始化的数据
int* p4 = new int[10]{ 1,2,3,4,5 };
cout << "p4的5个元素初始化为1,2,3,4,5,剩下5个元素应为0" << endl;
for (int i = 0; i < 10; ++i)
{
cout << p4[i] << " ";
}
cout << endl << endl;
delete[] p4;
return 0;
}
2.自定义类型
class Test
{
private:
int _a;
public:
Test(int a = 0)
:_a(a)
{
cout << "Test():_a = " << _a << endl;
}
~Test()
{
cout << "~Test()" << endl;
}
};
int main()
{
// 对于内置类型,new会调用其默认构造函数
// delete会调用其析构函数
Test* ptest1 = new Test;
delete ptest1;
cout << endl;
// 也可以对其进行初始化赋值
Test* ptest2 = new Test(20);
delete ptest2;
cout << endl;
// 开辟一块连续的自定义类型的空间
// new会调用n次默认构造函数,delete会调用n次析构函数
Test* ptest3 = new Test[5];
delete[] ptest3;
cout << endl;
// 开辟一块连续空间并初始化
Test* ptest4 = new Test[5]{ 1,2,3 };
delete[] ptest4;
cout << endl;
return 0;
}
三、operator new与operator delete
new与delete是C++中的两个关键字,其实现的原理是通过调用对应的函数。
operator new是一个C++中的一个全局函数(不是运算符重载!)。当使用new的时候,会调用operator new函数,其参数是要开辟空间的大小,用malloc申请空间,申请成功就返回首地址,申请失败则抛出一个异常(异常先暂时了解)。
operator delete也是一个全局函数,内部实现最终是调用free释放空间。
四、定位new表达式
定位new表达式实在已经分配好的空间中调用构造函数初始化一个对象。
class Test
{
// 为了演示方便,将成员变量设置为public
public:
int _a;
Test(int a = 0)
:_a(a)
{
cout << "Test():_a = " << _a << endl;
}
~Test()
{
cout << "~Test()" << endl;
}
};
int main()
{
// 这样申请的空间并不会对这块空间初始化
Test* ptest1 = (Test*)malloc(sizeof(Test));
cout << ptest1->_a << endl << endl;
// 构造函数不能显示调用,下面的语句会报错
//ptest1->Test();
free(ptest1);
// 第一次打印应为随机值
Test* ptest2 = (Test*)malloc(sizeof(Test));
cout << ptest2->_a << endl;
// 通过定位 new 表达式调用构造函数之后,打印应为 0
new(ptest2)Test();
cout << ptest2->_a << endl;
ptest2->~Test();
free(ptest2);
cout << endl;
// 也可以传值调用构造函数
Test* ptest3 = (Test*)malloc(sizeof(Test));
new(ptest3)Test(10);
cout << ptest3->_a << endl;
ptest3->~Test();
free(ptest2);
return 0;
}
五、内存泄漏
通俗一点的内存泄露,就是在申请了空间之后,丢失的该空间的指针,从而无法释放这一块空间,导致其他程序也无法使用这一块空间,就称为内存泄漏。
int a = 0;
int* p = new int[10];
p = &a;
在上述的代码中,new int[10]申请的40个字节的空间的指针丢了,导致无法释放这一块空间。
内存泄漏可能会导致程序运行过程中可使用的内存空间越来越少,导致程序变得卡顿,直至崩溃。