文章目录
【写在前面】
在 C 语言中我们也学过内存管理,可转至动态内存管理那些事:malloc、calloc、realloc、free
C/C++ 的内存管理跟 JAVA 这些语言是不同的 —— JAVA 的程序不是直接跑在操作系统上的,JAVA 是在 JVM 虚拟机上运行的;C/C++ 的程序是直接跑在 OS 上的,这也是为什么我们学习 C/C++ 要学习内存管理的原因,所以 C/C++ 的学习者需要对系统了解的更深,而对于系统的知识更多的会在 Linux 系统编程的阶段去学习。
这里我们会学习 new/delete 的使用,以及 new/delete 的底层原理。
一、C/C++内存分布
💦 填空题 && 选择题
int globalVar = 1; static int staticGlobalVar = 1; void Test() { static int staticVar = 1; int localVar = 1; const int localVal1 = 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 存储于栈
- localVar1 存储于栈
- num1 存储于栈
- char2 存储于栈
- *char2 存储于栈
- pChar3 存储于栈
- *pChar3 存储于代码段
- ptr1 存储于栈
- *ptr1 存储于堆
🔑 填空:
- sizeof(num1) = 40
- sizeof(char2) = 5
- sizeof(pChar3) = 4/8
- sizeof(ptr1) = 4/8
- strlen(char2) = 4
- strlen(pChar3) = 4
📝说明
注意这里比较容易出错的是:
💦 C/C++内存分布示意图
📝说明
栈 ❓
栈又叫堆栈,注意区分数据结构中的栈
函数调用建立栈帧,函数中的参数、局部变量都存在栈帧中
栈是向下增长的,比如 main 函数调用 f 函数:
堆 ❓
注意区分数据结构中的堆
malloc、calloc、realloc 都会在堆上开辟空间
堆是向上增长的,理论上后 malloc 的内存地址比先 malloc 的要大,但是也不一定,因为有可能下一次申请的是之前别人释放回来的:
内存映射段 ❓
内存映射段是高效的 I/O 映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程间通信。
虚拟内存 | 物理内存 ❓
虚拟内存跟物理内存要按页映射,就是对应起来。程序访问虚拟内存,实际要转到对应的物理内存。
内存区域特点 ❗
- 这几个区域堆是很大的 —— 你可以认为如果在 32 位下虚拟内存总共占 4G ,内核占 1G,剩下的 3G 空间大部分都是堆的
- 从图看,栈和堆差不多大,实际上栈很小 —— Linux下一般只有 8M,所以递归深度太深,很容易导致栈溢出 (Stack overflow)
- 数据段和代码段也不是很大 —— 因为没有多少数据 (全局数据 + 静态数据)
🍳拓展
对于内存划分、虚拟内存和物理内存映射可以去看:
《深入理解计算机系统》-> 虚拟存储器
《程序员的自我修养》-> 编译链接、动态库等
二、C语言中动态内存管理方式
💦 malloc/calloc/realloc和free
p2 calloc一块空间后,p3又realloc p2,要对p2 free吗 ❓
void Test () { int* p1 = (int*) malloc(sizeof(int)); free(p1); int* p2 = (int*)calloc(4, sizeof (int)); int* p3 = (int*)realloc(p2, sizeof(int)*10); free(p3); }
📝说明
这里涉及了 realloc 的开辟原理
所以对于上面程序的写法并不好:
【面试题】malloc/calloc/realloc的区别
malloc -> 开空间
calloc 等价于 malloc + memset(0) -> 开空间 + 初始化
realloc 单独使用时能实现 malloc 的效果 (不会初始化) -> 开空间 | 对 malloc/calloc 的空间扩容
三、C++内存管理方式
C 语言内存管理方式在 C++ 中可以继续使用,但有些地方就无能为力而且使用起来比较麻烦,因此 C++ 又提出
了自己的内存管理方式:通过 new 和 delete 操作符进行动态内存管理。
💦 new/delete操作内置类型
int main() { //库函数 int* p1 = (int*)malloc(sizeof(int)); free(p1); //操作符/关键字 int* p2 = new int; delete p2; return 0; }
📝说明
malloc/free 和 new/delete 有什么区别 ❓
- 如果动态申请的对象是内置类型,那么 malloc 和 free 没有区别
- 如果动态申请的对象是自定义类型,那么 malloc 和 free 有区别
💦 new和delete操作自定义类型
class A { public: A(int a = 0/*int b = 0*/) :_a(a) { cout << "A()" << endl; } ~A() { cout << "~A()" << endl; } private: int _a; }; int main() { A* p3 = (A*)malloc(sizeof(A)); free(p3); A* p4 = new A; //A* p4 = new A(10); //A* p4 = new A(10, 20); delete p4; //数组 int* p5 = (int*)malloc(sizeof(int) * 10); free(p5); int* p6 = new int[10]; delete[]p6; A* p7 = new A[10];//调用10次构造 delete[]p7;//调用10次析构 return 0; }
📝说明
- 对于内置类型 malloc/free 仅仅会开空间/释放空间
- 对于自定义类型 new/delete 不仅仅会开空间/释放空间,还会调用构造函数和析构函数 —— 调用构造函数时还可以传参,且可以传多个参数
注意我们在 new A 类时不需要默认构造函数;但是在 new A[10] 时则需要默认构造函数
在 C++ 中建议尽量使用 new/delete,因为 malloc/free 能做到的,new/delete 也能做到;new/delete 能做到的,malloc/free 不一定能做到。
注意申请和释放单个元素的空间,使用 new 和 delete 操作符,申请和释放连续的空间,使用 new[] 和 delete[]
这种特性有什么用 ❓
struct ListNode { int _val; ListNode* _next; ListNode(int val) : _val(val) , _next(nullptr) {} }; int main() { //C ListNode* n1 = (ListNode*)malloc(sizeof(ListNode)); n1->_val = 1; n1->_next = nullptr; //C++ ListNode* n2 = new ListNode(1); return 0; }
牛角尖问题 ❓
int* p1 = (int*)malloc(sizeof(int) * 10); free(p1); delete p1; int* p2 = new int; delete p2; free(p2); int* p2 = new int[10]; delete[]p2; delete[10]p2; delete p2;//err free p2;/err
📝说明
注意,一定要匹配使用:malloc ↔ free 、new ↔ delete、new 类型[] ↔ delete[]类型,否则可能会崩溃。