内存管理基础
C++中可以访问的内存,通常可以分为三类:静态常量区、调用栈和堆。通常编译器使用三块独立内存:一块用于静态变量(可能再细分),一块用于自动变量,另一块用于动态存储。
静态变量区
这一块内存用于静态变量。静态变量区是编译器在编译期间就已经确定好的数据。例如在全局范围内声明const char text[] = "Hello World!"
,字符串text就会被存放在静态变量区。
特点
- 只读;
- 大小固定;
- 无法管理;
- 可以利用其特性在逻辑上做一些特定的优化。
调用栈
这一块内存用于自动变量。
特点
- 被动分配,被动回收;
- 大小在运行时有一个动态调整的过程;
- 大小有系统限制;
- 底大顶小。
问题
- 为什么变量有作用域?
防止不同的变量使用相同的栈内存,导致后面的变量覆盖前面的数据,从而在使用前面的变量时输出后面的变量。所以设置作用域,使变量出了作用域不再有效。
- 为什么不建议使用递归?
因为栈内存是一个非常珍贵的资源,而使用递归有时会产生死循环,将消耗栈内存,直至消耗完,从而产生StackOverflow的错误。
注意
- 栈内存先进后出,轮流使用。
同一块内存要在占用它的函数执行完毕,内存被回收之后,其它函数才能使用这一块内存。栈中的内存可以被轮流使用,这也是为什么正常的程序可以一直执行,而不会出现栈溢出的问题。
- 不要在函数体中声明大块内存(大型数组,大型结构体)。
例如声明int arr[10240000]
,十分占用栈内存。栈内存十分珍贵,应避免这种情况。
堆
这一块变量用于动态存储。
特点
- 堆大小较大;
- 主动分配,主动回收。
分配
分配方式 | 回收方式 |
---|---|
malloc/calloc | free |
new | delete |
new T[] | delete[] T |
示例
/* c 风格, 分配方式 1. */
void * mem = malloc(1024);
/* c 风格, 分配方式 2.*/
void * mem = calloc(1024);
/* C 风格, 回收内存. */
free(mem);
int * iptr = new int; // cpp new
delete iptr; // cpp delete
int * iarrptr = new int[1024]; // cpp new T[]
delete[] iarrptr; // cpp delete[] T
注意
- 不用堆内存时,一定要及时调用回收API回收内存。
- 访问申请的内存时不要超过申请的量,例如
int * ptr = new int[4]; ptr[8] = 0;
否则会出现一系列未定义行为Undefined behavior。
指针
指针表示的时某块数据在内存中的地址,地址为整型,它能表示的范围因平台的不同而不同。
程序类型 | 指针位数 | 等同于 |
---|---|---|
32位程序 | 32 | uint32_t |
64位程序 | 64 | uint64_t |
在同一平台上,任何一个指针都可以退化为整数表示,因为任何指针的本质都是一个整数,它代表着一个内存位置。
基本操作
- 赋值
- 强制转换
- 解引用
- 加减
int * ptr = new int;
int * iptr = ptr; // 1. 相同类型的指针直接赋值.
char * c = (char *)ptr; // 2. 不同类型的指针需要强制转换.
int i = *ptr; // 3. 解引用, 将内存 ptr 指向的内存中的内容赋值给变量 i.
// 4. 加减. 指针的加减单位是以指针类型为依据的, int 在内存中占用 4 个字节, testPtr 加减每次的单位也是 4.
// sizeof(int) == 4;
int * arrPtr = new int[4];
int * testPtr = arrPtr;
printf("%u\n", testPtr); // 可将指针作为无符号整型输出.
testPtr = testPtr + 1;
printf("%u\n", testPtr);
testPtr = testPtr + 1;
printf("%u\n", testPtr);
指针,指向指针的指针,指向指针的指针的指针
视频链接
- (含字幕)C++ 让你不再害怕内存和指针 其一_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili
- [(含字幕)C++ 让你不再害怕内存和指针 其二_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili](