malloc
提供: フリー百科事典『ウィキペディア(Wikipedia)』
malloc (マロック、エムアロック)はC言語の標準ライブラリで提供される動的メモリ確保を行う関数(サブルーチン)である。
目次 |
原理
C言語は通常メモリを「静的メモリ確保」か「自動メモリ確保」で管理する。静的変数は主記憶上にプログラムが存在する期間中ずっと確保されている。自動変数(局所変数)は通常コールスタック上に確保され、対応するサブルーチンが実行中の間だけ存在する。しかし、いずれの方法も限界があり、確保できるメモリ量(変数のサイズ)はコンパイル時に決められてしまう。必要なサイズが実行時でないと判明しない場合、例えばディスク上のファイルから任意のサイズのデータを読み込むような場合、固定サイズのデータオブジェクトだけでは不十分である。
確保されたメモリの生存期間(使用可能期間)も問題となる。静的でも自動的でも確保されたメモリの生存期間はあらゆる状況に対応できるものではない。スタック上のデータは複数の関数呼出をまたいで持続できないし、静的データは必要かどうかに関わらずプログラムが動作中ずっとメモリ領域を確保し続ける。しかし、そうではなく確保したメモリの生存期間をプログラマが自由にできる必要がある場合も多い。
これらの制限は動的メモリ確保を使用することで解決する。メモリの管理は明示的に行う必要が出てくるが、柔軟性が向上する。「ヒープ領域」はこのために用意されたメモリ領域である。C言語では、malloc関数を使ってヒープ領域からメモリブロックを確保する。プログラムはmallocの戻り値であるポインタを使って、そのメモリブロックにアクセスする。メモリブロックが不要になったら、そのポインタをfreeに渡して解放し、他の用途に再利用できるようにする。
C言語での動的メモリアロケーション
mallocは C 言語におけるヒープ領域からのメモリ確保に使われる基本関数である。その関数プロトタイプは以下のようになる。
void *malloc(size_t size)
ここで、size バイトのメモリが確保される。確保が成功するとそのメモリブロックへのポインタが返される。
mallocが返すのは、void型へのポインタ(void *)であり、そのポインタが指す領域のデータ型が不明であることを示している。このポインタは、代入時、暗黙的に型変換され、必要なデータ型へのポインタ型となる。上記のとおりANSI Cにおいては、mallocのリターン値は*void型なので明示的に変換(キャスト)する必要が無いにもかかわらず[1]、手動での型変換(キャスト)を行う人も存在する。明示的に型キャストする人がいるのは、C++ではエラーになるのと、かつてANSI以前の仕様のCにおいては、mallocは char * を返していたからである。
mallocで確保されたメモリは持続性がある。プログラム終了時か明示的にプログラマが解放しない限り存在し続ける。解放はfree関数で行われる。そのプロトタイプは次のようになる。
void free(void *pointer)
ここで、pointerの指すメモリブロックが解放される。
使用例
スタック上に10個の整数の配列を作成する一般的な方式は次の通りである:
<source lang="c"> int array[10]; </source>
同様の配列を動的に確保するには、以下のようなコードを使うことが出来る。
<source lang="c">
- include <stdlib.h>
/* 10個のintの配列のためのメモリを確保 */ int *ptr = malloc(sizeof (int) * 10); if (ptr == NULL)
exit(EXIT_FAILURE); /* メモリを確保できなかったので、exit */
/* 確保成功 */ </source>
関連する関数
mallocはメモリブロックを確保して返すが、その領域は初期化されていない。必要に応じてメモリは個別に初期化する。例えば、memset関数で初期化したり、個別の代入文で初期化する。他にもcalloc関数を使って、メモリ確保と初期化を行うこともできる。そのプロトタイプは以下のようになる。
void *calloc(size_t nelements, size_t bytes)
bytesのサイズのメモリ領域をnelements個格納できるメモリ領域を確保する。確保された領域はゼロで初期化される。
メモリブロックを大きくしたり小さくしたりできれば便利である。これはmallocとfreeを組み合わせて、新たなメモリブロックを確保して内容を前のメモリブロックからコピーし、前のメモリブロックを解放することで実現できる。しかし、この方法は回りくどい。代わりにrealloc関数を使うことが出来る。そのプロトタイプは以下の通りである。
void *realloc(void *pointer, size_t bytes)
reallocは指定されたサイズのメモリ領域へのポインタを返す。新しいサイズが前のサイズより大きければブロックは大きくされ、逆ならば小さくされる。
vallocはmallocとほとんど同じだが、メモリ確保がページ境界になる点が異なる。vallocで確保された領域へのポインタをreallocに渡すことはできない[1]。また、これは標準Cライブラリに含まれる関数ではない。
一般的なエラー
mallocや関連するC言語の関数の使用はバグの発生源になりやすい。
確保エラー
mallocは必ず成功するとは限らない。利用可能な空きメモリ領域がないとき、プログラムが限界値を超えてメモリを使用しようとしたときなど、mallocは ヌルポインタを返す。環境によって、このような状況の起きる可能性は異なる。多くのプログラムはmallocが失敗することを考慮していない。そのようなプログラムでmallocがヌルポインタを返してきたとき、プログラムはNULLに相当するアドレスにアクセスしてクラッシュするだろう。これは昔から設計上のミスとされているが、未だにそのようなプログラムが多い[2]。というのもメモリ確保に失敗するという状況は非常に珍しく、発生した場合には終了する以外にプログラミング上できることがない[3]からでもある。確保失敗をチェックすることはライブラリでは特に重要である。ライブラリはメモリ量が限られた状況でも使用される可能性がある。ライブラリ内でメモリ確保に失敗した場合、呼び出したアプリケーション側にエラーを通知して、アプリケーションプログラムに判断を委ねるのがよいとされる。
メモリリーク
mallocで確保したメモリブロックを使用しなくなってもfreeで解放せず、次々と新たなメモリブロックを確保していると空きメモリが少なくなってくる。これをメモリリークと呼ぶ。メモリリークによるメモリ消費が無視できない量に達するとページ置換アルゴリズムによってページアウトが発生し、システム性能が低下する。さらに仮想記憶の容量限界に達すると、システム内の他の全プロセスでメモリ確保が失敗してシステムがストールする。メモリリークは、利用のたびにプロセスの起動・終了が行われるプログラムにおいて、特に重要な問題を引き起こさないため、メモリリークが混入した状態で出荷される商用プログラムは多い。しかしながら、連続稼動が要求されるサーバコンピュータ上のプログラム(サービス、デーモン)や、組み込みプログラム等の場合は、メモリリークは死活問題とされる。
解放後の使用
ポインタがfreeに渡された後でその領域への参照を行っても、その内容は未定義であり利用できない。しかし、ポインタ自体が残っていると使ってしまうことがある。次のコードはその例である。
<source lang="c"> int *ptr = malloc(sizeof *ptr); free(ptr);
- ptr = 0; /* 何が起きるかわからない! */
</source>
このようなコードは予測不能の振る舞いをする。メモリが解放された後でシステムがその領域を他の用途に転用しているかもしれない。従って解放済みメモリ領域へのポインタを使った書き込みはプログラム内の別のデータを不正に書き換えてしまう。どういうデータを書き換えたかによって、その後のプログラムの動作は単なるデータ破壊で済むかもしれないし、クラッシュするかもしれない。特に破壊的なバグとしては、同じポインタを2回freeに渡してしまうことで、これを「二重解放; double free」と呼ぶ。これを防ぐため、解放した後でポインタ変数にNULLを格納することがある。というのもfree(NULL)は何もしないのである。
実装
メモリ管理の実装はオペレーティングシステム (OS)と(ハードウェア)アーキテクチャに大きく依存する。OSによってはmallocのためのアロケータを提供しているし、データ領域の制御関数を提供している場合もある。
同じ動的メモリアロケータでmallocだけでなくC++の operator new も実装していることが多い。そこでこれを malloc ではなく「アロケータ」と呼ぶ。
ヒープ方式
IA-32アーキテクチャでのアロケータの実装には一般にヒープまたはデータセグメントが使用されている(セグメント方式)。アロケータは確保要求を満たすためにヒープを宣言し拡張するのが一般的である。
ヒープ方式はフラグメンテーションという問題がある。どのようなメモリ確保方式でもヒープではフラグメントが発生する。つまり、ヒープ上に飛び飛びに使用中領域と未使用領域が存在することになる。優秀なアロケータはヒープを拡張する前に未使用領域を再利用しようとする。しかし性能問題があるため、リアルタイムシステムでは代わりに「メモリプール」という方式を使う必要がある(特定サイズのメモリブロックのプールを予め用意しておく方式)。
この方式の主な問題はヒープが2つの属性(ヒープ領域の先頭位置とサイズ)しか持っていないことに発している。ヒープは領域全体を満たすメモリを必要とし、その先頭位置は変更できない。従って大きな未使用領域があっても、それは使われることがない。また、あるヒープの直後に別のセグメントがあると、ヒープを拡張することもできない。このため何らかのメモリ量が無駄にされる。
glibc アロケータ
GNU Cライブラリ (glibc)はLinuxなどのbrkとmmapを使用している。brkシステムコールはヒープのサイズを拡張したり縮小したりできる。一方、mmapシステムコールは非常に大きな領域を確保するときに使われる。ヒープには他と同様の問題があるが、mmapで大きな領域の拡張の問題が回避できる可能性がある。
mmapにも問題はある。mmapではページを割り当てて新たなセグメントを作成する。つまり、確保された各セグメントにページが割り当てられている。1バイトしか使っていなくてもページ単位に割り当てが行われ、そのサイズはIA-32では一般に4096バイトである。しかし、大きなページとしては1024倍の4MBのページがあり、これとmmap方式の組合せはメモリを浪費する可能性がある。mmap方式の利点は、セグメントを解放すると対応するメモリが即座に解放される点である。
OpenBSD の malloc
OpenBSDのmalloc関数の実装はmmapを使用している。ページサイズ以上の要求はmmapで行われ、ページサイズ未満ならmalloc内で管理しているメモリプールから割り当てる。そのメモリプールもmmapで確保したものである。freeを実行すると、munmapを使ってプロセスのアドレス空間からアンマップされて解放される。このシステムはアドレス空間のレイアウトをランダム化することによってセキュリティを高めるための設計でもあり、OpenBSDのmmapが持つギャップページ機能も利用している(mmapで確保した領域が仮想空間上で隣接しないようにする機能)。また、解放後の領域は仮想空間としてマッピングが存在しないため、解放後のアクセスの検出も容易である(普通なら不正なページフォールトが発生してプログラムが終了する)。
最大確保サイズ
mallocが確保できるメモリブロックの最大サイズはシステムに依存する。特に物理メモリ量とOSの実装に依存する。理論上の最大値は "size_t" 型(メモリ領域のサイズを表す符号なし整数)である。その最大値は (size_t)-1 か、C99標準の定数 SIZE_MAX である。C言語標準は一回の確保で保証される最小値を提示している(C90 では 0x7FFF、C99 では 0xFFFF)。
C++での利用
C++でもmalloc関数は利用できるが、この利用は後述の問題を引き起こすため推奨されない。C++では言語の機能としてnew演算子、delete演算子が用意されている。mallocで確保したメモリ領域に対してdeleteしたり、逆にnewで確保した領域をfreeしたりすると結果は未定義となる。mallocによって生まれたポインタとnewによって生まれたポインタの混在はバグの温床であり、また、new/delete演算子と違い、malloc/free関数ではクラスのコンストラクタとデストラクタが呼ばれないという違いもあり、C++でのmallocは禁じ手の扱いである。
脚注
- ^ stdlib.hをインクルードし忘れた場合、コンパイラは
mallocがint型であるとみなす。これをキャストしているとインクルードし忘れたことに気づかないが、キャストしていないとコンパイル時にintをポインタ型変数に代入しようとしているとして何らかのメッセージが表示される。 - ^ 特にquick-hack的なコードの場合。行儀の良いまたは良くあるべきソース、例えばアプリケーション製品などでは必ずしも多いとは言えない
- ^ これも、前述と同様に、コードの異常系すなわち例外処理はアプリケーション製品では重要な要請である。
関連項目
外部リンク
- Definition of malloc in IEEE Std 1003.1 standard(英語)
- Manpage of MALLOC Linux JMプロジェクト

