高度なプログラムを作成する上で必要になるメモリの動的確保について説明します。
メモリ
既に説明したように、プログラムの実行中に入力されたデータや変数などの情報は全て、コンピュータのメモリに保存されています。
さらに、メモリにはプログラムの命令などもあり、プログラムの実行とは、メモリに展開したプログラムを順に実行しているものと言えます。
高度なプログラムを書いていく上で、例えば配列の要素数を入力によって、決めたいということがあると思います。
しかし、現在の方法では配列の要素数は定数などのコンパイル時に要素数が確定していないと、コンパイルができず、プログラムを作成できません。
これは、配列の要素数はコンパイル時に決まっていないといけないというルールがあるためです。
なので、プログラム中にメモリを確保して、配列の要素数を決定するという方法を取る必要があります。
このようなメモリの確保をメモリの「動的確保」と言います。
動的確保
メモリを動的に確保するには、malloc関数を使います。
malloc関数には、確保したいメモリの数を、バイト(byte)数を指定します。
バイトとは、メモリの領域の単位で、環境によっても違いますが、int型であれば4バイトであることがほとんどです。
ここでは、int型の配列を malloc関数を使って、要素数100で作成します。
#include <stdlib.h>
int main(void)
{
int *a;
a = (int *)malloc(sizeof(int) * 100);
if (a == NULL) {
exit(1);
}
a[0] = 0;
free(a);
return 0;
}
malloc関数を使うには、stdlib.h をインクルードする必要があります。
「(int *)malloc(sizeof(int) * 100)」の部分で、メモリの動的確保をしています。
malloc関数の引数には、動的確保をするメモリの数を引数で指定するのですが、ここで「sizeof(int)」というものが登場します。
sizeof は演算子で、sizeof の右にあるもののバイト数を計算します。
sizeof(int) であれば、int型のバイト数が結果となります。
よって「sizeof(int) * 100」なので、要素数100の配列を用意します。
malloc関数の戻り値は、確保したメモリの先頭アドレスです。それを int型の配列ということで(int*)にキャストしています。メモリは連続した領域で確保するため、そのまま配列として使うことができます。
そのため、戻り値の代入先はポインタ変数となっています。
確保した後は、普段の配列と同じように使うことができます。
様々な要因がありますが、メモリの動的確保に失敗することがあります。
そのような場合、malloc関数の戻り値は、「何もない」ということを表す「NULL」という定数になります。
なので、失敗した時のために if文で条件分岐をして、exit関数を実行するようにしてあります。
exit関数は、stdlib.h をインクルードすることで使うことができ、main関数の終了とは別に、プログラムを止めるための関数で、引数には main関数の return 0; と同じように、プログラムの終了時にプログラムの呼び出し元に渡す値を値を指定します。
引数には、異常終了を表す1などの0以外の数字を指定します。
動的確保したメモリは、スコープが外れた時に自動的に解放されません。
そのため、プログラム中で解放する必要があります。
解放するには、free関数を使い、引数には動的確保したメモリのアドレスを指定します。
ちなみに、メモリを解放できずに残しておくと、メモリを無駄に使うことになり、バグとなります。
サイズ変更
動的確保したメモリの数を変更したい場合は、realloc関数を使用します。
#include <stdlib.h>
int main(void)
{
int *a;
int *b;
a = (int *)malloc(sizeof(int) * 100);
if (a == NULL) {
exit(1);
}
b = (int *)realloc(a, sizeof(int) * 150);
if (b == NULL) {
free(a);
exit(1);
}
free(b);
return 0;
}
上のソースコードに追加で、要素数100の配列を作成したら、その要素数を150に変更するソースコードです。
realloc関数は、stdlib.h をインクルードすることで使うことができます。
realloc関数の第一引数には、変更したい動的確保したメモリのアドレス、第二引数には、変更後のメモリの数を指定します。
realloc関数も、malloc関数と同じで、変更に失敗するとNULLを戻り値として返すので、if文で失敗した時には、プログラムを終了するようにしています。
変更に成功した場合は、第一引数のメモリが自動的に解放されるので、realloc関数の戻り値のメモリのみ free関数を使って解放するだけでいいのですが、失敗した場合は解放されないので、「free(a)」のように解放する必要があります。
メモリの数を増やすのは大丈夫なのですが、減らす時には減らした部分のデータが消えてしまうということを忘れないようにしましょう。
まとめ
何度も malloc関数や realloc関数を使うと、プログラムの実行時間が遅くなります。
そのため、配列の要素数をrelloc関数で毎回増やしたり減らしたりするのではなく、少し多く要素数を確保しておくことで、realloc関数の呼び出しを減らすようにするべきです。