C++:动态内存管理(含五大内存分区区分)详解
目录
一.内存五大分区图解
1.五大分区:
2.栈和堆的区别与共通:
二.C的基础动态内存管理函数(深入理解)
1.malloc和calloc在对内存初始化分配上的区别:
2.三种函数返回值的可变性:
3.realloc分配内存空间的不稳定性:
三.C++的动态内存管理
1.C++相较C的三大基本优势:
2.C++最重要的操作符引入(new和delete):
(1)new和·delete操作内置类型和自定义类型:
(2)new和delete的底层介绍:
一.内存五大分区图解
1.五大分区:
简单来说,五大分区粗略来分是这样的:
其中,对数据段还可以再进行区分·:
其中:
未初始化数据区(BSS)
加载的是可执行文件 BSS 段,位置可以分开也可以紧靠数据段,存储于数据段的数据(全局未初始化,静态未初始化数据)的生存周期是整个程序运行过程
全局初始化数据区/静态数据区(data segment)
加载的是可执行文件数据段,存储于数据段(全局初始化,静态初始化数据,文字常量(只读))的数据的生存周期是整个程序运行过程
2.栈和堆的区别与共通:
首先栈和堆的基本区别就在下图:
总结来说:
其次,对于栈和堆还有一点重要的共同点:
堆区和栈区都是在程序运行之后产生的
程序在加载到内存前,代码区和全局区(data+ bss)的大小是固定的,程序运行期间不变。然后可执行程序,操作系统把物理硬盘程序加载到内存,除根据可执行程序的信息分出代码区(text)数据区(data)和未初始化数据区(bss)之外,还额外增加了栈区、堆区
二.C的基础动态内存管理函数(深入理解)
void Test() { int* p1 = (int*)malloc(sizeof(int)); free(p1); //malloc:在内存的动态存储区中分配一块长度为size字节的连续区域,参数size为需要内存空间的长度,返回该区域的首地址 int* p2 = (int*)calloc(4, sizeof(int)); free(p2); //与malloc相似,不过函数calloc() 会将所分配的内存空间中的每一位都初始化为零 int* p3 = (int*)realloc(p2, sizeof(int) * 10); free(p3); //realloc: 给一个已经分配了地址的指针重新分配空间,可以做到对动态开辟内存大小的调整 }
1.malloc和calloc在对内存初始化分配上的区别:
函数malloc不能初始化所分配的内存空间,而函数calloc能
如果由malloc函数分配的内存空间原来没有被使用过,则其中的每一位可能都是0;反之, 如果这部分内存曾经被分配过,则其中可能遗留有各种各样的数据.也就是说,使用malloc函数的程序开始时(内存空间还没有被重新分配)能正常进行,但经过一段时间(内存空间还已经被重新分配)可能会出现问题.
函数calloc() 会将所分配的内存空间中的每一位都初始化为零
也就是说,如果你是为字符类型或整数类型的元素分配内存,那么这些元素将保证会被初始化为0;如果你是为指针类型的元素分配内存,那么这些元素通常会被初始化为空指针
2.三种函数返回值的可变性:
函数malloc向系统申请分配指定size个字节的内存空间.返回类型是 void类型.void表示未确定类型的指针.C/C++规定,void* 类型可以强制转换为任何其它类型的指针
3.realloc分配内存空间的不稳定性:
realloc可以对给定的指针所指的空间进行扩大或者缩小,无论是扩张或是缩小,原有内存的中内容将保持不变.当然,对于缩小,则被缩小的那一部分的内容会丢失,(不稳定因素其一),realloc并不保证调整后的内存空间和原来的内存空间保持同一内存地址(不稳定因素其二).即realloc返回的指针很可能指向一个新的地址.realloc是从堆上分配内存的.当扩大一块内存空间时,realloc试图直接从堆上现存的数据后面的那些字节中获得附加的字节,如果能够满足,此时即原地扩;如果数据后面的字节不够,那么就使用堆上第一个有足够大小的自由块,现存的数据然后就被拷贝至新的位置,而老块则放回到堆上.这句话传递的一个重要的信息就是数据可能被移动,即异地扩
三.C++的动态内存管理
1.C++相较C的三大基本优势:
(构造与析构,类型转换的安全,错误处理)
/1.构造函数和析构函数 //C语言: //分配的内存需手动初始化(如 `memset`),释放时无自动清理逻辑 int* p = (int*)malloc(sizeof(int)); *p = 10; // 手动赋值 free(p); //C++: //new会调用对象的构造函数,delete调用析构函数,确保资源(如文件句柄、动态内存)自动释放。 class MyClass { public: MyClass() { /* 构造 */ } ~MyClass() { /* 析构 */ } }; int main() { MyClass* obj = new MyClass(); // 自动调用构造函数 delete obj; // 自动调用析构函数 } 2.类型安全// // C语言: //使用 void* 指针,类型转换易出错。 int* p = (int*)malloc(sizeof(int) * 10); // 需显式转换 //C++: //new 返回精确类型指针,避免类型错误。 int* p = new int(10); // 类型安全,无需转换 ///3.错误处理/ //C语言: //malloc 返回 NULL 表示失败,需手动检查。 void* ptr = malloc(...); if (ptr == NULL) { /* 处理错误 */ } //C++: //默认情况下 `new` 在失败时抛出 `std::bad_alloc` 异常,也可用 `nothrow` 版本: int* p = new (std::nothrow) int10; // 返回 nullptr 而非异常
2.C++最重要的操作符引入(new和delete):
(1)new和·delete操作内置类型和自定义类型:
class List
{
public:
struct List* _next;
int _val;
//构造函数
List(int val = 0)
:_next(nullptr)
, _val(val)
{
}
~List()
{
;
}
};
int main()
{
List* n2 = new List(10); //自动调用构造函数,程序结束自动析构
return 0;
}
//总结:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会,new和malloc还有一个区别就是在申请内存失败时的处理情况不同,malloc如若开辟内存失败,会返回空指针,但是new失败会抛异常
(2)new和delete的底层介绍:
new的原理
调用operator new函数申请空间\n在申请的空间上执行构造函数,完成对象的构造
该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。operator new本质是封装了malloc(operator delete本质是封装了free),operator new和operator delete的功能和malloc、free一样,也不会去调用构造函数和析构函数,不过还是有区别的:
1、operator new不需要检查开辟空间的合法性
2、operator new开辟空间失败就抛异常
delete的原理
在空间上执行析构函数,完成对象中资源的清理工作,调用operator delete函数释放对象的空间
new T[N]的原理
调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请\n在申请的空间上执行N次构造函数
delete[ ]的原理
在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理\n调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
欧克,关于C++动态内存管理的基础知识就到此为止了,
那就这样吧,
全文终