C 言語の通常の配列はコンパイル時にその領域が確保され、 したがってプログラム実行時にその大きさを変えることができない。 ここでは、単なる配列の宣言ではない方法でメモリを確保する方法を説明する。
ベクトルを扱う問題を考える。 ベクトルの次元 (長さ) は実行時に与えられるとしよう。 前に説明したのは、この場合に十分大きなメモリを確保しておき、 その一部を利用するという方法である。 この場合、"十分大きな"というのがどれくらいが妥当か分からず、 また多くのメモリが無駄に使われるという問題がある。 そこで必要な時に必要な大きさのメモリを確保する方法が必要になる。
例を見てみよう。
1 #include <stdio.h> 2 3 int main(int argc, char *argv[]) 4 { 5 int i, n; 6 float *p; 7 8 printf("Input the size of your vectors : "); 9 scanf("%d", &n); 10 11 p = (float *)malloc(sizeof(float) * n); 12 13 printf("Input entries of your vector x : "); 14 for (i = 0; i < n; i++) 15 scanf("%f", p + i); 16 for (i = 0; i < n; i++) 17 printf("%f ", *(p + i)); 18 printf("\n"); 19 20 free(p); 21 22 return 0; 23 }まずベクトルの長さを決め、その大きさによって malloc によって領域を確保している。 確保する領域のサイズは(確保する変数の型のサイズ)×(欲しい配列の長さ)で、 sizeof を用いて 11 行のように表される。 malloc の戻り値は (void *) なので、それを (float *) にキャストする。 これで float p[n] と宣言した時と同じように配列 p を用いることができる。 15, 17 行では &p[i], p[i] という記述も出来るが、ここではポインタを使った記述をしてみた。 malloc で確保した領域はプログラム終了時に自動的に開放されるが、 それ以外の場合には明示的に開放しなければ開放されない。 メモリを解放するには 20 行のように、そのメモリを示すポインタ変数を用いて free(p) のようにする。 プログラム終了時には開放されるのだから、小さなプログラムでは開放を忘れてもまったく問題はない。 しかし大規模なプログラムやループの多いプログラムで不要なメモリを開放せずにいると、 必要以上にメモリを使ってしまい、場合によってはシステムに悪影響を与える。 malloc で確保したメモリはなるべく明示的に開放するようにしよう。
malloc は要求されたメモリの量を確保できないと NULL を返す。 この場合にそのメモリに値を書き込もうとするとエラーとなる。 malloc でメモリを確保する時には
if ((p = (float *)malloc(sizeof(float) * n)) == NULL) { printf("I cannot allocate the memory\n"); exit(1); }などとして、メモリの確保に失敗したことをチェックするとよい。 exit(1) は 1 を戻り値として直ちにプログラムを停止するためのコマンドである。 main 関数内では適当な値を return すればプログラムは停止するが、 main 以外の関数内ではそれではプログラムが停止しないため exit で強制的に停止する。
課題 06.01
自然数 n を引数とし、長さ n の int の領域を確保し、そのポインタを返す関数を書け。
関数内で適当な配列を宣言しても、その配列が利用出来るのはその関数の中だけである。 しかし malloc で確保した領域は free しない限り、定義した関数以外の場所でも利用できる。 これは便利なことであるが、先に述べたように free を忘れるとメモリの無駄な消費につながる。
課題 06.02
float を成分とする (m,n) 行列のための領域を確保し、そのポインタを返す関数を書け。
(戻り値の型は (float **) となる。)
また、これによって確保した領域を開放するための関数を書け。
課題 06.03
適当な関数をもつプログラムを書き、exit(1) の動きを確認せよ。
malloc と関係して calloc や realloc もあるが、これらについては自分で調べて欲しい。