1054 字
5 分钟
【C++】空间管理
在程序运行中,电脑的内存空间是有限的,因此我们要对内存进行管理,使得程序运行时不会因为内存不足而崩溃,尤其是避免内存泄漏的问题。
内存存储区域
C++中,内存存储区域主要有以下几种:
- 静态存储区域
静态存储区域用来存储全局变量和静态变量(如静态局部变量和静态成员变量)等。这些内存空间在程序编译完成时就已经分配好,并在程序结束时被销毁,变量的生命周期存在于程序运行期间。 - 栈空间
栈空间用来存储局部变量(不包括static
修饰的局部变量)、函数参数等。这些变量在函数调用结束后会被销毁,生命周期只存在于函数调用期间。栈空间的管理由编译器自动完成。 - 堆空间
堆是用来动态分配内存的区域,程序员可以通过new
或malloc
等函数来请求分配内存,并通过delete
或free
来释放内存。堆内存的生命周期由程序控制。
注意栈空间和堆空间之间有些区别:
- 栈空间是编译器自动管理的,而堆空间是程序员手动管理的。栈空间在函数调用结束后会自动销毁,而堆空间需要程序员手动释放
- 栈空间的大小通常是固定的,而堆空间的大小受限于内存限制
void Demo() { int *p = nullptr; // 申请一个栈空间 p p = new int[5]; // 申请一个堆空间 int [5] delete[] p; // 释放堆空间 int[5] } // 销毁栈空间 p
栈空间
在C++中,代码块是一种特殊的语句块,用于定义一个代码块,代码块中的语句会按照顺序执行。代码块由花括号 { }
包含,并且可以嵌套在其他代码块中。代码块可以包含任何类型的语句,包括变量声明、函数调用、条件语句、循环语句等。
代码块里面的变量声明和函数调用,会在代码块结束后被销毁,生命周期只存在于代码块中。
代码块里产生的变量存在于栈空间里,会随着代码块的结束而销毁。
void Demo() {
int a = 1;
{
int b = 2;
} // b 会被销毁
} // a 会被销毁
堆空间
使用malloc
我们可以使用cstdlib
的malloc
和free
函数来申请和释放堆空间,单位为字节。
#include <cstdlib>
void Demo() {
void *buffer = std::malloc(64); // 申请64字节的堆空间
if (buffer != nullptr) { // 判断堆空间是否申请成功
// TODO 使用 buffer
std::free(buffer); // 使用完要进行释放
} else {
// TODO 异常操作
}
}
使用opreator new
operator new
是一个特殊的操作符,用于在堆上分配内存,区别于new
,它只分配内存,不进行初始化,单位为字节。
需要注意的是,使用operator new
时,如果分配失败,operator new
会抛出一个异常,而不是返回一个空指针,这里我们需要使用try catch
自行捕获异常,并进行处理。
void Demo() {
void *buffer = nullptr; // 定义一个空指针
try {
buffer = ::operator new(16); // 申请16字节的堆空间
} catch (const std::exception &errmsg) { // 捕获异常
// TODO 错误处理
}
// TODO 使用 buffer
auto array = static_cast<uint8_t *>(buffer); // 把内存转换为数组,类型为uint8_t
for (int i = 0; i < 16; i++) array[i] = i;
::operator delete(buffer); // 释放堆空间
}
使用new
对于具体类型的变量,我们可以使用new
来申请堆空间。new
实际上是调用了operator new
,再对申请到的空间进行初始化。
void Demo() {
int *pIntArray = new int[10];
// TODO 使用 pIntArray
delete[] pIntArray;
pIntArray = nullptr;
}
注意事项
注意
- 栈区的变量在函数调用结束后会被销毁,有自己的生命周期,而堆区的变量需要我们手动释放,否则会造成内存泄漏;
- 堆区没有类型定义,如何使用内存空间取决于空间的使用方。