{ }codelper

学ぶ › C

言語ごとの特徴 · 35

動的メモリ

実行中に決めた大きさだけメモリを借り、使い終わったら必ず返します。

この単元は C++ · C 向けです。
Python・JavaScript・TypeScript・Java・C#・Craykon では、ガベージコレクタがもう使われないオブジェクトを自動で片付けるので、メモリを借りて返すコードは書きません。

入力を読むまで大きさがわからないときは、実行中にヒープ(heap)からメモリを借ります。
下の例は n を読んで n 要素分を借り、数を入れて逆順に出力し、合計を出してから返します。

#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;}
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 に任せるのがいちばん安全です。

練習問題