
C/C++内存管理

1. C/C++内存分布
1.1 内存划分

🍁 栈__又叫__堆栈,非静态局部变量/函数参数/返回值等等,栈是向下增长的。栈一般是有规定大小,Linux下栈是8M
🍁 __内存映射段__是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
🍁 __堆__用于程序运行时动态内存分配,堆是可以上增长的。堆一般比较大,一般是2G的空间
🍁 数据段–存储全局数据和静态数据。
🍁 代码段–可执行的代码/只读常量。⚠️const变量不是在常量区
1.1.1 栈的演示
向下生长就是b的地址要比a的地址小
void f2()
{
int b = 0;
cout << "b:" << &b << endl;
}
void f1()
{
int a = 0;
cout <<"a:" <<&a << endl;
f2();
}
int main()
{
f1();
return 0;
}

1.1.2 堆的演示
int main()
{
int* p1 = (int*)malloc(4);
int* p2 = (int*)malloc(4);
cout << "p1:" << p1 << endl;
cout << "p2:" << p2 << endl;
free(p1);
}

1.2 🌰
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";
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);
}
🌿 选择题:
选项 : A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
| globalVar在哪里? | staticGlobalVar在哪里? | staticVar在哪里? | localVar在哪里? | num1 在哪里? | |
|---|---|---|---|---|---|
| C | C | C | A | A | |
| char2在哪里? | * char2在哪里? | pChar3在哪里? | * pChar3在哪里? | ptr1在哪里? | * ptr1在哪里? |
| A | A | A | D(地址在常量区) | A | B(只有*ptr1才会在堆上) |
🌿 填空题:(单位/字节)
| sizeof(num1) = ;sizeof(ptr1) = ; | 40 | 4 |
|---|---|---|
| sizeof(char2) = ; strlen(char2) = ; | 5 | 4 |
| sizeof(pChar3) = ; strlen(pChar3) = ; | 4/8 | 4 |
2. C语言动态内存管理
主要涉及malloc/calloc/realloc和free四个函数
void Test ()
{
int* p1 = (int*) malloc(sizeof(int));
free(p1);
// 1.malloc/calloc/realloc的区别是什么?
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
// 这里需要free(p2)吗?
free(p3 );
}
看看三个函数的区别

3. C++内存管理方式
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
3.1 针对内置类型
new/delete和malloc/free 针对内置类型没有任何差别,只是用法不一样
int main()
{
// 申请一个10个int的数组
int* p1 = (int*)malloc(sizeof(int) * 10);
int* p2 = new int[10];
free(p1);
delete[] p2;
// 申请单个int对象
int* p3 = (int*)malloc(sizeof(int));
int* p4 = new int;
free(p3);
delete p4;
return 0;
}
⚠️注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[]
3.2 针对自定义类型
struct ListNode
{
//struct ListNode* _next; //C
ListNode* _next;
ListNode* _prev;
int _val;
ListNode(int val = 0)
:_next(nullptr)
, _prev(nullptr)
, _val(val)
{
cout << "ListNode(int val = 0)" << endl;
}
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
// C languege malloc只是开空间
//free释放空间
struct ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));
free(n1);
// cpp new 针对自定义类型,开空间+构造函数初始化
// delete 针对自定义类型,析构函数清理 + 释放空间
ListNode* n2 = new ListNode(5); // -> 相当于c语言中BuyListNode(5)
delete n2;
}
如果new和delete类型不匹配,会主动提示报错

另外C++11有一个初始化写法
int* p2 = new int[4]{ 1, 2, 3, 4 };
小结:
在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会
4. operator new与operator delete
new和delete是用户进行动态内存申请和释放的操作符,operator new 和__operator delete__是__系统提供的
全局函数__,__new__在底层调用__operator new全局函数__来申请空间,__delete__在底层通过__operator delete全局
函数__来释放空间。
4.1 malloc和operator new的区别
用法跟malloc和free是完全一样的,功能都是在堆上申请释放空间
ListNode* p1 = (ListNode*)malloc(sizeof(ListNode));
free(p1);
ListNode* p2 = (ListNode*)operator new(sizeof(ListNode));
operator delete(p2);
失败了处理方式不一样,malloc失败返回NULL,operator new失败以后抛异常
//malloc
void* p3 = malloc(0xefffffff);
if (p3 == NULL){
cout << "malloc fail" << endl;
}
//new
try{
void* p4 = operator new(0xefffffff);
}
catch (exception& e){
cout << e.what() << endl;
}

也就是说当我们使用new时,发生了如下的封装的函数的调用

4.2 operator new与operator delete的类专属重载
针对链表的节点ListNode通过__重载类专属 operator new/ operator delete__,实现链表节点__使用内存池申请和释放内存,提高效率__。
struct ListNode
{
ListNode* _next;
ListNode* _prev;
int _data;
void* operator new(size_t n)
{
void* p = nullptr;
p = allocator<ListNode>().allocate(1);//STL中的空间配置器,也就是一个简单的内存池
cout << "memory pool allocate" << endl;
return p;
}
void operator delete(void* p)
{
allocator<ListNode>().deallocate((ListNode*)p, 1);
cout << "memory pool deallocate" << endl;
}
};
class List
{
public:
List()
{
_head = new ListNode;
_head->_next = _head;
_head->_prev = _head;
}
~List()
{
ListNode* cur = _head->_next;
while (cur != _head)
{
ListNode* next = cur->_next;
delete cur;
cur = next;
}
delete _head;
_head = nullptr;
}
private:
ListNode* _head;
};
如果对new类专属重载的话相当于不再是像系统申请一块空间,而是特定的ListNode域申请空间,类比于你受到压岁钱,重载相当于放到了银行里,本质虽然都是来源于长辈给的压岁钱,但是一个是全局的,一个是限定类专属的

5. new和delete的实现原理
5.1 内置类型
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。
5.2 自定义类型
🍁 new的原理
🌿调用operator new函数申请空间
🌿在申请的空间上执行构造函数,完成对象的构造
🍁 delete的原理
🌿在空间上执行析构函数,完成对象中资源的清理工作
🌿调用operator delete函数释放对象的空间
🍁 new T[N]的原理
🌿调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
🌿在申请的空间上执行N次构造函数
🍁 delete[]的原理
🌿在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
🌿调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
6. 定位new表达式(placement-new)
该表达式想要解决的问题是__在已分配的原始内存空间中调用构造函数初始化一个对象__
6.1 使用方法
new (place_address) type / new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
6.2 使用示例(使用情况不多)
// 等价于直接用A* p = new A
A* p = (A*)operator new(sizeof(A));
new(p)A; // new(p)A(3); // 定位new,placement-new,显示调用构造函数初始化这块对象空间
// 等于 delete p
p->~A(); // 析构函数可以显示调用,构造函数不可以
operator delete(p);
7. 一波🌰即将来袭
7.1 🌰malloc/free和new/delete的区别
7.1.1共同点
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。
7.1.2 不同点
🍁 malloc和free是__函数__,new和delete是__操作符__
🍁 malloc申请的空间不会初始化,new__可以初始化__
🍁 malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可
🍁 malloc的返回值为void*, 在__使用时必须强转__,new不需要,因为new后跟的是空间的类型
🍁 malloc申请空间失败时,返回的是NULL,因此__使用时必须判空__,new不需要,但是__new需要捕获异常__
🍁 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后__会调用构造函数完成对象的初始化__,delete在释放空间前会调用析构函数完成空间中资源的清理
7.2 🌰🌰内存泄漏
内存泄漏是指针丢了还是内存丢了?
答:指针丢了,申请完空间用了之后不归还,典型的占着茅坑不拉屎
void MemoryLeaks()
{
// 1.内存申请了忘记释放
int* p1 = (int*)malloc(sizeof(int));
int* p2 = new int;
// 2.异常安全问题
int* p3 = new int[10];
Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
delete[] p3;
}
7.2.1 什么是内存泄漏
🌸 内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
7.2.2 内存泄漏的危害
🌸 长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
有些股票交易软件如果内存泄漏严重,突然卡死,会导致出现金融事故
7.2.3 如何避免内存泄漏
🍁 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。但是这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。
🍁 采用RAII思想或者智能指针来管理资源。
🍁 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
🍁 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。
小结:内存泄漏非常常见,解决方案分为两种:
🍁 事前预防型。如智能指针等。
🍁 事后查错型。如泄漏检测工具。
C/C++内存管理有关知识点告一段落,干净又卫生兄弟们
本文深入探讨了C/C++内存管理,包括内存划分(栈、堆、数据段、代码段)、动态内存管理(malloc、calloc、realloc、free与new、delete的区别)、自定义类型内存管理、operatornew与operatordelete的作用,以及定位new表达式的应用。特别强调了内存泄漏的危害及避免策略,提供了实例解析malloc/free与new/delete的差异,并讨论了智能指针在防止内存泄漏中的作用。


5745

被折叠的 条评论
为什么被折叠?



