C++——内存管理

49 阅读4分钟

动态内存分配:newnew[]

在 C++ 中,可以使用 newnew[] 运算符在运行时动态地分配内存。动态分配的内存位于堆(heap)区,与栈(stack)区的自动分配内存不同,需要手动释放。

  • new 用于分配单个对象的内存。
  • new[] 用于分配数组的内存。

代码示例:

#include <iostream>

int main() {
  // 使用 new 分配一个整数
  int *p = new int;
  *p = 10;
  std::cout << "*p: " << *p << std::endl;
  delete p; // 释放单个对象的内存

  // 使用 new[] 分配一个包含 5 个整数的数组
  int *arr = new int[5];
  for (int i = 0; i < 5; ++i) {
    arr[i] = i * 2;
  }
  std::cout << "动态分配的数组:";
  for (int i = 0; i < 5; ++i) {
    std::cout << arr[i] << " ";
  }
  std::cout << std::endl; // 输出:0 2 4 6 8
  delete[] arr; // 释放数组内存

  return 0;
}

解释:

  • int *p = new int; 在堆上分配一块足以存储一个 int 的内存,并返回该内存的地址,赋值给指针 p
  • int *arr = new int[5]; 在堆上分配一块足以存储 5 个 int 的连续内存,并返回首元素的地址。

C 语言中的对应操作:

  • malloc(): 用于动态分配内存。
  • calloc(): 用于动态分配并初始化为零。
  • realloc(): 用于重新分配内存。
  • free(): 用于释放 malloc, calloc, 或 realloc 分配的内存。

重要提示: newdelete 必须成对使用,new[]delete[] 必须成对使用,否则会导致内存泄漏或程序崩溃。


3. 动态内存释放:deletedelete[]

使用 newnew[] 分配的内存,在不再使用时必须使用 deletedelete[] 进行释放。

  • delete 用于释放通过 new 分配的单个对象的内存。
  • delete[] 用于释放通过 new[] 分配的数组的内存。

4. 内存越界及其危害

内存越界指的是程序试图访问不属于其分配范围的内存区域。这是一种非常危险的行为,可能导致:

  • 程序崩溃: 操作系统可能会阻止程序访问非法内存。
  • 数据损坏: 覆盖其他变量或数据结构的内容,导致程序逻辑错误。
  • 安全漏洞: 在某些情况下,越界访问可能被恶意利用。

代码示例:

#include <iostream>

int main() {
  int arr[5] = {1, 2, 3, 4, 5};

  for (int i = 0; i <= 5; ++i) { // 错误:循环条件应为 i < 5
    std::cout << arr[i] << " ";
  }
  std::cout << std::endl; // 最后一个访问 arr[5] 越界

  return 0;
}

5. 内存泄漏及其危害

内存泄漏是指程序在动态分配内存后,忘记或未能释放不再使用的内存。随着程序的运行,泄漏的内存会越来越多,最终可能导致:

  • 程序性能下降: 可用内存减少,系统需要更频繁地进行内存管理。
  • 系统崩溃: 当所有可用内存都被耗尽时。

代码示例:

#include <iostream>

void memoryLeaker() {
  int *ptr = new int[1000];
  // 忘记 delete[] ptr;
}

int main() {
  for (int i = 0; i < 100; ++i) {
    memoryLeaker(); // 每次调用都会泄漏 4000 字节的内存 (假设 int 为 4 字节)
  }
  // 程序结束时,泄漏的内存由操作系统回收,但长期运行的程序可能会耗尽内存
  return 0;
}

6. 指针的安全使用原则

指针是 C++ 中强大但容易出错的特性。安全使用指针至关重要:

(1) 初始化: 指针在声明时应初始化为一个有效的地址或空指针 (nullptr)。

(2) 空指针检查: 在解引用指针之前,务必检查它是否为 nullptr,避免访问无效内存。

(3) 匹配的 newdelete 确保每个 new 分配的内存都有对应的 delete 释放,new[] 对应 delete[]

(4) 避免重复释放: 不要释放已经释放过的内存,这会导致程序崩溃。

(5) 释放后置空: 释放内存后,立即将指针设置为 nullptr,防止产生悬挂指针(dangling pointer)。

(6) 避免悬挂指针: 悬挂指针指向的内存已经被释放,访问它会导致未定义行为。

(7) 谨慎使用原始指针: 考虑使用智能指针来管理动态分配的内存,以减少手动管理的错误。

代码示例:

#include <iostream>

int main() {
  int *ptr = nullptr; // 初始化为空指针

  if (ptr != nullptr) {
    *ptr = 10; // 不会执行,避免了访问空指针
  }

  ptr = new int;
  if (ptr != nullptr) {
    *ptr = 20;
    std::cout << "*ptr: " << *ptr << std::endl;
    delete ptr;
    ptr = nullptr; // 释放后置空
  }

  // ... 后续不再使用已释放的 ptr

  return 0;
}