6.メモリ領域の動的確保
これまでのプログラムでは、多くの数値をまとめて扱うために配列を宣言して使っていました。
int data1[]={1, 2, 3, 4, 5, 6, 7,
8, 9, 10}; /* int型の配列 */
double data2[100]; /* double型の配列 */
このように宣言すると、これらの配列に必要なメモリ領域はプログラム実行時に確保され、プログラム終了時までそのまま保持されます。このようなやり方を「静的な」メモリ領域の確保といいます。プログラムを書く時点でサイズが決まっているデータを扱う際には、この方法で問題ありません。
では、「扱うデータの大きさが、プログラムを書く時点では定まらない(プログラム実行時に決まる)場合」はどうすればよいでしょう?
例えば、ファイルからデータを読み込む場合には、必要なメモリサイズが異なるのが普通です。そのような時にはファイルの大きさに従って、適切なサイズのメモリ領域を用意しなければなりません。予め、十分に大きなサイズの配列を静的に宣言しておく、というのも一つの方法ですが、これはメモリの無駄遣いであり、また融通の利かない方法です。
プログラムの実行途中に、「必要な時に、必要な分だけメモリ領域を確保する」やり方を「動的」なメモリ確保と呼びます。上述のように必要なメモリサイズが始めは分からない場合や、実行中に一時的に大きなデータを格納するための領域が必要な場合などにしばしば使われる方法です。
6.1 malloc関数
メモリを動的に確保する際はmalloc関数を使います。stdlib.hというヘッダファイルで宣言されています。
<例6−1>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int
*ip; /*
割り振られた領域のアドレスを格納するためのint型のポインタipを宣言 */
int
i, n;
/*
確保する要素数を入力 */
printf("Input
a number : ");
scanf("%d",
&n);
ip
= (int *)malloc(n * sizeof(int)); /*
メモリ領域の確保 */
for
(i=0; i<n; i++)
ip[i]
= i;
for
(i=0;i < n; i++)
printf("%d
", ip[i]);
printf("\n");
free(ip); /* 確保したメモリ領域の解放 */
return
0;
}
[解説] malloc関数の返却値
この例では、キーボードから入力した要素数nを持つint型配列ipのためのメモリ領域を、動的に確保しています。
宣言時には、そのメモリ領域のアドレスを格納するためのポインタ変数を宣言しておくだけです。
int *ip;
配列数が確定した後、必要なメモリ領域をmalloc関数で確保します。malloc関数には、必要なメモリサイズのバイト数を引数として渡します。要素数nからなるint型配列に必要なサイズは、n * sizeof(int)で計算できます。
ip = (int *)malloc(n *
sizeof(int));
malloc関数は、指定されたサイズのメモリ確保に成功すると、その先頭アドレスを返却値として返しますので、それをポインタで受け取ればよいわけです。
6.2 free関数
mallocで確保したメモリ領域が不要になったら、free関数を使ってその領域を他の変数が使えるように「開放」してやる必要があります。
free(ip);
free関数を使い忘れてもプログラム終了時には自動的に解放されますが、その間不必要にメモリを占有していることになるわけですから、用が済んだら速やかにfreeで解放するのが原則です。
【課題6−1】(プログラムを提出)
以下のプログラムでは、データ配列dataのそれぞれの配列要素を二乗した値を、静的に確保した配列resultにいれている。これを、resultのメモリ領域を動的に確保するようにする。
#include <stdio.h>
#define DATA_SIZE 5
int main(void) {
double
data[DATA_SIZE] = {1.1, 2.2, 3.3, 4.4, 5.5}; /*
データ配列 */
double
result[DATA_SIZE]; /*
結果格納用の配列 */
int
i;
/*
dataの各要素の値の二乗をresultの各要素に代入 */
for
(i = 0; i < DATA_SIZE; i++)
result[i]
= data[i] * data[i];
/*
resultの各要素の表示 */
for
(i = 0; i < DATA_SIZE; i++)
printf("%5.2f\n",
result[i]);
return
0;
}
【余力がある人は...】
「dataを受け取り、必要なメモリを確保した上で、そこに計算結果をいれ、その領域を指すポインタを返却値として返す」という関数を使ったプログラムにしてみる。
[解説] ポインタを返す関数
《プログラム例》
6.3 二次元データのためのメモリの動的確保
二次元データ(配列の配列)のためのメモリ領域を動的に確保するには、幾つか方法があります。下の例は、複数の文字列を格納するためのメモリを動的に確保するプログラムです。
<例6−2>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 5
int main(void) {
char
s[101];
char
*table[N]; /*
文字型のポインタの配列tableを宣言 */
int
i;
/*
配列tableの各要素(ポインタ)をNULLポインタで初期化 */
for
(i = 0; i < N; i++)
table[i]
= NULL;
for
(i = 0; i < N; i++) {
printf("%d:",
i+1);
scanf("%100s",
s); /* 文字列の入力 */
/*
入力文字列の長さ+1文字分のメモリを確保し、そのアドレスをポインタ配列tableの要素として記憶する */
table[i]=(char
*)malloc(sizeof(char)*(strlen(s)+1));
if
(table[i]==NULL) { /* エラー処理 */
printf("Failed
to allocate memory.\n");
goto
FREE;
}
strcpy(table[i],
s);
}
/*
すべての入力文字列の表示 */
for
(i = 0; i < N; i++)
printf("%s\n",
table[i]);
FREE: /* 確保したメモリ領域の開放 */
for
(i = 0; i < N; i++)
free(table[i]);
return
0;
}
[解説] ポインタ配列の初期化とエラー処理
この例では、まず「ポインタの配列」を宣言しています。
char *table[N];
そして、それぞれの要素(table[0], table[1]...)に、入力された各文字列の長さに応じて確保したメモリ領域を指すアドレスを格納しています。
table[i]=(char
*)malloc(sizeof(char)*(strlen(s)+1));
表示が終わった後、確保したすべての領域を開放します。
for (i = 0; i < N; i++)
free(table[i]);
上の例の方法では、N個より多い文字列は扱えない、という制限があります。「ポインタへのポインタ」を使うとより柔軟な対応ができます。
【課題6−2】(プログラムを提出)
<例6−2>では入力文字列の数(N)は固定である。これを「文字列の数nをキーボードから入力し、その後n個の文字列を取得する」というプログラムに変更する。
[解説] ポインタへのポインタ
《プログラム例》