C言語編 第34章 ポインタC(動的なメモリ@)

先頭へ戻る

このエントリーをはてなブックマークに追加

この章の概要

この章の概要です。

記憶域期間

第22章でスコープの説明をした際、少しだけ登場した言葉に、 自動記憶域期間、静的記憶域期間というものがあります。 これらの用語は、ある変数がメモリ上に存在している期間を表しています。

自動記憶域期間は、staticキーワードの付いていない通常のローカル変数が該当します。 これは、あるスコープ内で生成され、そのスコープを抜け出すときに自動的に解体されることを示しています。

静的記憶域期間は、staticキーワードの付いたローカル変数や、(static の有無を問わず)グローバル変数が該当します。 これは、プログラムの実行が開始されてから、実行が終了するまでの間、ずっとメモリ上に存在し続けることを示しています。

本章で説明するのは、これらに続く3つ目の考え方である動的記憶域期間というものです。 これは、プログラマの自由意思によって、好きなタイミングで生成し、好きなタイミングで解体できることを示しています。

メモリの使われ方

変数の実体は、メモリ上に存在しますが、記憶域期間の違いによって、メモリの使われ方は異なります。

自動記憶域期間を持つ変数は、通常、メモリ内のスタック領域と呼ばれる場所に配置されます。
スタック領域は、メモリの中の比較的狭い範囲に固定的に用意されており、ここを使いまわすように利用されます。
使いまわすというのが1つのポイントで、ある関数の中で定義されたローカル変数のアドレスと、異なる関数の中のローカル変数が、 同じアドレスに配置される可能性もあります。 逆に、同じローカル変数でも、その関数を呼び出すたびに、異なるアドレスに配置される可能性もあります。

静的記憶域期間を持つ変数は、メモリ内の専用の領域に配置されます。 この変数は、プログラムの開始後、終了するまで消えることがないので、アドレスは常に固定です。
必要なサイズの合計値は、リンクまでの過程の中で判断できるため、無駄な領域が出来ないようにきっちりと制御されます。

動的記憶域期間を持つ変数は、メモリ内のヒープ領域に配置されます。
この領域を使うためには、OS に「これだけの大きさの領域が欲しい」という要求を出さなくてはなりません。 OS は要求に応じて必要な領域を確保し、そのアドレスを返してくれます。
また、もう必要なくなったときには、再び OS に「この領域はもういらない」と伝えます。 すると OS は、その領域を誰にも使われていない状態に戻します。
このようにメモリを確保することを、動的メモリ割り当てとか、ダイナミックアロケーションなどと呼びます。 また、使い終わったときにメモリを返却する過程を単純に、メモリを解放すると表現します。

動的メモリ割り当ては、正しく扱うのがなかなか難しいものです。 例えば、使い終わったメモリ領域は、忘れずに解放しなければなりません。 これを忘れると、メモリ上にはいつまでも無駄な領域が残ったままになってしまいます。

実際のところ、これはC言語ならではの事情とも言えます。 最近の主流のプログラミング言語の多くは、解放を自動的に行う仕組みが備わっています。 しかしながら、動的に確保したメモリを OS に返却しなければならないという事情は、言語うんぬんではなく、 コンピュータのメモリの仕組みの範疇なので、いつでも同じことです。

malloc関数

では実際に、動的メモリ割り当てを体験してみましょう。 まずは、malloc関数(⇒リファレンス)を紹介します。

malloc関数は、stdlib.h に次のように宣言されています。

void* malloc(size_t size);

引数には、要求するサイズを指定します。 size_t型は、sizeof演算子が返す型にもなっており、名前の通りサイズを表すための型です。 これは、符号無し整数ですが、その大きさに関しての規定はありません(大抵の環境では unsigned long型の typedef として定義されています)。
なお、0 を指定した場合の挙動は環境依存です。

戻り値は、確保されたメモリ領域の先頭アドレスです。 もし何らかの要因で(普通は、メモリ不足です)確保に失敗した場合には、NULL が返されます。
void*型は、第31章で軽く紹介した汎用ポインタと呼ばれるものです。 malloc関数は、いかなる型のための領域でも確保できなければならないため、このように汎用的なポインタ型が使われます。

malloc関数によって確保されたメモリ領域は、通常のローカル変数同様、不定な状態になっています。 よって、このまま初期化せずに値を参照してはいけません。

ここまでを踏まえて、double型の変数のための領域を確保する例を挙げます。

double* p = malloc( sizeof(double) );
if( p == NULL ){
	exit( EXIT_FAILURE );
}
*p = 10.5;
printf( "%f\n", *p );

malloc関数の実引数は、必ず sizeof演算子を使って取得した値を使うべきです。 char型の場合は必ず 1 なので、この必要はないのですが、それでもやはり sizeof(char) と書く方が分かりやすいと言えます。

戻り値は、そのまま素直にポインタ変数で受け取れます。 しかし、わざわざ、次のようにキャストしているコードも多いです。

double* p = (double*)malloc( sizeof(double) );

これは C++ ではキャストが必要なことが理由の1つですが、 C言語では、void*型から他のポインタ型へは暗黙的に変換できますから、キャストは不要です。

「理由の1つ」と書きましたが、他の理由には、昔の malloc関数の戻り値は char*型であったという歴史的な経緯もあるようです。 また、C++ で動的なメモリを確保する場合には、malloc関数よりも new演算子を使うべきです。

戻り値が NULL であった場合の対処には、様々な考え方があります。
NULL が返されることを考慮していないプログラムは大量に存在しています。 これは、メモリ不足に陥ること自体が珍しい状況であるため、起こり得ないだろうという安易な考えに基づくものです。
また、メモリ確保に失敗したとき、プログラムで出来ることは、実行を終了させることぐらいしかないという意味もあります。 先ほどの例では、exit関数(⇒リファレンス)で終了させています。
メモリ確保の失敗を調べるにしても、malloc関数を呼ぶたびにチェックするのは面倒なので、 独自の関数を用意することもあります。

void* xmalloc(size_t size)
{
	void* p = malloc( size );
	if( p == NULL ){
		exit( EXIT_FAILURE );
	}
	return p;
}

このような関数を用意し、常にこちらを使うようにしていれば、後からメモリ確保失敗時の対応の方針を変えることも容易になります。 こうやって、既存の関数を包み込むような独自関数を実装することを、一般に「関数をラップする」と表現します。 これは重要なテクニックです。


概要だけでものすごく長くなっていますが、もう少しお付き合い下さい。
動的に確保されたメモリ領域は、使い終わったら解放しなければならないのでした。 解放には、free関数(⇒リファレンス)を使います。

void free(void* ptr);

引数には、malloc関数が返したポインタを渡します。 なお、NULL を渡した場合には、何も起こらないことが保証されています。 また、動的に確保した領域以外のアドレスを渡してはなりません

free関数で解放された領域にあったデータがどうなるかは不定です。 大抵の環境では、そのまま残ったままになっていることが多いですが、これを当てにすることは出来ませんし、当てにしてはいけません。
また、一度解放した領域を、再度解放しようとしてもいけません

free関数で解放することを忘れても、大抵の環境では、プログラム終了時に自動的に解放されます。 ただし、C言語の立場としては、これは必ずそうであるとは言えません。 一種の保険という程度に考えておいて、プログラム内でしっかり解放することを勧めます。


さて、それでは実際のプログラムを見てみましょう。

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

int main(void)
{
	int* num_ptr;

	num_ptr = malloc( sizeof(int) );
	if( num_ptr == NULL ){
		exit( EXIT_FAILURE );
	}

	*num_ptr = 123;
	printf( "%d\n", *num_ptr );

	free( num_ptr );

	return 0;
}

実行結果:

123

プログラム例を示しておいて何ですが、int型変数 1個のために動的メモリを使うなんて馬鹿げています。 これは何の価値もない行為です。

動的メモリを使う価値がある場面は、どれだけの大きさがあれば十分足りるかが、プログラムを実行してみないと分らないようなときです。 例えば、標準入力からデータを受け取る際、そのデータ総数が何個あるか不明な場合などです。 こういう場合は、配列のために動的なメモリ割り当てを行うことになります。

配列を確保する場合には、配列全体のサイズを実引数にします。 80文字の文字列のために確保するのであれば、

char* str = malloc( sizeof(char) * 80 );

このように使います。 後で説明しますが、配列のための領域を確保する場合には、calloc関数を使う方法もあります。

配列を動的に確保する例を挙げます。

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

int main(void)
{
	char buf[40];
	int dataNum;
	int* dataArray;
	int i;


	/* データ件数を受け取る */
	puts( "データの件数を入力して下さい。" );
	fgets( buf, sizeof(buf), stdin );
	sscanf( buf, "%d", &dataNum );

	/* データ件数が 0件以下なら終了 */
	if( dataNum <= 0 ){
		return 0;
	}

	/* 件数に合わせて、領域を確保する */
	dataArray = malloc( sizeof(int) * dataNum );
	if( dataArray == NULL ){
		exit( EXIT_FAILURE );
	}

	/* データを受け取る */
	for( i = 0; i < dataNum; ++i ){
		puts( "データを入力して下さい。" );
		fgets( buf, sizeof(buf), stdin );
		sscanf( buf, "%d", &dataArray[i] );
	}

	/* 結果を出力 */
	for( i = 0; i < dataNum; ++i ){
		printf( "%d: %d\n", i+1, dataArray[i] );
	}

	free( dataArray );

	return 0;
}

実行結果:

データの件数を入力して下さい。
5
データを入力して下さい。
35
データを入力して下さい。
-50
データを入力して下さい。
95
データを入力して下さい。
20
データを入力して下さい。
-45
1: 35
2: -50
3: 95
4: 20
5: -45

このプログラムの場合、入力されるデータの総数が毎回変わりうるという状況を想定しています。 このように、プログラム作成時点ではデータ数が不明な場合、静的に領域を用意することは難しいといえます。
もし用意する領域が少な過ぎれば、膨大な量の入力に対応できないことになってしまいます。 逆に、膨大な量の入力に備えて巨大な配列を作ると、データ数が非常に少ない場合、ほとんどのメモリが無駄になってしまいます。

C99 (可変長配列)

C99 では、可変長配列という機能が追加されています。 これは、配列宣言時に、要素数を定数でなく変数で指定できるというものです。

VisualC++ 2013/2015/2017 は、この機能をサポートしていません。 Xcode では使用できます。

可変長配列を使って、先ほどのサンプルプログラムを書き換えると、次のようになります。

#include <stdio.h>

int main(void)
{
	char buf[40];
	int dataNum;
	int i;


	/* データ件数を受け取る */
	puts( "データの件数を入力して下さい。" );
	fgets( buf, sizeof(buf), stdin );
	sscanf( buf, "%d", &dataNum );

	/* データ件数が 0件以下なら終了 */
	if( dataNum <= 0 ){
		return 0;
	}

	/* 件数に合わせた大きさの可変長配列を宣言 */
	int dataArray[dataNum];

	/* データを受け取る */
	for( i = 0; i < dataNum; ++i ){
		puts( "データを入力して下さい。" );
		fgets( buf, sizeof(buf), stdin );
		sscanf( buf, "%d", &dataArray[i] );
	}

	/* 結果を出力 */
	for( i = 0; i < dataNum; ++i ){
		printf( "%d: %d\n", i+1, dataArray[i] );
	}

	return 0;
}

実行結果:

データの件数を入力して下さい。
5
データを入力して下さい。
35
データを入力して下さい。
-50
データを入力して下さい。
95
データを入力して下さい。
20
データを入力して下さい。
-45
1: 35
2: -50
3: 95
4: 20
5: -45

C99 では、変数宣言をブロックの先頭で行わなければならないというルールは無くなっている(第22章参照)ので、 本当に必要な要素数が分かってから、可変長配列を宣言すれば良い訳です。 実際に可変長配列がメモリ上に確保されるのは、その宣言箇所を通過するときです。

malloc関数を使わなくなったので、free関数の呼び出しもなくなっています。 つまり、解放忘れの心配もなくなったということです。


静的に作られた配列と、動的に作られた配列は完全に別物であることも意識しておくべきです。 動的な配列は、確かにメモリ上のどこかに配列の形で存在しているものの、実際にはポインタを通して扱うことになります。
しかしながら、普通に配列を扱う場合でも、暗黙的にポインタに変換されているのですから、使用感はほとんど変わりません。

また、ポインタを通して扱うという事情から、動的な配列は、sizeof演算子を使って配列サイズを知ることができない点にも注意すべきです。

char static_array[100];
char* dynamic_array = malloc( sizeof(char) * 100 );

printf( "%d\n", (int)sizeof(static_array) );   /* 100 */
printf( "%d\n", (int)sizeof(dynamic_array) );  /* 4 */

ポインタ経由で扱う以上、sizeof演算子が返す値は、常にポインタ変数のサイズにしかならない訳です。 このため、配列の要素数を調べる SIZE_OF_ARRAYマクロのようなマクロを用意していても、動的な配列には使用できません。
どうしてもサイズを知る必要がある場合には、管理が煩雑になりますが、 malloc関数を呼び出したついでに、別の変数にサイズを記憶させておくしかありません。

calloc関数

配列の動的な確保に関しては、calloc関数(⇒リファレンス)を使う方法もあります。

void* calloc(size_t n, size_t size);

第1引数に要素数を、第2引数に1要素のサイズを指定します。 戻り値は、malloc関数と同様に、動的確保された領域のアドレスが返され、失敗すると NULL になります。

malloc関数と異なり、確保された領域が自動的にゼロクリアされます

ゼロクリアということは、メモリ上に 0 というビットが並ぶ訳ですが、これが常に適切な値であるとは限りません。 特に、浮動小数点型の場合、一部の環境では、「全ビットが 0」=「0.0」とはならないかも知れません。

なお、calloc関数で確保した領域も、free関数で解放します。

次の malloc関数を使ったコードと、calloc関数を使ったコードは、同じ結果になります。

buf = malloc( sizeof(int) * 1000 );
memset( buf, 0, sizeof(int) * 1000 );

buf = calloc( 1000, sizeof(int) );

memset関数(⇒リファレンス)は、 指定されたメモリ領域を、指定した 1Byte の値で埋め尽くす標準関数です。

注意事項のまとめ

malloc関数、calloc関数、free関数を正しく使うには、色々と注意すべき点があります。 慣れるまで大変なので、ここにまとめておきます。

malloc関数に関して、

free関数に関して、

これだけのことを考慮するのは、結構、骨が折れます。 労力を減らすために有効な手法の1つとして、次のような解放マクロを定義しておくというものがあります。

#define SAFE_FREE(ptr)		if( ptr != NULL ){ free(ptr); ptr = NULL; }

free関数を直接呼び出す代わりに、このマクロを使って解放するようにすれば、解放後に、ポインタ変数は NULL で上書きされます。 ちなみに、if文での NULLチェックは本来は不要です( free関数に NULL を渡しても何も起こらない)。

このマクロを使うことで、

という利点が生まれます。 ただ、同じ領域を指し示すポインタが 2つ以上存在するような状況を作ってしまっていると、これだけで確実とはいえません。


また、動的なメモリ割り当てを覚えると、やたらと使いたがる人もいるようですが、お勧めできません。

まず、ここまでの内容を見ても明らかなように、バグを作ってしまう可能性が一気に増大します。 実際、プログラムのバグの原因として、動的なメモリの扱いが絡んでいる可能性は非常に高いです。
また、動的なメモリ割り当ては、速度面でも不利です。 確保も解放も、かなり時間のかかる処理なのです。
そして、実はメモリの利用効率も、考えているほど優れている訳ではありません。 例えば、8バイトの領域を確保したつもりでも、実際には OS側の都合などで、管理情報(数十Byte程度)が付加されるため、 普通はもっとずっと多くの領域を消費しています。
ある程度まとまったサイズの確保であれば、管理情報のサイズは無視できる程度になりますが、 細々した領域を大量に確保するような使い方はやめた方が良いです。

このような理由から、動的なメモリ割り当ては、明確な必要性が無い限りは避けるべきです。


練習問題

問題@ 次のプログラムの誤りを指摘して下さい。(メモリ不足への対策を行っていない点は無視して下さい)

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

int main(void)
{
	long* value;

	value = malloc( sizeof(long) );

	value = 100;
	printf( "%d\n", *value );
	
	free( value );

	return 0;
}

問題A 次のプログラムの誤りを指摘して下さい。(メモリ不足への対策を行っていない点は無視して下さい)

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

#define NAME_LEN          64        /* 名前の最大長 */
#define PHONE_NUMBER_LEN  16        /* 電話番号の最大長 */

/* 個人情報 */
typedef struct {
	char	name[NAME_LEN];                 /* 名前 */
	char	phone_number[PHONE_NUMBER_LEN]; /* 電話番号 */
} PersonalInfo;

int main(void)
{
	char buf[40];
	int dataNum;
	PersonalInfo* infoArray;
	int i;


	/* データ件数を受け取る */
	puts( "データの件数を入力して下さい。" );
	fgets( buf, sizeof(buf), stdin );
	sscanf( buf, "%d", &dataNum );

	/* データ件数が 0件以下なら終了 */
	if( dataNum <= 0 ){
		return 0;
	}

	/* 件数に合わせて、領域を確保する */
	infoArray = malloc( sizeof(PersonalInfo) * dataNum );
	if( infoArray == NULL ){
		exit( EXIT_FAILURE );
	}

	/* データを受け取る */
	for( i = 0; i < dataNum; ++i ){
		puts( "名前を入力して下さい。" );
		fgets( buf, sizeof(buf), stdin );
		sscanf( buf, "%s", infoArray[i].name );

		puts( "電話番号を入力して下さい。" );
		fgets( buf, sizeof(buf), stdin );
		sscanf( buf, "%s", infoArray[i].phone_number );
	}

	/* 特に手を加えないので、領域を解放する */
	free( infoArray );

	/* 結果を出力 */
	for( i = 0; i < dataNum; ++i ){
		printf( "%s: %s\n", infoArray[i].name, infoArray[i].phone_number );
	}

	return 0;
}

問題B 次のプログラムの誤りを指摘して下さい。(メモリ不足への対策を行っていない点は無視して下さい)

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

void SetSequentialNumber(int* array, size_t size);
void PrintArray(int* array, size_t size);

int main(void)
{
	int* values;
	size_t size;


	size = 50;
	values = malloc( sizeof(int) * size );
	SetSequentialNumber( values, size );
	PrintArray( values, size );

	size = 100;
	values = calloc( size, sizeof(int) );
	SetSequentialNumber( values, size );
	PrintArray( values, size );

	free( values );

	return 0;
}

/*
	配列に連番をセットする。
	引数:
		array:	対象配列のアドレス。
		size:	対象配列の要素数。
*/
void SetSequentialNumber(int* array, size_t size)
{
	size_t i;

	for( i = 0; i < size; ++i ){
		array[i] = i;
	}
}

/*
	配列の中身を標準出力へ出力する。
	引数:
		array:	対象配列のアドレス。
		size:	対象配列の要素数。
*/
void PrintArray(int* array, size_t size)
{
	size_t i;

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


解答ページはこちら

参考リンク

更新履歴

'2017/7/30 clang 3.7 (Xcode 7.3) を、Xcode 8.3.3 に置き換え。

'2017/3/25 VisualC++ 2017 に対応。

'2016/10/15 clang の対応バージョンを 3.7 に更新。

'2015/10/12 clang の対応バージョンを 3.4 に更新。

'2015/9/5 VisualC++ 2012 の対応終了。

'2015/8/18 VisualC++ 2010 の対応終了。

'2015/8/15 VisualC++ 2015 に対応。

'2014/10/18 clang 3.2 に対応。

'2014/2/1 VisualC++ 2013 に対応。

'2014/1/14 VisualC++ 2008 の対応終了。

'2014/1/12 clang 3.0 に対応。

'2013/4/21 「C99 (可変長配列)」の項を追加。

'2011/9/3 calloc関数の実引数の順番が逆になっていたのを修正。

'2009/12/11 新規作成。



前の章へ

次の章へ

C言語編のトップページへ

Programming Place Plus のトップページへ