苦しんで覚えるC言語

配列を自由自在に作る


配列の欠点


第13章では配列の使い方を説明しました。
この配列は、多量のデータの取り扱いに非常に有効な手段なのですが、
実はいくつかの欠点があり、いささか実用性が低いのです。

配列の最大の欠点は、要素数をプログラム中で変更出来ないことです。
配列を宣言する時に、要素数を定数で直接指定するしかありません。
実行中にユーザーに入力してもらい、その値を利用するようなことは出来ません。
変更できる環境
GCCというコンパイラでは独自の拡張により、
要素数をプログラム中で変更できるようになっています。
また、C99というより新しいC言語でも同様の機能が追加されています。
(C++ではできません)
このことは、様々な目的で動作するプログラムを作るのに不便です。
例えば、会社の社員の給料を管理するソフトを作る場合、
社員の給料を記憶する配列は社員人数分以上必要になります。

ところが、世の中には社員数人の会社から、数千人の会社まで様々です。
もし、要素数を10個にすると、11人以上の会社では使えませんし、
逆に、要素数を1万にすると、10人の会社では残りの9990個が無駄です。
その無駄な分にもメモリを使用するので、巨大なメモリの無駄となります。

この様に、配列の要素数は自由に変更することが出来ないため、
メモリを有効利用するのが難しく、実用性に欠けているのです。

目次に戻る

メモリの確保


前項では、配列は自由に要素数を変更出来ないため、不便であると説明しました。
その為、自由に配列を作るmalloc(エムアロック)関数が用意されています。
なお、malloc関数を使うには、<stdlib.h> を #include する必要があります。
malloc関数の使い方は、次の通りです。

ポインタ変数 = malloc(必要なメモリのバイトサイズ);
メモリを確保できなかった場合は NULL が返る。
返されるポインタ変数には、確保された配列の先頭アドレスが代入されるので、
これに[]演算子を使用すれば、配列と同様に使うことが出来ます。
大丈夫かな
もし、上の2行の意味がわからない人は、15章 からやり直して下さい。
C言語の要でもあるので、理解してから先に進んで下さい。
malloc関数で指定出来るのは、バイト単位のサイズなので、
任意の要素数の配列を確保するには、sizeof演算子を使用します。
なお、malloc関数で確保したメモリを、ヒープと呼ぶことがあります。
また、ヒープに確保された配列を、動的配列と呼ぶことがあります。

なお、メモリ確保に失敗するとNULLが返されます。
これをそのまま使用すると当然強制終了するので、
malloc関数の戻り値は必ずチェックする必要があります。

ただ、わずかなメモリ確保に失敗した場合は、
システム全体が深刻なメモリ不足で今にもフリーズ、
ということなので、強制終了以外の対策はありません。
ヒープ
長期的に使用される大きなサイズのメモリを格納する領域。
動的配列
malloc関数などを使用して、
プログラムの実行中に用意された任意のサイズの配列。
malloc関数によって確保されたメモリは、プログラムが終了するまで残りますが、
そのメモリが不要になった場合、free(フリー)関数を使って解放します。
これを忘れると、無駄なメモリが残り続けることになるため、
malloc関数を使ったら、必ずfree関数を呼び出します。
free関数の使い方は次の通りです。

free(ポインタ変数);
終了時のfree関数
free関数は必ず呼び出すと説明しましたが、例外もあります。
プログラムが終了する直前では、free関数を使わなくても、
プログラム終了と同時にOSがメモリを解放します。
しかし、free関数 は常に呼び出す癖をつけておいて下さい。
だんだん動作が遅くなる原因
皆さんも、長時間コンピュータを使っているうちに、
だんだん動作が遅くなってきて、再起動することになる、という経験があると思います。
実は、あの現象の原因が、まさにfree関数の呼び出し忘れなのです。
  
極めて大規模なプログラムでは、あちこちでmalloc関数を使うことになるので、
常に正しくfree関数を呼び出すのは非常に困難な問題となっており、
市販され世界中で使われているようなアプリケーションでも、どこかで呼び出し忘れがあります。
なお、この呼び出し忘れによってメモリが無駄になることをメモリリークと呼びます。
ポインタ変数には、malloc関数の戻り値を格納したポインタ変数を指定します。

次のプログラムは、int型の要素数10個の配列を動的に確保します。

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

int main()
{
	int i;
	int *heap;
	heap = (int *)malloc(sizeof(int) * 10);
	if (heap == NULL) exit(0);

	for (i = 0;i < 10;i++) {
		heap[i] = i;
	}
	printf("%d\n",heap[5]);

	free(heap);

	return 0;
}
sizeof(int)によって、int型変数1つのバイト単位のサイズが求められるので、
それを10倍することで、int型変数10個分のメモリを確保しています。

malloc関数が返すアドレスは、void型のポインタです。
この型は、どんなポインタ変数にも代入出来るという型なので、
本当は(int *)にキャストする必要はないのですが、
C++コンパイラではキャストしないとエラーがでます。

メモリ確保に失敗した場合はexit関数を呼び出して強制終了します。
exit関数はプログラムを終了させる関数です。
なお、exit関数を使うには、<stdlib.h> を #include する必要があります。
ちなみに、エラーによる強制終了の時はabort関数を使うこともあります。

確保した配列を使い終わったら、free関数を呼び出して解放します。
malloc関数の実態
malloc関数は、好きなように好きなサイズの動的配列を作ることが出来るので、
非常に便利ですが、実は、その仕組みは、メモリにマークをつけているだけです。

これは、冷蔵庫に入っているお菓子に名前を書いておくのと同じことで、
家族みんながその名前に従い、他人のお菓子を食べなければ問題はありませんが、
勘違いによって他の人にお菓子が食べられてしまう可能性は十分あります。

malloc関数にも似たような性質があり、うまく使うのは意外に難しいのです。
従って、プログラムの時は、出来る限り普通の配列を使うようにして、
どうしても必要な部分だけmalloc関数を使うようにした方が良いでしょう。

目次に戻る

動的配列の要素数を拡大する


malloc関数によって、好きな要素数の動的配列を作ることが出来ます。
しかし、これでは、最初に述べた、配列の要素数を変更出来ない、
という問題は、完全に解決されたとは言いません。
そこで、要素数を変更する、realloc(リアロック)関数が用意されています。
realloc関数の使い方は、次の通りです。

新しいポインタ変数 = realloc(以前のポインタ変数,必要なメモリのバイトサイズ);
以前のポインタ変数には、malloc関数で確保したメモリのアドレスを指定します。
realloc関数は、中身を維持したまま、新しいサイズのメモリを確保します。
新しいポインタ変数には、拡張されたメモリのアドレスが返されますが、
特別な理由がなければ、以前のポインタ変数と同じ変数を指定出来ます。

次のプログラムは、realloc関数で動的配列の要素数を変更します。

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

int main(void)
{
	  int *heap;
	  heap = (int *)malloc(sizeof(int) * 10);
	  heap = (int *)realloc(heap,sizeof(int) * 100);
	  free(heap);
	  return 0;
}
realloc関数で、要素数を10個から100個に増加しています。
呼び出し回数を減らそう
realloc関数を何回も呼び出すとメモリが散らかってきます。
この様な状態をフラグメンテーションと呼び、不安定になります。
初めのmalloc関数である程度大きめに確保しておき、
realloc関数を呼び出す場合も、一回で大きめに確保するべきです。

目次に戻る