【C++】C++内存管理分布
阳区欠 2024-08-09 11:35:01 阅读 75
目录
思维导图大纲:
1. 内存底层分别
分析答案:
2. 内存管理
c语言中的内存管理:
c++中的内存管理:
创建(单对象/多对象/)+ 初始化 :
创建一个链表:
失败抛异常:
正确配对使用new/delete:
3. operator new与operator delete函数
4. 定位new表达式(placement-new)
5. new和delete的实现原理
内置类型:
自定义类型:
malloc/free和new/delete的区别
思维导图大纲:
1. 内存底层分别
C/C++内存管理
我们知道在内存区大致分为以下几块:栈区,堆区,静态区(数据段),常量区(代码段)
我们来分析以下代码,看看他们分别处于什么区域
globalVar在哪里?____
staticGlobalVar在哪里?____
staticVar在哪里?____
localVar在哪里?____
num1 在哪里?____
char2在哪里?____
*char2在哪里?___
pChar3在哪里?____
*pChar3在哪里?____
ptr1在哪里?____
*ptr1在哪里?____
<code>
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = { 1, 2, 3, 4 };
char char2[] = "abcd";
const char* pChar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof(int) * 4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
free(ptr1);
free(ptr3);
}
分析答案:
<code>// globalVar是全局变量,属于静态区(数据段)
int globalVar = 1;
// staticGlobalVar是静态变量,属于静态区(数据段)
static int staticGlobalVar = 1;
void Test()
{
// staticVar是Test函数栈帧空间上的静态变量,属于静态区(数据段)
static int staticVar = 1;
// localVar是正常变量,在栈上
int localVar = 1;
// num1是数组的名字,数组名在此处代表首元素地址在栈上
int num1[10] = { 1, 2, 3, 4 };
// char2是数组的名字,数组名在此处代表首元素地址在栈上
// *char2表示首元素'a',处于栈上
char char2[] = "abcd";
// const修饰char* pChar3
// pChar3在栈上
// *pChar3由于const的修饰在常量区(代码段)
const char* pChar3 = "abcd";
// 以下的ptr(1~3)属于一个名称,在栈上
// *ptr(1~3)访问的是申请的空间在堆区
int* ptr1 = (int*)malloc(sizeof(int) * 4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
free(ptr1);
free(ptr3);
}
2. 内存管理
c语言中的内存管理:
// C语言中动态内存管理方式:malloc / calloc / realloc / free
void Test()
{
// 1.malloc/calloc/realloc的区别是什么?
// 三者都是可以开空间
// malloc:开空间不初始化
// calloc:开空间可以初始化
// realloc:可以在原有的空间上进行增容扩容
int* p1 = (int*)calloc(4, sizeof(int));
int* p2 = (int*)realloc(p1, sizeof(int) * 10);
// 这里需要free(p2)吗?
//free(p2);
// 不需要,如果这块空间足够,realloc会直接向后开辟申请空间,不需要释放空间
// 如果这块空间不够,realloc会申请另一块新的空间,将原空间内部资源拷贝过去然后释放,
// 不需要我们手动释放空间
}
c++中的内存管理:
创建(单对象/多对象/)+ 初始化 :
// c++中申请内存使用new/delete
// 不同于c语言中的malloc/realloc/free等等,new/delete使用会去调用构造函数和析构函数
class A
{
public:
// 构造
A(int a1 = 10, int a2 = 20)
:_a1(a1)
, _a2(a2)
{
cout << "A(int a1 = 10, int a2 = 20)" << endl;
}
// 拷贝构造
A(const A& a)
{
cout << "A(const A& a)" << endl;
_a1 = a._a1;
_a2 = a._a2;
}
// 析构
~A()
{
cout << "~A()" << endl;
}
private:
// 声明成员变量,给缺省值用于初始化列表
int _a1 = 10;
int _a2 = 20;
};
int main()
{
// new申请一个对象
A* p1 = new A;
delete p1;
// new申请多个对象
A* p2 = new A[4];
delete[] p2;
// new申请对象+初始化
// 方法一:
// 构造+拷贝
A aa1(1, 1);
A aa2(2, 2);
A aa3(3, 3);
A aa4(4, 4);
A* p3 = new A[4]{aa1, aa2, aa3, aa4};
delete[] p3;
// 方法二:类型转换
// 优化后:构造
A* p4 = new A[4]{ {1,1},{2,2},{3,3},{4,4} };
delete[] p4;
// 方法三:匿名对象
// 构造
A* p5 = new A[4]{ A(1,1), A(2,2), A(3,3), A(4,4) };
delete[] p5;
return 0;
}
创建一个链表:
class ListNode
{
public:
ListNode(int val = 0)
:_val(val)
,_next(nullptr)
{}
int _val = 0;
ListNode* _next;
};
int main()
{
ListNode* n1 = new ListNode(1);
ListNode* n2 = new ListNode(2);
ListNode* n3 = new ListNode(3);
ListNode* n4 = new ListNode(4);
n1->_next = n2;
n2->_next = n3;
n3->_next = n4;
delete n1;
delete n2;
delete n3;
delete n4;
return 0;
}
失败抛异常:
// new申请失败不会返回NULL,而是抛异常
void Text01()
{
// 关键字 throw/try/catch
void* p1 = new char[1024 * 1024 * 1024]; // 1G
cout << p1 << endl;
void* p2 = new char[1024 * 1024 * 1024]; // 1G
cout << p2 << endl;
void* p3 = new char[1024 * 1024 * 1024]; // 1G
cout << p3 << endl;
}
void Text02()
{
int n = 0;
while (1)
{
void* p1 = new char[1024 * 1024]; // 1M
++n;
cout << p1 << "->" << n << endl;
}
}
int main()
{
try
{
// Text01();
// 虚拟内存
// 32位 -> 4G
// 64为 -> 8G
// Text02();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
正确配对使用new/delete:
// 正确使用new和delete
class A
{
public:
A(int a1 = 1, int a2 = 2)
:_a1(a1)
,_a2(a2)
{}
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
B(int b1 = 1, int b2 = 2)
:_b1(b1)
, _b2(b2)
{}
private:
int _b1 = 1;
int _b2 = 2;
};
int main()
{
// new 与 delete 对应
// new[] 与 delete[] 对应
// 串用可能会出现以下情况
// 可能会出现报错
A* p1 = new A[10];
delete p1;
B* p2 = new B[20];
delete p2;
return 0;
}
3. operator new与operator delete函数
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是
系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过
operator delete全局函数来释放空间。
<code>class A
{
public:
A(int a = 1)
:_a(a)
{}
~A()
{}
void Print() const
{
cout << _a << endl;
}
private:
int _a = 1;
};
int main()
{
// 写法一:
A* ptr1 = new A(1);
ptr1->Print();
// 写法二:
// 申请空间+初始化
A* ptr2 = (A*)operator new(sizeof(A));
new(ptr2)A(2);
ptr2->Print();
// 析构+释放空间
ptr2->~A();
operator delete(ptr2);
return 0;
}
4. 定位new表达式(placement-new)
这种方法看起来有点多此一举的味道,明明直接使用new/delete就可以直接申请
但是在某些场景下:如内存池,我们默认申请的空间不去堆而是去内存池,就需要new表达式(placement-new)
<code>class A
{
public:
A(int a = 1)
:_a(a)
{}
~A()
{}
private:
int _a = 1;
};
int main()
{
A* ptr1 = (A*)malloc(sizeof(A));
new(ptr1)A(1);
ptr1->~A();
free(ptr1);
A* ptr2 = (A*)operator new(sizeof(A));
new(ptr2)A(2);
ptr2->~A();
operator delete(ptr2);
return 0;
}
我们有时存在一块高频繁调用的内存块,为了更加高效我们手动从堆中申请一块内存池子只用于我们申请,这时候我们就需要使用前面的方法了不去调用operator new,而是其他的容器,因为operator new默认向堆中申请空间
5. 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来释放空间
malloc/free和new/delete的区别
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
1. malloc和free是函数,new和delete是操作符
2. malloc申请的空间不会初始化,new可以初始化
3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可
4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理释放
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。