【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;

}

 

 

创建一个链表: 

<code>// 使用new来创建一个链表

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在释放空间前会调用析构函数完成空间中资源的清理释放

 



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。