「メモリの動的な確保と解放」の版間の差分
(ページの作成:「C言語では、グローバル変数、自動変数(ローカル変数)以外に動的に確保するメモリをしようします。malloc,calloc,allocaなどの...」) |
(相違点なし)
|
2014年4月6日 (日) 17:43時点における版
C言語では、グローバル変数、自動変数(ローカル変数)以外に動的に確保するメモリをしようします。malloc,calloc,allocaなどの関数でメモリを確保し、freeでメモリを解放できます。不要になったメモリは、解放しなければなりません。
読み方
- malloc
- まろっく、えむあろっく
- calloc
- かろっく
- alloca
- あろか
- realloc
- りあろっく
- free
- ふりー
目次
概要
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 | メモリをスタックフレームから割り当てます。スコープから外れるときに自動的に解放されます。明示的にfreeで解放してはいけません。 |
realloc | メモリを動的に再確保します。渡されたポインタのメモリをコピーします。渡されたポインタの領域は解放します。 |
メモリの再確保 realloc
malloc()によって確保したメモリが足りなくなったときに、さらにメモリのサイズを大きくする必要があれば、realloc()を使って、メモリサイズを増やすことができます。 ただし、純粋に今確保している領域が後ろに伸びるわけではありません。 適当な場所にメモリを確保しなおし、そこにデータをコピーすることになります。 すでに確保しているメモリが大きいほど、コピーする量も多くなり、スピードが遅くなります。
realloc()の処理の内容を簡単に説明すると以下の流れになります。
- メモリの確保(旧領域)
- メモリの再確保
- メモリの確保(新領域)
- 旧領域から新領域へデータのコピー
- 旧領域の解放
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になってるので、解放されたアドレスを再利用するバグはなくなる。 }
関連項目
- メモリの動的な確保と解放