1. C语言动态内存管理
1.1 C和C++内存分布
C和C++内存分布都是一样的。
栈区(stack)
栈又叫堆栈,非静态局部变量/函数参数/返回值等等,栈是向下增长的。
执行函数时,函数内部局部变量的存储单元都可以在栈上创建。
函数执行结束后这些存储单元会被自动释放。栈内存分配运算内置于处理器的指令集中,
拥有很高的效率,但是分配的内存容量是有限的。
栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
堆区(heap)
堆用于程序运行时动态内存分配,堆是可以上增长的。
一般由程序员自主分配释放,若程序员不主动不释放,程序结束时可能由操作系统回收。
其分配方式类似于链表。
数据段(data segment)
静态存储区,数据段存放全局变量和静态数据,程序结束后由系统释放。
代码段(code segment)
可执行的代码 / 只读常量。代码段存放类成员函数和全局函数的二进制代码。
一个程序起来之后,会把它的空间进行划分,而划分是为了更好地管理。
函数调用,函数里可能会有很多变量,函数调用建立栈帧,栈帧里存形参、局部变量等等。
内存映射段(memory mapping)(了解)
内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。
用户可使用系统接口创建共享共享内存,做进程间通信。
结合以前的知识观察下面一段代码,并回答问题:
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); }
1. 选择题:
选项 : A . 栈 B . 堆 C . 数据段 ( 静态区 ) D . 代码段 ( 常量区 )
globalVar 在哪里? ____ staticGlobalVar 在哪里? ____
staticVar 在哪里? ____ localVar 在哪里? ____
num1 在哪里? ____
char2 在哪里? ____ * char2 在哪里? ___
pChar3 在哪里? ____ * pChar3 在哪里? ____
ptr1 在哪里? ____ * ptr1 在哪里? ____
2. 填空题:
sizeof(num1) = ____;
sizeof(char2) = ____; strlen(char2) = ____;
sizeof(pChar3) = ____; strlen(pChar3) = ____;
sizeof(ptr1) = ____;
答案解析:
1.2 C语言中动态内存管理的方式
面试题1:malloc / calloc / realloc 的区别?
这三个函数我们以前学C语言已经学过了:
C语言进阶⑰(动态内存管理)四个动态内存函数+动态通讯录+柔性数组_GR C的博客-CSDN博客
相同点:
- 1.都是从堆上申请空间
- 2.都需要对返回值判空
- 3.都需要用户free释放
- 4.返回值类型相同,都是void*
- 5.都需要类型转化
- 6.底层实现上是一样的,都需要开辟多余的空间,用来维护申请的空间
不同点:
- 1.函数名字不同和参数类型不同。
- 2.calloc会对申请空间初始化,并且初始化为0,而其他两个不会。
- 3.malloc申请的空间必须使用memset初始化
4.realloc是对已经存在的空间进行调整,当第一个参数传入NULL的时候和malloc一样,调整分为两种情况:
① 调整的空间比原有空间大:
1.大了一点:要调整的多出来的空间小于原有空间后面空闲的空间,
做法:
1.1 直接延伸申请空间
1.2 返回原空间首地址**
2.大了很多:要调整的多出来的空间大于于原有空间后面空闲的空间,
做法:
2.1 重新开辟新空间
2.2 将旧空间的内容拷贝到新空间中
2.3 释放旧空间
2.4 返回新空间的首地址
- ② 调整的空间比原有空间小:
做法:
1.将原空间缩小
2 .返回旧空间首地址
面试题2:malloc的实现原理?(以下现在做简单了解)
从操作系统角度看,进程分配内存有两种方式,分别由两个系统调用完成:brk 和 mmap (不考虑共享内存)
brk 是将数据段(.data)的最高地址指针 _edata 往高地址推
mmap 是在进程的虚拟地址空间中(堆和栈中间,称为“文件映射区域”的地方)找一块空闲的虚拟内存
这两种方式分配的都是虚拟内存,并没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,会发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系、
情况一:malloc 小于 128K 的内存,使用 brk() 函数分配
情况二:malloc 大于 128K 的内存,使用 mmap 分配(munmap 释放)
一个拓展链接:【CTF】GLibc堆利用入门-机制介绍_哔哩哔哩_bilibili
2. C++动态内存管理方式
#include<iostream> using namespace std; int main() { int* p1 = (int*)malloc(sizeof(int)); int* p2 = (int*)malloc(sizeof(int) * 5); return 0; }
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦
(比如写链表的时候要多次申请空间),因此C++又提出了自己的内存管理方式:
通过new和delete操作符进行动态内存管理。
2.1 new/delete操作内置类型
new/delete 的用法:
#include<iostream> using namespace std; int main() { // 动态申请一个int类型的空间 int* p1 = new int; // 动态申请一个int类型的空间并初始化为10 int* p2 = new int(10); // 动态申请10个int类型的空间 int* p3 = new int[10]; //释放申请的空间,并置空 delete p1; p1 = nullptr; delete p2; p2 = nullptr; delete[] p3; p3 = nullptr; return 0; }
申请和释放单个元素的空间,使用 new 和 delete 操作符,
申请和释放连续的空间, 使用 new[ ] 和 delete[ ] ,注意:匹配起来使用。
是不是非常的方便,而且 new 不需要强制类型转换。
2.2 初始化new数组的问题
C++98 不支持初始化 new 数组:
int* p = new int[5];
但现在已经202X年了,C++11 允许大括号初始化,
我们就可以用 { } 列表初始化:(后面讲C++11还会讲这个语法)
int* p4 = new int[5] {1, 2}; // 1 2 0 0 0 int* p5 = new int[5] {1, 2, 3, 4, 5}; // 1 2 3 4 5
2.3 new 和 delete 操作自定义类型
malloc / free 和 new / delete 对于内置类型没有本质区别,
那么它存在的意义是什么呢?仅仅是因为用法更简洁吗?
巨佬为了这么点事设计出来?可以但没必要,看看作用:
#include<iostream> using namespace std; class A { public: A(int a = 0) : _a(a) { cout << "A():" << this << endl; } ~A() { cout << "~A():" << this << endl; } private: int _a; }; int main() { // new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间 //还会调用构造函数和析构函数 A* p1 = (A*)malloc(sizeof(A)); 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); A* p6 = new A[10]; free(p5); delete[] p6; return 0; }
new 不仅会开内存,还会调用对应的构造函数初始化,
相对的,free 只是把 p1 p2 指向的空间释放掉。
而 delete 不仅会释 p1 p2 指向的空间,delete 还会调用对应的析构函数。
new / delete 不仅仅是写法上变得简单了, 可以说就是为了自定义类型弄出来的。
(还有以后学的抛异常,下面讲operator new函数也简单的讲了一下)
new 对应的是 delete,可以可以 new 出来的用 free 释放?
不建议大家混着用, new 出来的用 free,有的编译器就会报错,
new[ ] 出来的 你去 delate 而不是 delete[ ] 也可能会报错。
总结:
在申请自定义类型的空间时,new 会调用构造函数,
delete 会调用析构函数,而 malloc 与 free 不会。
new:在堆上申请空间 + 调用构造函数输出。
delete:先调用指针类型的析构函数 + 释放空间给堆上。
匹配使用:malloc/free ,delete/delete ,new[ ] / delete[ ]
从C语言到C++⑨(第三章_C&C++内存管理)详解new和delete+面试题笔试题(中):https://developer.aliyun.com/article/1513662