C言語基礎|配列の動的生成

これまでの配列は、だいたい次のどちらかでした。

  • 要素数が最初から決まっている(int a[10]; のように固定)
  • ある程度大きめに確保して、先頭の必要な分だけ使う

でも実際のプログラムでは、実行してみないと必要な要素数が分からないことが普通にあります。たとえば、

  • ファイルから読み込んだ行数だけ配列が欲しい。
  • ユーザーが入力した件数ぶんだけ記録したい。
  • ネットワークから受け取ったデータサイズに合わせたい。

こういうときに使うのが、動的メモリ確保です。
配列の要素数を実行時に決めて、必要な分だけメモリを借りて、使い終わったら返す。これが「配列の動的生成」です。

静的配列・VLA・動的配列の違い

まずは整理しておくとスッキリします。

種類サイズ決定置かれる場所のイメージ後片付け
静的配列(固定長)int a[100];コンパイル時スタック(main内など)不要(自動で消える)
VLA(可変長配列)int a[n];実行時スタック不要(自動で消える)
動的配列(callocなど)int *a = calloc(n, sizeof(int));実行時ヒープ(借りる領域)必須(freeで返す)

ポイントはここ

  • 動的配列は free しないと返せない
  • だから便利な分、責任も増える(でも実務ではこっちが基本になりがち)

calloc と free の役割(借りる・返す)

動的配列の基本ペアはこの2つです。

calloc の書式と意味

書式意味
void *calloc(size_t nmemb, size_t size);nmemb 個の要素(1個あたり size バイト)の領域を確保し、全バイトを 0 で初期化して返す

要点

  • 第1引数:要素数
  • 第2引数:要素1個の大きさ(sizeof(型) を使う)
  • 戻り値:確保に成功すれば先頭アドレス、失敗すると NULL

free の書式と意味

書式意味
void free(void *ptr);calloc などで確保した領域を解放する(返す)

ポインタが「配列の先頭」を指す

動的配列は、変数そのものが配列になるわけではなく、配列の先頭を指すポインタを受け取ります。

図:動的配列の全体像(概念図)

もの役割イメージ
int *a先頭要素を指すa → a[0]
a[i]i番目の要素にアクセス配列と同じ書き方で使える

つまり、a はポインタだけど、a[i] という添字アクセスが使えるので、見た目は配列っぽく扱えます。
(実際は「ポインタ+添字演算子」でアクセスしています)

サンプルプログラム(温度ログを動的配列に保存して平均を出す)

温度(double)を n 個入力して、平均・最大・最小を出すプログラムを例に解説をします。

プロジェクト名:chap5-11-1 ソースファイル名:chap5-11-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

// double型の配列を動的に生成して、平均・最大・最小を表示
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int n;

    printf("記録する回数を入力してください:");
    scanf("%d", &n);

    if (n < 1 || n > 100000) {
        puts("回数は 1〜100000 の範囲で入力してください。");
        return 0;
    }

    double *temp = calloc(n, sizeof(double));   // n個のdouble配列を確保(0初期化)

    if (temp == NULL) {
        puts("メモリ確保に失敗しました。");
        return 0;
    }

    puts("温度を入力してください。");
    for (int i = 0; i < n; i++) {
        printf("%d回目:", i + 1);
        scanf("%lf", &temp[i]);
    }

    double sum = 0.0;
    double max = temp[0];
    double min = temp[0];

    for (int i = 0; i < n; i++) {
        sum += temp[i];
        if (temp[i] > max) max = temp[i];
        if (temp[i] < min) min = temp[i];
    }

    printf("平均:%.2f\n", sum / n);
    printf("最大:%.2f\n", max);
    printf("最小:%.2f\n", min);

    free(temp);   // 確保した領域を解放

    return 0;
}

このプログラムで学ぶポイント

行動使っているものねらい
回数 n を入力scanf実行時に要素数を決める
temp を確保calloc(n, sizeof(double))n個分の領域を借りる(0初期化)
temp[i] に入力scanf + &temp[i]配列と同じ書き方で保存する
平均・最大・最小for + if走査して集計する
返却free(temp)借りた領域を返す

ありがちなミスと安全な作法

動的配列は、慣れるまではここでつまずきがちです。

ミス何が困る?対策
temp が NULL なのに使う即クラッシュ級NULLチェックを必ずする
free を忘れるメモリリーク(返し忘れ)使い終わったら必ず free
要素数に変な値(負、巨大)異常動作・確保失敗・危険入力値の範囲チェック
sizeof を間違える予想外のサイズで確保sizeof(型) を使う(sizeof(double)など)
free 後に temp[i] を使うすでに返した領域を触るfree 後は使わない。必要なら temp = NULL

calloc と malloc の違い(補足)

今回は calloc を中心にしました。理由はシンプルで、学習に向いているからです。

関数初期化使いどころ
calloc0で初期化される初期値が欲しい配列、学習向け
malloc初期化されない(不定値)高速寄り、必要なら自分で初期化

「初期値が0でいい配列」なら calloc は便利です。