「メモリの動的な確保と解放」の版間の差分

提供: C言語入門
移動: 案内検索
(ページの作成:「C言語では、グローバル変数、自動変数(ローカル変数)以外に動的に確保するメモリをしようします。malloc,calloc,allocaなどの...」)
 
行10: 行10:
  
 
== 概要 ==
 
== 概要 ==
 +
ここでは、以下の点について説明します。
 +
* メモリを動的に確保するメリット
 +
* メモリを操作する関数
 +
* 動的メモリにおける注意点
 +
* サンプルコード
 +
== なぜメモリを動的に確保するのか ==
 +
なぜ、メモリを動的に確保する必要があるのでしょうか?
 +
はじめから必要なメモリの量はわかりますか?
 +
プログラムの果たしたい目的によって、メモリを動的に確保しなければならないかどうか、決まります。
 +
 +
必ず、プログラムが起動してすぐに、10MBのメモリを使用して、プログラムがすぐに終了してしまうなら、メモリをmallocで確保する必要がないでしょう。自動変数、グローバル変数で十分でしょう。
 +
 +
malloc/freeを使うと何が嬉しいかというと、 必要な量を必要なときに確保することができます。
 +
例えば、通信プログラムは、クライアントが接続してきたときに、クライアントとのメッセージ交換に使うメッセージ用のバッファを動的に確保し、クライアントが切断したら、そのメッセージバッファは不要になるため、解放することができます。
 +
 +
処理する最大サイズが10MBだけど、いつも10MB必要ではない、プログラムがあったとして、ずっと10MBのメモリを専有し続けるよりも、10MBが必要になったら、10MBのメモリを確保し、3MBのメモリでいいなら、10MBではなく、3MBのメモリだけ確保すればよいのです。
 +
 +
扱いデータの個数やサイズが不明なときや、状況に応じて異なる場合には、動的なメモリの確保と解放のほうが、柔軟にメモリを使用できます。
 +
== malloc系関数とは ==
 
malloc()系(memory allocation)の関数は、第1引数に必要なサイズを指定し、確保したメモリへのポインタを返します。
 
malloc()系(memory allocation)の関数は、第1引数に必要なサイズを指定し、確保したメモリへのポインタを返します。
 
メモリのサイズの指定には、sizeof()を使用して「型」のサイズを調べて、必要な数を掛けあわて、サイズを決めることができます。malloc/free は、C言語の標準ライブラリで提供される関数です。
 
メモリのサイズの指定には、sizeof()を使用して「型」のサイズを調べて、必要な数を掛けあわて、サイズを決めることができます。malloc/free は、C言語の標準ライブラリで提供される関数です。

2014年4月6日 (日) 17:58時点における版

C言語では、グローバル変数、自動変数(ローカル変数)以外に動的に確保するメモリをしようします。malloc,calloc,allocaなどの関数でメモリを確保し、freeでメモリを解放できます。不要になったメモリは、解放しなければなりません。

読み方

malloc
まろっく、えむあろっく
calloc
かろっく
alloca
あろか
realloc
りあろっく
free
ふりー

概要

ここでは、以下の点について説明します。

  • メモリを動的に確保するメリット
  • メモリを操作する関数
  • 動的メモリにおける注意点
  • サンプルコード

なぜメモリを動的に確保するのか

なぜ、メモリを動的に確保する必要があるのでしょうか? はじめから必要なメモリの量はわかりますか? プログラムの果たしたい目的によって、メモリを動的に確保しなければならないかどうか、決まります。

必ず、プログラムが起動してすぐに、10MBのメモリを使用して、プログラムがすぐに終了してしまうなら、メモリをmallocで確保する必要がないでしょう。自動変数、グローバル変数で十分でしょう。

malloc/freeを使うと何が嬉しいかというと、 必要な量を必要なときに確保することができます。 例えば、通信プログラムは、クライアントが接続してきたときに、クライアントとのメッセージ交換に使うメッセージ用のバッファを動的に確保し、クライアントが切断したら、そのメッセージバッファは不要になるため、解放することができます。

処理する最大サイズが10MBだけど、いつも10MB必要ではない、プログラムがあったとして、ずっと10MBのメモリを専有し続けるよりも、10MBが必要になったら、10MBのメモリを確保し、3MBのメモリでいいなら、10MBではなく、3MBのメモリだけ確保すればよいのです。

扱いデータの個数やサイズが不明なときや、状況に応じて異なる場合には、動的なメモリの確保と解放のほうが、柔軟にメモリを使用できます。

malloc系関数とは

malloc()系(memory allocation)の関数は、第1引数に必要なサイズを指定し、確保したメモリへのポインタを返します。 メモリのサイズの指定には、sizeof()を使用して「型」のサイズを調べて、必要な数を掛けあわて、サイズを決めることができます。malloc/free は、C言語の標準ライブラリで提供される関数です。

メモリは、いくらでも確保できるわけではありません。いろいろな制約によって使用できるメモリ量はかわってきます。

  • OSの上限
  • プロセスの上限(OSなどによって制限されます)
  • 物理的な制限
    • 物理メモリサイズ
    • スワップのサイズ

C言語のmalloc/freeには、ガベージコレクション(GC)はありませんので、プログラムが自身でメモリを管理しなければなりません。

プロトタイプ

メモリを操作する関数のプロトタイプは以下の通りです。 malloc, calloc, realloc は、失敗したときに NULL を返します。

#include <stdlib.h>
 
void *
malloc(size_t size);
 
void *
calloc(size_t number, size_t size);
 
void *
realloc(void *ptr, size_t size);
 
void
free(void *ptr);
 
void *
alloca(size_t size);

malloc, calloc, alloca の違い

それぞれの関数の違いを説明します。

malloc,calloc,allocaの違い
関数名 説明
malloc メモリを動的に確保します。メモリ領域は、ゼロクリアされません。
calloc メモリを動的に確保します。確保したメモリを自動的にゼロクリアします。
alloca メモリをスタックフレームから割り当てます。スコープから外れるときに自動的に解放されます。明示的にfreeで解放してはいけません。
realloc メモリを動的に再確保します。渡されたポインタのメモリをコピーします。渡されたポインタの領域は解放します。

メモリの再確保 realloc

malloc()によって確保したメモリが足りなくなったときに、さらにメモリのサイズを大きくする必要があれば、realloc()を使って、メモリサイズを増やすことができます。 ただし、純粋に今確保している領域が後ろに伸びるわけではありません。 適当な場所にメモリを確保しなおし、そこにデータをコピーすることになります。 すでに確保しているメモリが大きいほど、コピーする量も多くなり、スピードが遅くなります。

realloc()の処理の内容を簡単に説明すると以下の流れになります。

  1. メモリの確保(旧領域)
  2. メモリの再確保
    1. メモリの確保(新領域)
    2. 旧領域から新領域へデータのコピー
    3. 旧領域の解放

realloc()は、確保した新しい領域へのアドレス(ポインタ)を返します。 reallocは、必ず成功するとは限りません。 realloc()は、失敗したとき NULL を返します。 そのため、第一引数に渡す、ポインタで、reallocの戻り値を受け取るべきではありません。

以下は、ダメなコードの例です。

p = malloc(10 * sizeof(int));		// (1)
p = realloc(p, 20 * sizeof(int));	// (2)

realloc()が失敗したとき、(1)で確保したメモリへのアドレスを失ってしまいます。 そうしたとき、(1)のアドレスを操作できなくなってしまいます。当然、free()もできません。

正しいコードは、以下の通りです。

int *p;
 
p = malloc(10 * sizeof(int));
if (! p) {
	err(EXIT_FAILURE, "can not malloc");
}
int *tmp;
tmp = realloc(p, 20 * sizeof(int));
if (! tmp) {
	free(p);
	err(EXIT_FAILURE, "can not realloc");
}
p = tmp;

不要なメモリは解放する

確保したメモリは、そのメモリが不要になったときに明示的にfree()しなければなりません。 malloc()で確保したメモリが不要になっても確保しつつ、メモリが必要になったときに、すでに持っているメモリを再利用せずに、さらにmalloc()でメモリを確保していくと、そのうち、システム上のメモリを使い果たしてしまうか、プロセスのメモリ上限に到達してしまい、プログラムがそれ以上メモリが確保できなくなったり、OSのOOM Killer(Out of Memory Killer)にプログラム(プロセス)を終了させられてしまいます。

メモリリーク

不要なメモリを解放せず、メモリを確保し続けて、システムのメモリをだんだん消費していく現象を「メモリリーク」と呼びます。 メモリリーク(Memory leak)は、プログラミングの一種のバグです。

スワップやスラッシング

OSの実装によるため、どのような条件で引き起こされるかは、環境によりますが、一般的なOSでは、実行中のプログラム達によってメモリを消費されていき、物理メモリが足りなくなると、OSはスワップを使いはじめます。OSは、ファイルシステムにメモリ上のデータを逃します。

スワップを使用するとOS、プログラムの動作が遅くなっていきます。 プログラムが必要なデータがスワップにある場合、一度、メモリに戻さなければなりません。メモリのデータをスワップにどかして、スワップからデータを読むといった動作が必要なります。 このメモリとスワップの入れ替えが激しく起きて、プログラムがほとんど停止状態に陥ることをスラッシング(thrashing)と呼びます。

メモリが足りない場合には

不要なメモリは解放し、それでも必要な物理メモリが足りず、スワップを使用してしまう場合には、サーバを増やして、処理を分散させるか(スケールアウト)、物理メモリを増設する(スケールアップ)などの対応が必要です。

動的メモリ確保のアルゴリズム

mallocなどの動的なメモリを確保するアルゴリズムには、いろいろなアルゴリズムがあります。 高速なアルゴリズムとして gperftoolsのtcmalloc があります。 簡単に既存のmallocと置き換えることができるため、パフォーマンスがメモリの動的確保にある場合には、tcmallocを利用することで、パフォーマンスの改善が期待できます。

プログラムが終了するときにメモリを解放するのか

「確保したメモリが不要になったら解放するべきである」と説明しました。 「プログラムが終了するときにメモリを解放する必要があるのか?」という疑問があるでしょうか?

OSの実装によりますが、たいていのOS(UnixやWindows)では、プログラムが終了するとき、OSによって、プログラムが使用していたリソースが自動的に解放されます。 そのため、プログラムが終了するとき、OSによってプログラムが持っていたメモリも一緒に解放されます。 だから、メモリを解放するためのロジックは、作成しておいたとしても、それを呼び出す必要はありません。

メモリを明示的に解放してもいいのですが、複雑なデータ構造をたどって、1つ1つのメモリを解放していくようなロジックを持っている場合、メモリの解放に時間が掛かります。OSに任せられるものは、任せてしまったほうが、OSのパフォーマンスやプログラムの終了処理が速くできる利点があります。

蛇足ですが、メモリの解放とは別に、OSがやってくれないメモリのデータをプログラムでディスクに書き出す必要がある場合には、メモリの解放ではなく、データの書き出しの処理はしなければなりません。

サンプルコード

ソースコード malloc.c

#include <stdio.h>
#include <stdlib.h>
#include <err.h>
int
main(int argc, char *argv[])
{
        int *p = (int*)malloc(sizeof(int)*4);
	if (NULL == p) {
		err(EXIT_FAILURE, "can not malloc");
	}
	free(p);
        exit(EXIT_SUCCESS);
}

ソースコード calloc.c

#include <stdio.h>
#include <stdlib.h>
#include <err.h>
int
main(int argc, char *argv[])
{
        int *p = (int*)calloc(sizeof(int)*4);
	if (NULL == p) {
		err(EXIT_FAILURE, "can not calloc");
	}
	free(p);
        exit(EXIT_SUCCESS);
}

ソースコード alloca.c

#include <stdio.h>
#include <stdlib.h>
#include <err.h>
int
main(int argc, char *argv[])
{
        int *p = (int*)alloca(sizeof(int)*4);
	if (NULL == p) {
		err(EXIT_FAILURE, "can not alloca");
	}
 
	// alloca は、freeしない。
        exit(EXIT_SUCCESS);
}

コンパイル

$ cc malloc.c
$ cc calloc.c
$ cc alloca.c

メモリの再確保

#include <stdio.h>
#include <stdlib.h>
#include <err.h>
 
int main(void)
{
        int *p;
        int i;
 
        p = malloc(10 * sizeof(int));
        if (! p) {
                err(EXIT_FAILURE, "can not malloc");
        }
        for(i=0;i<10;i++)*(p+i)=i;
 
        int *tmp;
        tmp = realloc(p, 20 * sizeof(int));	// 一度テンポラリで受け取る
        if (! tmp) {
                free(p);	// 失敗したら自分で解放する
                err(EXIT_FAILURE, "can not realloc");
        }
        p = tmp;
 
        for(i=10;i<20;i++)*(p+i)=i;
        for(i=0;i<20;i++)printf("%3d\n",*(p+i));
 
	free(p);	// プログラムが終了するので、解放しないでもOSがしてくれる。
 
        return 0;
}

メモリの再利用防止

解放したアドレスを誤って、再利用するのを防止するため、解放したアドレスを持つポインタには、NULLを入れてしまうのが好ましいです。

#define FREE(p)	{free(p);p=NULL}
 
void f(){
	int *p = (int*)malloc(sizeof(int)*3);
 
	// *p の使用
 
	FREE(p);
 
	// p はNULLになってるので、解放されたアドレスを再利用するバグはなくなる。
}

関連項目

  • メモリの動的な確保と解放