一 内存分布
--前言
平时写的代码,都是写在文件中,存储在磁盘.
经过编译链接生成的可执行程序,也是一个文件(二进制),存储在磁盘上.
此时可执行程序里写的主要是 二进制指令代码 和 数据【包括全局变量、常量】.
--空间分布
程序加载到内存,将数据加载到对应的位置
栈:也叫堆栈,存储非静态局部变量、返回值、函数参数等
栈区域的变量空间,是在进程运行,建立栈帧时开辟的.
堆:用于动态内存分配,堆上的空间也是在进程运行时开辟的.
数据段/静态区:存储全局数据和静态数据,程序刚加载到内存,就要把全局和静态数据加载进来.
代码段/常量区:存储二进制指令代码 和 常量,程序刚加载到内存,就要把它们加载进来.
二 C++动态内存管理
只有堆区的数据,需要我们手动申请,手动释放,否则会造成内存泄漏.
c语言的malloc/calloc/realloc函数仍然可以用,
同时c++引入了 new/delete 操作符,来进行动态内存管理.
--用法
对内置类型
void test()//针对内置类型
{
//动态申请一个int空间
int* p1 = new int;
cout << *p1 << endl;
delete p1;//手动释放
//动态申请一个double空间,初始化为1.1
double* p2 = new double(1.1);
cout << *p2 << endl;
delete p2;
//动态申请多个int
int* p3 = new int[3];
for (int i = 0;i < 3; i++)
{
cout << p3[i] << " ";
}
cout << endl;
delete[] p3;//new和delete匹配,new[]和delete[]对应匹配
//动态申请多个int,并用{}初始化(c++11支持)
int* p4 = new int[4]{1, 2, 3};//剩下元素都会初始化为0
for (int i = 0; i < 4; i++)
{
cout << p4[i] <<" ";
}
delete[] p4;
}
结论
针对内置类型,new/new[ ]仅仅只是开空间,且更方便,并不会自动初始化内置类型.
对自定义类型
class A
{
public:
A()
{
cout << "调用构造函数" << endl;
}
~A()
{
cout << "调用析构函数" << endl;
}
};
//申请一个A类空间
A* a1 = new A;
delete a1;
cout << endl << endl;
//申请多个A类空间
A* a2 = new A[4];
delete[] a2; //匹配new[] 和delete[]
结论
针对自定义类型,除了申请和释放空间,
new会去调用它的构造函数,delete会去调用它的析构函数.
但malloc和free不会去调用.
注意
不传参数调用的是默认构造函数,传参数可以选择调用对应的构造函数
class A
{
public:
A(int a)
{
cout << "调用构造函数1" << endl;
}
A()
{
cout << "调用构造函数2" << endl;
}
};
void test3()
{
A* a1 = new A(1);
A* a2 = new A;
delete a1;
delete a2;
}
new申请内存失败
new失败后,不需要检查返回值,它失败后是抛异常,此时需要捕获异常
void test4()
{
try
{
//new申请空间发生异常,立刻跳转到catch,不会再向下执行,否则无视catch
char* a = new char[1024 * 1024*1024u*2 - 1];
cout << a << endl;
}
catch (const exception& e)
{
//打印出异常内容
cout << e.what() << endl;
}
}
--底层原理【重点】
以下是new一个A类对象的汇编代码(部分),【注意new是一个关键字/操作符】
会发现它先调用了名为operator new的函数,再调用A构造函数.
operator new函数:
内部封装malloc函数帮助【操作符new】开空间;
同时实现抛异常的机制.(全局函数)
operator delete函数:
内部封装free函数帮助【操作符delete】释放空间,
与operator new配对.(全局函数)
cplusplus.com里的函数原型
结论
new操作符
1 先底层调用 operator new函数 在堆上开空间,并实现抛异常机制;
2 再调用构造函数初始化自定义类型.
3 operator new函数内部调用了malloc函数
【先在堆区开了空间,才能初始化里面的类对象】
delete操作符
1 先调用析构函数清理对象空间.
2 底层调用 operator delete函数 释放空间;
3 operator delete函数内部调用了free函数
【先把堆里的对象内部空间清理后,再free掉该堆区空间】
--重载operator new和operator delete
可以在类里重载类专属operator new和operator delete
例如:在ListNode里重载了operator new函数,
在new这个类时,new调用的operator new函数就是该类专属的.
class ListNode
{
public:
ListNode(int val = 0)
:_val(val)
,_next(nullptr)
{}
void* operator new(size_t size)
{
cout << "使用ListNode专属的operator new" << endl;
//使用域作用限定符指定全局域里的operator new
void* ret = ::operator new(size);
return ret;
}
void operator delete(void* _block)
{
cout << "使用ListNode专属的operator delete" << endl;
::operator delete(_block);
}
private:
int _val;
ListNode* _next;
};
void test7()
{
ListNode* n1 = new ListNode(1);
delete n1;
}
--定位new表达式
某些场景下,空间已经开辟出来了,但还没有初始化。例:
A* a1 = (A*)malloc(sizeof(A));
此时可以手动调用构造函数初始化这块空间吗?
定位new表达式是在已分配的内存空间中,调用构造函数初始化[一个对象].
定位new表达式格式:
new(空间地址)type(参数列表)
例:
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "构造函数" << _a << endl;
}
~A()
{
cout << "析构函数" << endl;
}
private:
int _a;
};
void test8()
{
A* a1 = (A*)malloc(sizeof(A));
//定位new格式,手动调用构造函数
new(a1)A(1);
//手动调用析构函数
a1->~A();
free(a1);
A* a2 = (A*)malloc(sizeof(A));
//不给参数就调用默认构造
new(a2)A;
a2->~A();
free(a2);
}
不能一次性初始化多个: