{ }codelper

学习 › C++

各语言特有 · 35

动态内存

在运行时按需要的大小借用内存,用完一定要归还。

本单元适用于 C++ · C。
Python、JavaScript、TypeScript、Java、C# 和 Craykon 有垃圾回收器,会自动清理不再使用的对象,所以不用写借用和归还内存的代码。

要读完输入才知道大小时,就在运行中从堆(heap)借内存。
下面的例子读入 n,借 n 个格子存放数字,倒序输出并求和,最后把内存还回去。

#include <iostream>#include <vector> int main() {    int n;    if (!(std::cin >> n) || n <= 0) return 0;     std::vector<int> a(n);  // n칸을 빌리고, 범위를 벗어나면 저절로 돌려줘요    long long sum = 0;    for (int& x : a) {        std::cin >> x;        sum += x;    }    for (int i = n - 1; i >= 0; --i)        std::cout << a[i] << (i > 0 ? ' ' : '\n');    std::cout << sum << '\n';}  // 여기서 a의 소멸자가 메모리를 해제해요 (delete가 필요 없어요)
stdin
5
3 1 4 1 5
stdout
5 1 4 1 3
14

要点

  • 到目前为止,数组都像 int arr[100] 这样事先定好大小。
    用堆就能按运行时决定的大小来借。
  • 局部变量(栈)在函数结束时自动消失。
    从堆借来的内存会一直留着,直到你亲手归还。
  • 所以函数结束后仍要使用的结果也能返回出去。
    代价是归还也是你的责任。
  • C++ 把内存绑定到对象的生命周期上(RAII)。
    变量离开作用域时由析构函数归还,即使抛出异常或中途 return 也不会漏掉释放。
  • 用 gcc -fsanitize=address 编译,运行时就能抓到内存泄漏、释放后使用和越界写入。

各语言的写法

C++通常用 std::vector<int> a(n)(n 个填 0 的格子)。
单个对象或大小不变的数组用 std::make_unique<int[]>(n) 创建的 std::unique_ptr — 也有 new int[n] / delete[] a(只借一个就用 delete,配错是未定义行为),但现代 C++ 很少直接写。
int *a = malloc(...) 在 C++ 里是编译错误,因为 void * 不会自动转换
C<stdlib.h> 的 malloc(n * sizeof *a) — 内存不够时返回 NULL,格子里是垃圾值。
calloc(n, sizeof *a) 会全部填 0;realloc(a, m * sizeof *a) 可能搬到别处,要用它返回的新指针。
用完后 free(a)

常见错误

忘了 free

for (int k = 0; k < 1000; k++) {
    int *a = malloc(n * sizeof *a);
    /* … 没有 free(a) */
}

→ 程序照常运行,但占用的内存不断增加(内存泄漏)

每借一次就对应一个 free。
C++ 交给 std::vector。

释放后又使用,或释放两次

free(a);
printf("%d\n", a[0]);
free(a);

→ 释放后使用(use after free)是未定义行为,而且旧值常常还在,所以更难发现。释放两次(double free)时,glibc 通常输出 free(): double free detected in tcache 2 并终止

free(a); 之后写 a = NULL;。
误用时通常会立刻崩溃,而 free(NULL) 什么也不做。

i <= n 越过了借来的范围

int *a = malloc(n * sizeof *a);
for (int i = 0; i <= n; i++)
    a[i] = 0;

→ 写到了 n 个格子之后的 a[n],破坏了别人的内存(缓冲区溢出)。当场可能毫无异样,很久以后才在不相干的地方崩溃

写成 i < n,并用 -fsanitize=address 检查。

各语言的差异
借来的东西恰好归还一次:每个 malloc、calloc、realloc 对应一个 free,new 对应 delete,new[] 对应 delete[]。
在 C++ 中,最安全的做法是把这种配对交给 std::vector 和 std::unique_ptr。

练习题