C言語で動的に多次元配列を確保する方法
author: dynamic shun
reviewer: aso, h-got, moto, 河上様

はじめに

本稿に書かれてあるプログラムには,実際に実行してはならないものや 間違いがあるプログラムがありますので注意して下さい. ちなみに実際に動的に確保をするプログラムは 結構長い能書きの後に書いてありますが, 今後のことも考えて最初から熟読されることを薦めます.

コンパイル時にサイズが既知の場合

例えば画像処理を行なうプログラムを書こうとします. その画像を表現する二次元配列を用意するには以下のような方法が一般的でしょ う.

コンパイル時に画像サイズがわかっているとき
#define SIZE_X 64
#define SIZE_Y 32
main()
{
    int i, j                        /* ループ変数 */
    double image[SIZE_X][SIZE_Y];   /* 画像 */

    for (i=0 ; i < SIZE_X ; i++) {
        for (j=0 ; j < SIZE_Y ; j++) {
	    image[i][j] = どうしたこうした /* 画像をいろいろいじる */
        }
    }

    exit(0);
}

上の例ではサイズが 64x32 とわかっている場合ですが,我々の研究において そのような状況は多くなく,通常はそのサイズが未知である場合が普通でしょ う.すなわちコンパイル時にはそのサイズが決定されず,プログラムを実行し, ある画像を入力した段階でようやくサイズがわかるような状況です.

上の例ではたまたま 64x32 とサイズが固定されていましたが, このプログラムではそれより大きいサイズの画像を読みこむことはできません. 一番簡単な方法はとにかく画像のサイズを最初から巨大なものにしておくこと です.以下のようにします.

とりあえず大きいサイズにしておく(薦めないが,よくやる方法)
#define SIZE_X 1000   /* でかいサイズ */
#define SIZE_Y 1000   /* でかいサイズ */
main()
{
    int i, j                        /* ループ変数 */
    double image[SIZE_X][SIZE_Y];   /* 画像 */

    for (i=0 ; i < SIZE_X ; i++) {
        for (j=0 ; j < SIZE_Y ; j++) {
	    image[i][j] = どうしたこうした /* 画像をいろいろいじる */
        }
    }

    exit(0);
}

これは極端な例ですが,この方法ではメモリを大量に消費しますし, ただ単に最初のプログラムのサイズを大きくしただけですので, この大きいサイズを越える画像を処理することはできません. ようするに根本的な解決をする以外に方法はありません.

注意:鈴木基之二児のパパ情報
上のプログラムの #define SIZE_X や #define SIZE_Y のところで あまりにも大きい数字を書いてしまうと,コンパイルは通るが変な実行結果 が出る,もしくはコンパイルすら通らないという状態になります. これは以下の理由です. 上記プログラム中の int i,int j,double image[SIZE_X][SIZE_Y] などの いわゆる普通の変数は auto 変数と呼ばれます. auto 変数には実は限られた領域しか使用できないという制限があります (制限の上限は使っているマシンに依存します).
故に上記プログラムで
#define SIZE_X 1000000000000000
#define SIZE_Y 999999999999999
などとすると,多分動きません.(仮に動いて怒られても知らないよ)
結論として,あまりに大きい auto 変数の配列は使うべきではないということです.

malloc を用いた一次元配列の確保

ではどうするかというと,動的にメモリを取る

malloc
という関数を使用します. とりあえず,簡単の為に一次元のメモリ(ベクトル)を動的に取る方法を 示します.このプログラムを実行すると数字の入力を求められます. その数字は変数 size に代入され,サイズが size の一次元ベクトルを メモリに取り,それを変数 vector として用いるものです.

動的に一次元ベクトルをメモリに取るプログラム
#include <stdio.h>
#include <stdlib.h>

main()
{
    double *vector;
    int     size;
    int     i;

    scanf("%d",&size)     /* 本当は scanf は使うべきではないが */
                          /* ここでの議論ではないので御容赦願いたい */

    vector = (double*)malloc(sizeof(double)*size);

    for (i=0 ; i < size ; i++) {
        vector[i] = どうしたこうした;
    }

    free(vector);

    exit(0);
}

malloc の()の中身は,

「サイズが double のメモリ(sizeof(double))を size 分だけ用 意してくれ」

という意味で,(double*)は念のためにキャストしているだけで す.(このようなキャストをすべきではないという方もいらっしゃいますが, malloc は返り値に void* 型を返すのであえてキャストしてあります)

図1malloc は返り値として何を返すかというと,メモリを確保した 領域の先頭の番地(アドレス)です. 例えば上記の malloc が確保したメモリの先頭番地が 「1丁目」だったとすると変数 vector には「1丁目」が 代入されます.vector には double 型の変数の番地が代入されるので,

double* vector

として宣言されたわけです.概念的には右上のような図になるでしょう. また
vector[0]はメモリ番地「1丁目」の中身
vector[1]はメモリ番地「2丁目」の中身
となりますので一般的なベクトルのように扱えるわけです. ちなみに *vector とするとこれは「1丁目の中身」を意味しますので,
*vector == vector[0]
です.さらに vector が今1丁目だったとすると,vector+1 は「2丁目」に なり,*(vector+1) は 「2丁目にある中身」という意味なので
*(vector+1) == vector[1]
です.

このプログラムでどんなサイズのベクトルも扱えるようになります. すなわち入力で 1024 と入れれば,サイズが 1024 のベクトルが生成されます し,3 と入れれば サイズが 3 のベクトルが生成されます. 当然巨大な数字を入れてコンピュータのメモリを使い切ってしまえば メモリ獲得,すなわち mallocの関数は失敗します. malloc はメモリの獲得に失敗すると NULL を返しますので,実際には 以下のように書き替えるべきです.

動的に一次元ベクトルをメモリに取るプログラム(完成版)
#include <stdio.h>
#include <stdlib.h>

main()
{
    double *vector;
    int     size;
    int     i;

    scanf("%d",&size)     /* 本当は scanf は使うべきではない */

    vector = (double*)malloc(sizeof(double)*size);

    if (vector == NULL) { /* メモリ確保の成功/失敗の確認 */
       fprintf(stderr,"Fail to insure memories\n");
       exit(1);
    }

    for (i=0 ; i < size ; i++) {
        vector[i] = どうしたこうした;
    }

    free(vector);

    exit(0);
}

メモリの解放(free)

さて,プログラム中に free という関数があるのにお気付きでしょうか? この関数は malloc とは逆に,メモリを解放する関数です. メモリを確保したら使い終った後にちゃんと後始末をしなければなりません. これを実現しているのが free です.

通常(auto)の変数に対して free を実行する必要はありませんでした. これは関数が終了すると自動的にその変数が使用しているメモリを解放して くれるからです.すなわち,ある関数 foo があったとすると, 以下のような処理が行なわれます.

auto 変数のメモリ確保/解放

void foo()
{
    int     i;  /* auto 変数として sizeof(int) 分のメモリが */
                /* 自動的に確保される */

    /* あーだこーだ色々な処理 */

}  /* 関数が終ると自動的に i が確保していたメモリが解放される */

free は malloc に対応して必ず書かなければならない関数です. free を書き忘れると,最悪, システムのメモリを極限までつかい切ってしまい,他のユーザに
「こりゃー!そのプログラムすぐ止めろ!」
と怒られてしまいます. では以下に free 書き忘れた最悪なプログラムを紹介しましょう.

最悪なプログラム.絶対実行してはいけません!

#include <stdio.h>
#include <stdlib.h>

main()
{
    double* vector;
    int     size;
    int     i;

    scanf("%d",&size)     /* 本当は scanf は使うべきではない */

    while (1) { /* ループ */
        vector = (double*)malloc(sizeof(double)*size); /* memory 確保 */

        /* vector[i] に対する色々な処理 */

	if (何かの条件) break; /* while から脱出 */
    }

    exit(0);
}

このプログラムを実行すると, while ループの中で malloc が何回も呼び出されている のにも関わらず,その後始末をしていないがためにメモリをどんどん使ってし まいます.ではちゃんと後始末をしましょう.

free を正しく行なうプログラム
#include <stdio.h>
#include <stdlib.h>

main()
{
    double* vector;
    int     size;
    int     i;

    scanf("%d",&size)     /* 本当は scanf は使うべきではない */

    while (1) { /* ループ */
        vector = (double*)malloc(sizeof(double)*size); /* memory 確保 */

        /* vector[i] に対する色々な処理 */

	free (vector); /* ここで後始末 */

	if (何かの条件) break; /* while から脱出 */
    }

    exit(0);
}

さて,このプログラムの free の位置に注意しましょう. free はあくまでも「使ったメモリを片付ける」ものですので, while の中に書かなくてはなりません.

二次元配列の動的確保

図2 いよいよ本題です.まず概念的なものを 右の図を使って説明します. 図では点線によって3つのパートに分れていますが, 右側にあるのが,実際にデータを入れる二次元配列となっており, 5行13列分すなわち65個の double 型の箱があります. 更に真中には (double*) 型のベクトルがあります.サイズは5です. 一番左には (double**) 型の変数があります. さて,図の青と赤の矢印が見られると思いますが, これはそれぞれどこを参照しているかを示したものです.

仮に図のような矢印がすでに張られた状態を考えてみます. matrix は matrix[0]という変数が存在している番地を指しています (青い矢印).

matrix[0] は matrix[0][0] が存在している番地を指しています.
matrix[1] は matrix[1][0] が存在している番地を指しています.

matrix[4] は matrix[4][0] が存在している番地を指しています.

matrix[3] がなぜ図にある位置を指すのかについては一次元のメモリの 確保の所で説明した通りです.図にあるような箱を用意し, 更に青や赤の矢印を張ってやれば二次元配列の完成となるわけです.

要するに我々がしなければならないことは以下ようになります.

  1. double** 型の変数を用意する(図左).
  2. double* 型でサイズが5のベクトルを確保する(図中央).
  3. 青い矢印を生成する.
  4. double 型でサイズが13のベクトルを確保する(図右).
  5. 一番上の赤い矢印を生成する.
  6. 以下2番目3番目とつづく
  7. double 型でサイズが13のベクトルを確保する(図右).
  8. 一番下の赤い矢印を生成する.

プログラムで書くと以下のようになります. ちなみにプログラム中のコメントにある数字は上に列挙してある数字と 対応していますので,その対応関係を眺めながら処理を追って下さい. また,青色の=と赤色の= は図のにそれ ぞれ対応しています.

5行13列 の二次元配列を確保するには?
#include <stdio.h>
#include <stdlib.h>

main()
{
    double** matrix;  /* 1. double** 型の用意 */
    int i;

    /* 右辺は 2. */
    /* 左辺と = で 3. の青い矢印を生成*/
    matrix = (double**)malloc(sizeof(double*)*5);

    /* このループで 4. 以降の処理 */
    matrix[0] = (double*)malloc(sizeof(double)*13);
    matrix[1] = (double*)malloc(sizeof(double)*13);
    matrix[2] = (double*)malloc(sizeof(double)*13);
    matrix[3] = (double*)malloc(sizeof(double)*13);
    matrix[4] = (double*)malloc(sizeof(double)*13);
    
    /* これより下は後で説明します */
    free(matrix[4]);
    free(matrix[3]);
    free(matrix[2]);
    free(matrix[1]);
    free(matrix[0]);

    free(matrix);

    exit(0);
}

これは当然 for ループを使って簡素に書くことができます.
もっと格好良く
#include <stdio.h>
#include <stdlib.h>

main()
{
    double** matrix;  /* 1. double** 型の用意 */
    int i;

    /* 右辺は 2. */
    /* 左辺と = で 3. の青い矢印を生成*/
    matrix = (double**)malloc(sizeof(double*)*5);

    /* このループで 4. 以降の処理 */
    for (i=0 ; i < 5 ; i++) {
        matrix[i] = (double*)malloc(sizeof(double)*13);
    }
    
    /* これより下は後で説明します */
    for (i=0 ; i < 5 ; i++) {
        free(matrix[i]);
    }
    free(matrix);

    exit(0);
}

一般的なサイズになると以下のようになります.

一般的なサイズの場合の動的二次元配列
#include <stdio.h>
#include <stdlib.h>

main()
{
    double** matrix;  
    int size_x, size_y; /* size_x 行 size_y 列 */
    int i;

    scanf("%d%d", &size_x, &size_y);

    /* 左辺と = で 3. の青い矢印を生成*/
    matrix = (double**)malloc(sizeof(double*)*size_x);

    /* このループで 4. 以降の処理 */
    for (i=0 ; i < size_x ; i++) {
        matrix[i] = (double*)malloc(sizeof(double)*size_y);
    }
    
    
    /* これより下は後で説明します */
    for (i=0 ; i < size_x ; i++) {
        free(matrix[i]);
    }
    free(matrix);

    exit(0);
}

二次元配列のメモリ解放

確保したら当然解放しなければなりません. 二次元の場合は少々注意が必要です.

二次元配列を生成する際に二種類のベクトルを生成しました. すなわち図2の中央部を先に生成し, 図2の右側を for ループで生成しまし た. free する際には生成した順とは逆に free する必要があります. この順番を守らないと,あたかもダルマ落しの真中をいきなり引き抜くような もので,へたすると(へたしなくても)壊れてしまいます.

以下が二次元配列を動的に確保し解放するプログラムの最終版です.

一般的なサイズの場合の動的二次元配列
#include <stdio.h>
#include <stdlib.h>

main()
{
    double** matrix;  
    int size_x, size_y; /* size_x 行 size_y 列 */
    int i;

    scanf("%d%d", &size_x, &size_y);

    /* 左辺と = で 3. の青い矢印を生成*/
    matrix = (double**)malloc(sizeof(double*)*size_x);
    if (matrix==NULL) exit(1); /* 正常にmallocできたかどうかのチェック*/

    /* このループで 4. 以降の処理 */
    for (i=0 ; i < size_x ; i++) {
        matrix[i] = (double*)malloc(sizeof(double)*size_y);
        if (matrix[i]==NULL) exit(1);
    }

    /* 図2右側のベクトルの解放 */
    for (i=0 ; i < size_x ; i++) {
        free(matrix[i]);
    }
    /* 図2中央のベクトルの解放 */
    free(matrix);

    exit(0);
}

for ループを使わないプログラムでは,matrix[i] に対する free の順番 は,「生成した順とは逆に free する」という規則がありましたので それに忠実に,

としましたが,上記の for ループを使ったプログラムを展開すると, それとは逆に の順で free を実行しています. では, 上のプログラムが「生成した順とは逆に free を実行する」という規則に 反しているので間違いかというと,そうではありません. matrix[i] をどこから free を実行しても他に影響がないからです (ダルマの頭が5個あるようなもの?). 上記の for ループを使用したプログラムは,見た目の簡素さと, デバッグの容易さから書かれているものです.

3次元以上の配列の確保/解放

3次元以上のものについても考えかたは全く同じです. すなわち double** matrix で宣言していたものを double*** matrix にし, for ループを2回まわすことになります(2次元のときは1回でした). free についても同様です.

一般的なサイズの場合の動的三次元配列
#include <stdio.h>
#include <stdlib.h>

main()
{
    double*** matrix;  
    int size_x, size_y, size_z; /* size_x × size_y × size_z */
    int i,j;

    /* サイズの読み込み */
    scanf("%d%d%d", &size_x, &size_y, &size_z);

    /* size_x × size_y × size_z の配列を確保 */
    matrix = (double***)malloc(sizeof(double**)*size_x);
    if (matrix==NULL) exit(1);
    for (i=0 ; i < size_x ; i++) {
        matrix[i] = (double**)malloc(sizeof(double*)*size_y);
        if (matrix[i]==NULL) exit(1);
            for (j=0 ; j < size_y ; j++) {
                matrix[i][j] = (double *)malloc(sizeof(double)*size_z);
                if (matrix[i][j]==NULL) exit(1);
            }
    }


    /* いろいろな処理をし終えたら */


    /* 確保した配列を解放 */
    for (i=0 ; i < size_x ; i++) {
        for (j=0 ; j < size_y ; j++) {
            free(matrix[i][j]);
        }
        free(matrix[i]);
    }
    free(matrix);

    exit(0);
}

最後に先人達の言い残した格言があるので紹介しておきます.
malloc にバグなし


shun@aso.ecei.tohoku.ac.jp

Copyright 1998, 1999, 2000 Shunji Satoh. All Rights Reserved.