C++ 内存管理
希望文章能对你有所帮助,有不足的地方请在评论区留言指正,一起交流学习!
目录
1.C/C++内存分布
2.C语言中动态内存管理方式
3.C++内存管理方式
3.1.内置类型
3.2.自定义类型
3.3.new和delete的底层
(1) 调用operator new与operator delete函数
(2)operator new与operator delete函数是什么
(3)new和malloc的关系,以及delete和free的关系
3.4.定位new表达式(placement-new)
4. malloc/free和new/delete的区别
1.C/C++内存分布
C/C++数据分为局部数据、全局数据和静态数据、常量数据、动态申请的数据四种。分别存储在栈(自上向下,向低地址方向生长)、静态区、常量区、堆(自下向上、向高地址方向生长)。
栈可以存储非静态局部变量、函数返回值、函数参数;堆用于程序运行时内存动态分配,数据段又叫做静态区存储全局数据和静态数据,常量区又叫做代码段可以存储可执行的代码和常量;
内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(还没见过)
内存的分配时基于程序编写的需求;我们所编写的程序存储在磁盘上的,当其运行起来本质是以进程的角度运行的;上述描述举实程序运行之后,操作系统分配给进程的虚拟进程地址空间。
2.C语言中动态内存管理方式
C语言使用动态内存函数开辟、扩展、初始化开辟的堆空间。之间的文章有内存管理函数的详细介绍,不了解的可以看看这篇博客 C语言笔记(动态空间的内存管理)-CSDN博客 。
在这个主要介绍三个函数malloc、calloc以及realloc之间的相同和不同。
相同点:
- 三者均用于在程序运行时从堆(Heap)中动态分配内存,且分配的内存需手动通过
free()
释放,否则会导致内存泄漏。- 成功时均返回
void*
指针,需根据实际使用场景强制转换为所需类型;失败时返回NULL
。- 分配的内存均位于堆区。
不同点:
- malloc函数分配一块连续的、未初始化的内存空间。
- calloc分配并初始化为零的内存空间;
- realloc调整已分配内存块的大小,可能涉及内存搬迁。
realloc分为异地扩容和原地扩容,当内存空间开辟成功的条件下,异地情况下,原来的内存空间销毁。但是使用realloc函数扩容的情况下,切勿将接受参数和实际参数相同,以防扩容失败导致内存泄漏。
4.当然参数也是不一样的;
演示一下
int main()
{int* ptr1 = (int*)malloc(3 * sizeof(int));// 未初始化// 需要额外的初始化ptr1[0] = 10;ptr1[1] = 20;ptr1[2] = 30;//calloc直接初始化为0int* ptr2 = (int*)calloc(3, sizeof(int));//realloc 在原本的大小上扩容int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 6);free(ptr1);free(ptr3); //ptr2不用释放,因为,ptr2已经被释放或者覆盖。ptr1 = nullptr;ptr2 = nullptr;ptr3 = nullptr;return 0;}
3.C++内存管理方式
C++中的内存管理主要依靠new和delete操作符进行内存管理,操作符的相比C语言的函数来说就是简洁,书写简单。
我将new和malloc函数进行对比来说明new以及delete是如何使用的。
3.1.内置类型
int main()
{//内置类型 // 开辟一个整型的空间,未初始化,并且其返回值为void* 类型,需要强制转换int* ptr1 = (int*)malloc(sizeof(int));free(ptr1);// 和上述malloc函数一样,同样没有初始化,但是new是根据后面的类型返回相同类型的指针的。int* ptr2 = new int;delete ptr2;// new的初始化int* ptr3 = new int(45);delete ptr3;// 上述都是开辟单个数,new开辟数组int* ptr4 = new int[5];delete[] ptr4;// 开辟数组并初始化 当初始化一部分的时候,未初始化部分按照0进行初始化int* ptr5 = new int[5]{0,1};delete[] ptr5;return 0;
}
大家可以自己调试一下,在内置类型上的相同和不同
相同 :malloc和new都是在堆上开辟了一块空间,一样的是二者的返回值都需要指针来接收。
不同:new可以在开辟空间之后初始化。
3.2.自定义类型
下述代码是new对于自定义类型的使用。
class B
{
public://构造函数B(int b = 4) :_b(b){_count++;cout << "Init" << ":" << _count << endl;}// 拷贝构造函数B(const B& b){_b = b._b;cout << "Copy" << endl;}void Print(){cout << _b << endl;}// 析构函数~B(){_count--;cout << "Destroy" << endl;_b = 0;}private:int _b;static int _count;
};
int B::_count = 0;
int main()
{//只是开辟了一块承载B的空间B* ptr1 = (B*)malloc(sizeof(B));free(ptr1);//开辟空间的同时会调用类初始化函数B* ptr2 = new B(45);ptr2->Print();//访问方式和结构体一样,在C++中二者大致一样,区别仅在于默认访问控制//消除对象的同时会调用析构函数delete ptr2;//new还可以创建多个对象//B* ptr3 = new B[4]{0,1,2,3};//这里调用的是构造函数B* ptr3 = new B[4]{ B(0),B(1),B(2),B(3)};//这里是基于匿名对象调用的拷贝构造函数,匿名对象具有常性,因此在拷贝//构造的参数处需要加上 const ,但是由于编译器的优化这里简化为一次的构造的调用,但是const不可以省去//ptr3->Print();//ptr3[0].Print();ptr3[1].Print();// ptr0 和ptr3是一样的ptr3[2].Print();ptr3[3].Print();ptr3[4].Print();//按照数组的访问来,此访问是越界了delete[] ptr3; // 在删除对象的同时会调用析构函数return 0;
}
这里需要说明就是,new创建对象,会调用构造函数,delete删除对象会调用析构函数。
3.3.new和delete的底层
(1) 调用operator new与operator delete函数
operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
int main()
{int* ptr1 = new int(46);delete ptr1;return 0;
}
在执行new和delete的时候会分别调用operator new以及operator detele。
(2)operator new与operator delete函数是什么
在C语言中,开辟内存空间失败的情况,我们习惯于返回空指针NULL;然后使用空指针检测开辟空间是否成功,C语言中失败了会产生错误编号,反馈出错误的原因。
在C++中,在开辟空间失败之后,一般使用抛异常,直接输出错误原因 。
为了简化程序的设计,operator new与operator delete出现了
operator new:是为对象分配原始内存空间(不涉及对象构造),并在分配失败时提供特定的错误处理机制。将开辟空间和开辟异常封装一起。
operator delete:回收原始内存空间(不涉及对象析构),并在必要时将内存归还给操作系统。为了适配new以及自定义数据专门将free进行的包装。
/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,
尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{// try to allocate size bytesvoid *p;while ((p = malloc(size)) == 0)if (_callnewh(size) == 0){// report no memory// 如果申请内存失败了,这里会抛出bad_alloc 类型异常static const std::bad_alloc nomem;_RAISE(nomem);}return (p);
}
/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void *pUserData)
{_CrtMemBlockHeader * pHead;RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));if (pUserData == NULL)return;_mlock(_HEAP_LOCK); /* block other threads */__TRY/* get a pointer to memory block header */pHead = pHdr(pUserData);/* verify block type */_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));_free_dbg(pUserData, pHead->nBlockUse);__FINALLY_munlock(_HEAP_LOCK); /* release other threads */__END_TRY_FINALLYreturn;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
C语言的输出异常:
#include <stdio.h>
#include <errno.h>
int main() {int* ptr1 = nullptr;do{ptr1 = (int*)malloc(1024 * 1024);cout << ptr1 << endl;} while (ptr1);if (ptr1 == NULL) {// 自动输出错误信息(格式:[自定义前缀]: [错误描述])perror("malloc failed ");}return 0;
}
当C语言中出现错误的时候,会生成对用的编号,perror函数会将编号转换为字符串输出。
C++的抛出异常并捕获:
int main()
{try{int* ptr1 = nullptr;do{ptr1 = (int*)operator new(1024 * 1024);// 使用方式和malloc 一样,是对malloc的封装cout << ptr1 << endl;} while (ptr1);}catch (const exception& e){cout << e.what() << endl;}return 0;
}
operator new 会抛出异常 ,try catch会捕获范围内所有错误,包括期间调用的函数出现的错误。
(3)new和malloc的关系,以及delete和free的关系
new和malloc的关系
对于内置类型看不出什么不同;但是对于自定义类型中,new分为两部分一个是operator new函数以及构造函数,而detele函数会调用operator detele函数和析构函数。
上一小节已经说明了operator new函数以及operator detele 函数的构成,不做赘述。
实例
3.4.定位new表达式(placement-new)
在 C++ 中,定位 new
(Placement New) 是一种特殊的内存分配语法,允许在已分配的内存块上直接构造对象,而无需重新分配内存。它的核心作用是分离内存分配和对象构造,常用于高性能场景(如内存池、嵌入式系统)或特殊内存位置(如共享内存、硬件缓冲区)。
#include <new> // 必须包含此头文件// 在已分配的内存地址上构造对象
void* raw_memory = /* 已分配的内存块 */;
T* obj = new (raw_memory) T(args...); // T 是类型,args... 是构造函数参数
#include <iostream>
using namespace std;class B
{
public://构造函数B(int b = 4) :_b(b){_count++;cout << "Init" << ":" << _count << endl;}// 拷贝构造函数B(const B& b){_b = b._b;cout << "Copy" << endl;}void Print() const{cout << _b << endl;}// 析构函数~B() {_count--;cout << "Destroy" << endl;_b = 0;}private:int _b;static int _count;
};
int B::_count = 0;int main()
{void* ptr1 = malloc(sizeof(B));B* obj = new(ptr1)B(5);obj->Print();obj->~B();
池化技术 提高效率;内存池、线程池、连接池……
4. malloc/free和new/delete的区别
- malloc和free是函数,new和delete是操作符
- malloc申请的空间不会初始化,new可以初始化
- malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, 如果是多个对象,[]中指定对象个数即可
- malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
- 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。