Logo
Section 1 / 5

内存分配

在本节课中,我们将学习内存管理的一个重要分支,即内存分配。

导入

内存分配

在本节课中,我们将学习内存管理的一个重要分支,即内存分配。

简介

C++ 中的显式内存管理具有较高的复杂性,但也为我们提供了强大的功能。遗憾的是,这一 C++ 特有的领域并不为很多人所熟知。

例如,我们可以直接在静态内存、预留区域甚至内存池中创建对象。这种功能在嵌入式领域的安全关键应用中尤为重要。

  • C++ 支持动态分配和释放内存。

  • 动态内存(即堆)需要程序员显式地请求和释放。

  • 我们可以使用 new 和 new[] 操作符来分配内存,使用 delete 和 delete[] 操作符来释放内存。

  • 编译器会自动管理栈上的内存。

Course Image
智能指针可自动管理内存。

内存分配

new

借助 new 操作符,我们可以为某个类型的实例动态分配内存。

1int* i = new int; 2double* d = new double(10.0); 3Point* p = new Point(1.0, 2.0);
  • new 会触发内存分配并完成对象初始化。

  • 括号中的参数会直接传递给构造函数。

  • new 返回指向对应对象的指针。

  • 如果动态创建的对象属于类型继承体系,则会调用更多构造函数。

new[]

new[] 用于为 C 风格数组分配内存。新创建的对象必须具备默认构造函数。

1double* d = new double[5]; 2Point* p = new Point[10];
  • 被分配对象的类必须拥有默认构造函数。

  • 每个数组元素都会调用一次默认构造函数。

Course Image
STL 容器和 C++ 字符串会自动管理其内存。

位置 new

位置 new 常用于在指定内存区域中实例化对象或 C 数组。此外,我们还可以对位置 new 进行全局或针对自定义数据类型的重载。这是 C++ 提供的一大优势。

1char* memory = new char[sizeof(Account)]; // 分配 std::size_t 大小的内存 2Account* acc = new(memory) Account; // 在 memory 中实例化 acc
  • 必须包含头文件 <new>。

  • 可以在类级别或全局范围内进行重载。

典型应用场景

  • 显式内存分配

  • 避免抛出异常

  • 调试

内存分配失败

如果内存分配操作失败,new 和 new[] 会抛出 std::bad_alloc 异常。但有时我们不希望出现异常。因此,可以使用常量 std::nothrow 调用位置 new。此时若分配失败,将返回 nullptr。

1char* c = new(std::nothrow) char[10]; 2if (c) { 3 delete[] c; 4} else { 5 // 发生错误 6}

新内存处理函数

当内存分配失败时,可以使用 std::set_new_handler 注册自定义处理函数。std::set_new_handler 返回旧的处理函数,并接受一个可调用对象作为参数。可调用对象通常是一个函数、函数对象或 lambda 表达式。该处理函数应无参数且无返回值。可以通过调用 std::get_new_handler 获取当前正在使用的处理函数。

自定义处理函数允许我们实现特定的失败处理策略:

  • 请求更多内存
  • 使用 std::terminate 终止程序
  • 抛出 std::bad_alloc 类型的异常

在下一课中,我们将学习如何释放内存。

移动语义与完美转发

内存释放