🔥博客主页:小王又困了
📚系列专栏:C++
🌟人之为学,不日近则日退
❤️感谢大家点赞👍收藏⭐评论✍️
目录
一、C/C++内存分布
二、内存管理方式
📒2.2C++内存管理方式
🎀2.2.1new/delete操作内置类型
🎀2.2.1new和delete操作自定义类型
三、operator new与operator delete函数
四、new和delete的实现原理
📒4.1内置类型
📒4.2自定义类型
五、定位new表达式(placement-new)
📒5.1概念
📒5.2使用格式
📒5.3使用场景
六、malloc/free和new/delete的区别
七、内存泄漏
📒7.1什么是内存泄漏
📒7.2内存泄漏的危害
📒7.3内存泄漏分类
📒7.4检测内存泄漏
📒7.5避免内存泄漏
一、C/C++内存分布
在C和C++中,内存分布主要包括以下几个部分:
代码段(Code Segment):
- 包含程序的机器代码,也称为文本段。
- 是只读的,存储程序的执行代码。
- 在程序运行时不可修改。
数据段(Data Segment):
- 包括初始化的全局变量和静态变量。
- 在程序开始运行时分配,并在整个程序的执行周期内保持不变。
- 数据区(Data Section): 存储已经初始化的全局变量和静态变量。
- BSS段(Block Started by Symbol): 存储未初始化的全局变量和静态变量。
堆(Heap):
- 由程序员分配和释放的内存区域。
- 动态分配的内存(例如使用new和malloc函数)存储在这里。
- 堆的大小在程序运行时可以动态调整。
栈(Stack):
- 存储函数的局部变量和函数调用的上下文信息。
- 由编译器自动分配和释放。
- 栈的大小在程序运行时是固定的。
堆栈区域之间的空间(Heap-Stack Gap):
- 用于防止堆和栈之间的冲突。
- 可以防止堆溢出覆盖栈数据或栈溢出覆盖堆数据。
小Tips:这些区域在内存中的布局可能因操作系统和编译器而异,但通常遵循相似的结构。在运行时,栈的地址通常位于较高的内存地址,而堆的地址通常位于较低的内存地址。代码段和数据段的位置取决于可执行文件的加载方式,通常在较高的内存地址。
二、内存管理方式
📒2.1C语言内存管理方式
C语言提供了一些基本的内存管理函数和操作符,使程序能够有效地分配和释放内存。以下是C语言中常用的内存管理方式:
📝malloc() 和 free():
- malloc()函数用于动态分配指定大小的内存空间。
- free()函数用于释放先前由malloc()分配的内存空间。
- 例子:
// 分配10个整数大小的内存空间 int *ptr = (int *)malloc(10 * sizeof(int)); if (ptr != NULL) { // 使用内存 // ... // 释放内存 free(ptr); }
📝 calloc() 和 realloc():
- calloc()函数用于分配指定数量和大小的内存空间,并将其初始化为零。
- realloc()函数用于更改之前分配的内存块的大小。
- 例子:
// 分配并初始化为0,10个整数大小的内存空间 int *ptr = (int *)calloc(10, sizeof(int)); if (ptr != NULL) { // 使用内存 // ... // 重新分配内存空间为20个整数大小 ptr = (int *)realloc(ptr, 20 * sizeof(int)); // 使用重新分配后的内存 // ... // 释放内存 free(ptr); }
📝静态分配:
- 静态分配是在编译时为变量分配内存空间。
- 包括全局变量、静态变量和局部变量(在函数外部定义的变量是静态的)。
- 内存分配和释放由编译器自动处理。
📝自动变量:
- 在函数内部定义的变量通常是自动变量。
- 自动变量的内存分配和释放由程序的执行控制流决定。
📝指针和动态内存分配:
- 使用指针进行内存分配和释放是C语言中动态内存管理的重要部分。
- 必须小心管理分配的内存,以避免内存泄漏和悬空指针问题。
小Tips:C语言中的内存管理是相对低级的,程序员需要手动分配和释放内存。不正确的内存管理可能导致内存泄漏、悬空指针或者堆栈溢出等问题。因此,编写健壮和高效的代码需要仔细管理内存的分配和释放。
📒2.2C++内存管理方式
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
🎀2.2.1new/delete操作内置类型
void Test() { // 动态申请一个int类型的空间 int* ptr1 = new int; // 动态申请一个int类型的空间并初始化为10 int* ptr2 = new int(10); // 动态申请10个int类型的空间,不初始化 int* ptr3 = new int[10]; // 动态申请10个int类型的空间,并初始化前三个 int* ptr4 = new int[10]{1,2,3}; delete ptr1; delete ptr2; delete[] ptr3; delete[] ptr4; }
小Tips:用new开辟空间是不会进行初始化的。如果我们想动态申请并初始化,类型后面要跟圆括号 () ,动态申请多个连续空间,类型后面跟的是方括号 [ ] ,如果要对这多个连续的空间初始化,可以在 [ ] 的后面跟 { } , { } 里面是初始化的数据,初始化的值不够,后面会默认初始化为0。申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[ ]和delete[ ]。
🎀2.2.1new和delete操作自定义类型
class A { public: A(int a = 0) : _a(a) { cout