各语言特有 · 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가 필요 없어요)#include <stdio.h>#include <stdlib.h> int main(void) { int n; if (scanf("%d", &n) != 1 || n <= 0) return 0; int *a = malloc(n * sizeof *a); /* 힙에서 n칸을 빌려요 */ if (a == NULL) return 1; /* 못 빌리면 NULL */ long long sum = 0; for (int i = 0; i < n; i++) { /* i <= n 이면 넘쳐요 */ scanf("%d", &a[i]); sum += a[i]; } for (int i = n - 1; i >= 0; i--) printf("%d%c", a[i], i > 0 ? ' ' : '\n'); printf("%lld\n", sum); free(a); /* 빌린 만큼 한 번 돌려줘요 */ a = NULL; /* 해제한 뒤에 실수로 쓰지 않게 */ return 0;}5 3 1 4 1 5
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 检查。
各语言的差异
借来的东西恰好归还一次:每个
在 C++ 中,最安全的做法是把这种配对交给
借来的东西恰好归还一次:每个
malloc、calloc、realloc 对应一个 free,new 对应 delete,new[] 对应 delete[]。在 C++ 中,最安全的做法是把这种配对交给
std::vector 和 std::unique_ptr。