【本节目标】
- 1. C/C++内存分布
- 2. C语言中动态内存管理方式
- 3. C++中动态内存管理
- 4. operator new与operator delete函数
- 5. new和delete的实现原理
- 6. 定位new表达式(placement-new)
- 7. 常见面试题
1. C/C++内存分布
我们先来看一下内存分布图。
【说明】:内存划分的意义:不同的数据,有不同的存储需求,各个区域满足不同的需求。
- 1. 栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
- 2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
- 3. 堆用于程序运行时动态内存分配,堆是可以上增长的,应用较多的场景:数据结构
- 4. 数据段--存储全局数据和静态数据。
- 5. 代码段--可执行的代码(经过链接形成的二进制文件)/只读常量,该区域不可被修改。我们平时在vs上写的代码是存在磁盘上的。
- 强制修改也不行
我们先来看下面的一段代码和相关问题。
#include <iostream> using namespace std; 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); }
解析:
答案:
静态变量和全局变量有什么区别?
- 作用域:
- 静态变量的作用域限定在声明它的函数内部,即它只在包含它的函数中可见。
- 全局变量的作用域是整个文件,可以在文件中的任何地方访问。
- 生命周期:
- 静态变量在程序运行期间一直存在,不会因为函数的调用结束而被销毁。它在第一次进入声明它的函数时初始化,然后在程序生命周期内保持其值。
- 全局变量也在程序运行期间一直存在,它在程序启动时初始化,直到程序结束。
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); }
- malloc(Memory Allocation):
- 函数原型:void* malloc(size_t size);
- 用途:用于分配指定大小的内存块,返回一个指向该内存块起始地址的指针。
- 行为:分配的内存块中的初始内容是未定义的,可能包含任意值。需要注意,malloc 不会初始化分配的内存。
- calloc(Contiguous Allocation):
- 函数原型:void* calloc(size_t num_elements, size_t element_size);
- 用途:用于分配一块指定数量和大小的连续内存块,返回一个指向该内存块起始地址的指针。
- 行为:分配的内存块中的每个字节都会被初始化为零。相比于 malloc,calloc 提供了初始化内存的功能,适用于需要确保分配内存中的所有位都为零的情况。
- realloc(Reallocate):
- 函数原型:void* realloc(void* ptr, size_t size);
- 用途:用于更改之前分配的内存块的大小,可以扩大或缩小。
- 行为:realloc 可以通过改变原有内存块的大小来满足新的需求。如果扩大内存块,新分配的内存区域的内容是未定义的,而原来的部分会保持不变。如果缩小内存块,多余的部分将会被释放。如果 ptr 是 NULL,realloc 的行为就相当于 malloc(size)。
malloc的实现原理?链接
3. C++内存管理方式
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因 此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
3.1 new/delete操作内置类型
void Test() { // 动态申请一个int类型的空间 int* ptr1 = new int; // 动态申请一个int类型的空间并初始化为10 int* ptr2 = new int(10); // 动态申请3个int类型的空间 int* ptr3 = new int[3]; // 动态申请3个int类型的空间并初始化为1,2,3 int* ptr4 = new int[3] {1, 2, 3}; // 动态申请5个int类型的空间并初始化为1,2,3 int* ptr5 = new int[5] {1, 2, 3}; delete ptr1; delete ptr2; delete[] ptr3; delete[] ptr4; delete[] ptr5; }
运行结果:
注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用 new[]和delete[],注意:匹配起来使用。
3.2 new和delete操作自定义类型
class A { public: A(int a = 0) : _a(a) { cout << "A():" << this << endl; } ~A() { cout << "~A():" << this << endl; } private: int _a; }; int main() { // malloc不方便解决动态申请的自定义类型对象的初始化问题 // new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间\ 还会调用构造函数和析构函数 // new的本质:开空间+调用构造函数初始化 // delete的本质:调用析构函数+释放空间 A* p1 = (A*)malloc(sizeof(A)); //p1->~A(1);//error C2521: 析构函数 不带任何参数 A* p2 = new A(1); free(p1); delete p2; // 内置类型是几乎是一样的 int* p3 = (int*)malloc(sizeof(int)); // C int* p4 = new int; free(p3); delete p4; // 多个对象 A* p5 = (A*)malloc(sizeof(A) * 10); free(p5); A aa1(1); A aa2(2); A aa3(3); A* p6 = new A[10]{ aa1, aa2, aa3 };//有名对象 A* p7 = new A[10]{ A(1),A(2),A(3) };//匿名对象 A* p8 = new A[10]{ 1 , 2, 3 };//隐式类型转换 delete[] p6; delete[] p7; delete[] p8; return 0; }
注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与 free不会。同时我们这里还要注意,malloc开辟失败会返回空指针,而new开辟失败会抛异常。
【探讨C++内存管理:有效避免内存泄漏与提高性能的关键】(下):https://developer.aliyun.com/article/1425600