{ }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)(0 で埋めた n 要素)。
オブジェクト 1 つや大きさの変わらない配列は std::make_unique<int[]>(n) で作る std::unique_ptr — new int[n]/delete[] a もありますが(1 つだけなら delete、組を間違えると未定義動作)、今の C++ ではほとんど直接書きません。
int *a = malloc(...) は void * が自動で変換されないので C++ ではコンパイルエラーです
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 を 1 つ。
C++ は std::vector に任せます。

解放した後に使った・2 回解放した

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

→ 解放後の使用(use after free)は未定義動作で、古い値がそのまま見えることもあるので、かえって見つけにくくなります。2 回解放(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 で確かめます。

言語ごとの違い
借りたものはちょうど1回返します。
malloc・calloc・realloc 1つに free 1つ、new には delete、new[] には delete[] で、C++ ではこの対応づけを std::vector と std::unique_ptr に任せるのがいちばん安全です。

練習問題