「Malloc」の版間の差分
m 角括弧の開始違反 |
|||
23行目: | 23行目: | ||
ANSI Cにおいて<code>malloc</code>が返すのは、[[void (コンピュータ)|void型]]へのポインタ (<code>void *</code>) であり、そのポインタが指す領域のデータ型が不明であることを示している。C言語では汎用ポインタ<code>void *</code>と任意の型<code>T</code>へのポインタ<code>T *</code>との間の[[型変換]] (キャスト) は暗黙的に実行されるため、<code>malloc</code>の戻り値は特に明示的な型変換を記述せずとも任意の型へのポインタ変数にそのまま代入できる。しかし、明示的に型変換を記述する必要が無いにもかかわらず、あえて意図的に型変換を記述することもある。明示的に型変換を記述する目的は、かつてANSI以前の仕様のC(K&R C)においては、<code>void *</code>が存在せず、<code>malloc</code>は<code>char *</code>を返していたからである<ref name="Cprog_malloc">{{Cite web|url=https://faq.cprogramming.com/cgi-bin/smartfaq.cgi?id=1043284351&answer=1047673478 |title=Casting malloc |publisher=Cprogramming.com |accessdate=2007-03-09 }}</ref><ref>[http://www.kouno.jp/home/c_faq/c7.html#7 C FAQ 日本語訳 7.7]</ref>。また、C言語よりも厳密な型システムを持つ[[C++]]では、<code>T *</code>から<code>void *</code>への型変換は暗黙的に行なわれるものの、<code>void *</code>から<code>T *</code>への型変換は暗黙的に行なわれないため、C/C++両方でコンパイルすることのできるソースコードを書く際には型変換の記述が必要となる。 |
ANSI Cにおいて<code>malloc</code>が返すのは、[[void (コンピュータ)|void型]]へのポインタ (<code>void *</code>) であり、そのポインタが指す領域のデータ型が不明であることを示している。C言語では汎用ポインタ<code>void *</code>と任意の型<code>T</code>へのポインタ<code>T *</code>との間の[[型変換]] (キャスト) は暗黙的に実行されるため、<code>malloc</code>の戻り値は特に明示的な型変換を記述せずとも任意の型へのポインタ変数にそのまま代入できる。しかし、明示的に型変換を記述する必要が無いにもかかわらず、あえて意図的に型変換を記述することもある。明示的に型変換を記述する目的は、かつてANSI以前の仕様のC(K&R C)においては、<code>void *</code>が存在せず、<code>malloc</code>は<code>char *</code>を返していたからである<ref name="Cprog_malloc">{{Cite web|url=https://faq.cprogramming.com/cgi-bin/smartfaq.cgi?id=1043284351&answer=1047673478 |title=Casting malloc |publisher=Cprogramming.com |accessdate=2007-03-09 }}</ref><ref>[http://www.kouno.jp/home/c_faq/c7.html#7 C FAQ 日本語訳 7.7]</ref>。また、C言語よりも厳密な型システムを持つ[[C++]]では、<code>T *</code>から<code>void *</code>への型変換は暗黙的に行なわれるものの、<code>void *</code>から<code>T *</code>への型変換は暗黙的に行なわれないため、C/C++両方でコンパイルすることのできるソースコードを書く際には型変換の記述が必要となる。 |
||
< |
<syntaxhighlight lang="c"> |
||
int *ptr = (int *)malloc(sizeof(int) * 10); |
int *ptr = (int *)malloc(sizeof(int) * 10); |
||
</syntaxhighlight> |
|||
</source> |
|||
しかし、<code>stdlib.h</code>をインクルードし忘れた場合(<code>malloc</code>関数の[[前方宣言]]がない場合)、Cコンパイラは<code>malloc</code>が<code>int</code>型を返す関数であるとみなす。<code>malloc</code>の戻り値を明示的に型変換していると、ヘッダーをインクルードし忘れたことに気づかない。一方、明示的に型変換していないと、コンパイル時に<code>int</code>をポインタ型変数に代入しようとしているとして何らかの警告あるいはエラーメッセージがコンパイラから表示されることでインクルード忘れに気づける可能性がある<ref>[http://www.kouno.jp/home/c_faq/c7.html#6 C FAQ 日本語訳 7.6]</ref><ref name="Cprog_malloc" /><ref>{{Cite web|url= http://www.c-faq.com/malloc/mallocnocast.html |title=comp.lang.c FAQ list · Question 7.7b |publisher=C-FAQ |accessdate=2007-03-09 }}</ref>。例えば[[64ビット]]のシステムでLP64というデータモデルを採用している場合、<code>long</code>とポインタは64ビットだが、<code>int</code>は32ビットとなる。この場合<code>stdlib.h</code>のインクルード忘れに気づかないと、<code>malloc</code>が実際には64ビットのポインタを返すのに、32ビットの値を返すと暗に宣言したことになり、バグが生じる可能性がある。ただし、多くのコンパイラは宣言のない関数を使用していると(キャストの有無にかかわらず)コンパイル時に警告を出す。 |
しかし、<code>stdlib.h</code>をインクルードし忘れた場合(<code>malloc</code>関数の[[前方宣言]]がない場合)、Cコンパイラは<code>malloc</code>が<code>int</code>型を返す関数であるとみなす。<code>malloc</code>の戻り値を明示的に型変換していると、ヘッダーをインクルードし忘れたことに気づかない。一方、明示的に型変換していないと、コンパイル時に<code>int</code>をポインタ型変数に代入しようとしているとして何らかの警告あるいはエラーメッセージがコンパイラから表示されることでインクルード忘れに気づける可能性がある<ref>[http://www.kouno.jp/home/c_faq/c7.html#6 C FAQ 日本語訳 7.6]</ref><ref name="Cprog_malloc" /><ref>{{Cite web|url= http://www.c-faq.com/malloc/mallocnocast.html |title=comp.lang.c FAQ list · Question 7.7b |publisher=C-FAQ |accessdate=2007-03-09 }}</ref>。例えば[[64ビット]]のシステムでLP64というデータモデルを採用している場合、<code>long</code>とポインタは64ビットだが、<code>int</code>は32ビットとなる。この場合<code>stdlib.h</code>のインクルード忘れに気づかないと、<code>malloc</code>が実際には64ビットのポインタを返すのに、32ビットの値を返すと暗に宣言したことになり、バグが生じる可能性がある。ただし、多くのコンパイラは宣言のない関数を使用していると(キャストの有無にかかわらず)コンパイル時に警告を出す。 |
||
54行目: | 54行目: | ||
スタック上に10個の整数の[[配列]]を作成する一般的な方法は次の通りである。 |
スタック上に10個の整数の[[配列]]を作成する一般的な方法は次の通りである。 |
||
< |
<syntaxhighlight lang="c"> |
||
int array[10]; |
int array[10]; |
||
</syntaxhighlight> |
|||
</source> |
|||
同様の配列を動的に確保するには、以下のように<code>malloc</code>を使う。 |
同様の配列を動的に確保するには、以下のように<code>malloc</code>を使う。 |
||
< |
<syntaxhighlight lang="c"> |
||
#include <stdlib.h> |
#include <stdlib.h> |
||
76行目: | 76行目: | ||
ptr = NULL; |
ptr = NULL; |
||
} |
} |
||
</syntaxhighlight> |
|||
</source> |
|||
== 一般的なエラー == |
== 一般的なエラー == |
||
92行目: | 92行目: | ||
ポインタが<code>free</code>関数に渡され、メモリが解放された後でその領域への参照を行っても、その内容は未定義であり利用できない。しかし、ポインタ自体が残っていると誤って使ってしまうことがある。次のコードはその例である。 |
ポインタが<code>free</code>関数に渡され、メモリが解放された後でその領域への参照を行っても、その内容は未定義であり利用できない。しかし、ポインタ自体が残っていると誤って使ってしまうことがある。次のコードはその例である。 |
||
< |
<syntaxhighlight lang="c"> |
||
int *ptr = malloc(sizeof(*ptr)); /* メモリ領域を動的確保 */ |
int *ptr = malloc(sizeof(*ptr)); /* メモリ領域を動的確保 */ |
||
... |
... |
||
98行目: | 98行目: | ||
/* ptr は解放済みの領域をまだ指したまま */ |
/* ptr は解放済みの領域をまだ指したまま */ |
||
*ptr = 0; /* 何が起きるかわからない! */ |
*ptr = 0; /* 何が起きるかわからない! */ |
||
</syntaxhighlight> |
|||
</source> |
|||
このようなコードは予測不能の振る舞いをする。メモリが解放された後でシステムがその領域を他の用途に転用しているかもしれない。従って解放済みメモリ領域へのポインタ(ダングリングポインタ; dangling pointer)を使った書き込みはプログラム内の別のデータを不正に書き換えてしまう。どういうデータを書き換えたかによって、その後のプログラムの動作も異なる。単なるデータ破壊で済むかもしれないし、クラッシュするかもしれない。特に破壊的なバグとしては、同じポインタを2回<code>free</code>関数に渡してしまうことであり、これを「二重解放; double free」と呼ぶ。これを防ぐため、解放した後でポインタ変数に<code>NULL</code>を格納することがある。ポインタ変数が無効な領域を指しているかどうかを後で調べるとき、<code>NULL</code>が格納されているかどうかで簡単に判定できるようになる。また、<code>free(NULL)</code>は何もしないことが規格で保証されているので、<code>NULL</code>を代入しておけば(<code>free</code>関数を再度呼んだとしても)二重解放の危険はない。この技法は寿命の長い[[グローバル変数]]でポインタを定義する場合は必須だが、寿命の短い[[ローカル変数]]でポインタを定義する場合でも[[フールプルーフ]]のために用いることができる。 |
このようなコードは予測不能の振る舞いをする。メモリが解放された後でシステムがその領域を他の用途に転用しているかもしれない。従って解放済みメモリ領域へのポインタ(ダングリングポインタ; dangling pointer)を使った書き込みはプログラム内の別のデータを不正に書き換えてしまう。どういうデータを書き換えたかによって、その後のプログラムの動作も異なる。単なるデータ破壊で済むかもしれないし、クラッシュするかもしれない。特に破壊的なバグとしては、同じポインタを2回<code>free</code>関数に渡してしまうことであり、これを「二重解放; double free」と呼ぶ。これを防ぐため、解放した後でポインタ変数に<code>NULL</code>を格納することがある。ポインタ変数が無効な領域を指しているかどうかを後で調べるとき、<code>NULL</code>が格納されているかどうかで簡単に判定できるようになる。また、<code>free(NULL)</code>は何もしないことが規格で保証されているので、<code>NULL</code>を代入しておけば(<code>free</code>関数を再度呼んだとしても)二重解放の危険はない。この技法は寿命の長い[[グローバル変数]]でポインタを定義する場合は必須だが、寿命の短い[[ローカル変数]]でポインタを定義する場合でも[[フールプルーフ]]のために用いることができる。 |
||
< |
<syntaxhighlight lang="c"> |
||
int *ptr = malloc(sizeof(*ptr)); |
int *ptr = malloc(sizeof(*ptr)); |
||
... |
... |
||
free(ptr); |
free(ptr); |
||
ptr = NULL; |
ptr = NULL; |
||
</syntaxhighlight> |
|||
</source> |
|||
== 実装 == |
== 実装 == |
2020年7月5日 (日) 22:46時点における版
malloc(マロック, エムアロック)、calloc、reallocは、動的メモリ確保を行うC言語の標準ライブラリの関数である[1][2][3]。確保したメモリの解放にはfree関数を使用する。
malloc
が使用する実際のメモリ確保機構には様々な実装がある。それらの性能は、実行時間と要求されるメモリの両面で様々である。
原理
C言語は通常メモリを「静的メモリ確保」か「自動メモリ確保」で管理する。静的変数は主記憶上にプログラムが存在する期間中ずっと確保されている。自動変数(局所変数)は通常コールスタック上に確保され、対応するサブルーチンが実行中の間だけ存在する。しかし、いずれの方法も限界があり、確保できるメモリ量(変数のサイズ)はコンパイル時に決められてしまう。必要なサイズが実行時でないと判明しない場合、例えばディスク上のファイルから任意のサイズのデータを読み込むような場合、固定サイズのデータオブジェクトだけでは不十分である。
確保されたメモリの生存期間(使用可能期間)も問題となる。静的でも自動的でも確保されたメモリの生存期間はあらゆる状況に対応できるものではない。スタック上のデータは複数の関数呼出をまたいで持続できないし、静的データは必要かどうかに関わらずプログラムが動作中ずっとメモリ領域を確保し続ける。しかし、そうではなく確保したメモリの生存期間をプログラマが自由に制御できる必要がある場合も多い。
これらの制限は動的メモリ確保を使用することで解決する。メモリの管理は明示的に行う必要が出てくるが、柔軟性が向上する。「ヒープ領域」はこのために用意されたメモリ領域である。C言語では、malloc
関数を使ってヒープ領域からメモリブロックを確保する。プログラムはmalloc
の戻り値であるポインタを使って、そのメモリブロックにアクセスする。メモリブロックが不要になったら、そのポインタをfree
に渡して解放し、他の用途に再利用できるようにする。
ヒープではなくCのスタックフレームから実行時に動的にメモリを確保するライブラリルーチンもある(Unix系の alloca()
[4]、Microsoft Visual C++におけるCランタイムライブラリの _malloca()
[5] など)。このように確保したメモリは呼び出した関数から抜ける時点で自動的に解放される。ただし、スタックオーバーフローに注意する必要がある。GCC言語拡張の可変長配列がC99標準でサポートされ[6]、実行時に大きさを決定でき任意の静的スコープの範囲で存在するメモリ確保法が言語構文に組み込まれたが、C11ではサポート必須ではなくオプションに格下げとなった。
C言語での動的メモリアロケーション
malloc
はC言語におけるヒープ領域からのメモリ確保に使われる基本関数である。その関数プロトタイプはstdlib.h
ヘッダに次のように定義されている[1]。
void *malloc(size_t size)
ここで、size
バイトのメモリが確保される。確保が成功するとそのメモリブロックへのポインタが返される。
ANSI Cにおいてmalloc
が返すのは、void型へのポインタ (void *
) であり、そのポインタが指す領域のデータ型が不明であることを示している。C言語では汎用ポインタvoid *
と任意の型T
へのポインタT *
との間の型変換 (キャスト) は暗黙的に実行されるため、malloc
の戻り値は特に明示的な型変換を記述せずとも任意の型へのポインタ変数にそのまま代入できる。しかし、明示的に型変換を記述する必要が無いにもかかわらず、あえて意図的に型変換を記述することもある。明示的に型変換を記述する目的は、かつてANSI以前の仕様のC(K&R C)においては、void *
が存在せず、malloc
はchar *
を返していたからである[7][8]。また、C言語よりも厳密な型システムを持つC++では、T *
からvoid *
への型変換は暗黙的に行なわれるものの、void *
からT *
への型変換は暗黙的に行なわれないため、C/C++両方でコンパイルすることのできるソースコードを書く際には型変換の記述が必要となる。
int *ptr = (int *)malloc(sizeof(int) * 10);
しかし、stdlib.h
をインクルードし忘れた場合(malloc
関数の前方宣言がない場合)、Cコンパイラはmalloc
がint
型を返す関数であるとみなす。malloc
の戻り値を明示的に型変換していると、ヘッダーをインクルードし忘れたことに気づかない。一方、明示的に型変換していないと、コンパイル時にint
をポインタ型変数に代入しようとしているとして何らかの警告あるいはエラーメッセージがコンパイラから表示されることでインクルード忘れに気づける可能性がある[9][7][10]。例えば64ビットのシステムでLP64というデータモデルを採用している場合、long
とポインタは64ビットだが、int
は32ビットとなる。この場合stdlib.h
のインクルード忘れに気づかないと、malloc
が実際には64ビットのポインタを返すのに、32ビットの値を返すと暗に宣言したことになり、バグが生じる可能性がある。ただし、多くのコンパイラは宣言のない関数を使用していると(キャストの有無にかかわらず)コンパイル時に警告を出す。
メモリの解放
malloc
で確保されたメモリは持続性がある。プログラム終了時か明示的にプログラマが解放しない限り存在し続ける。解放はfree
関数で行われる。そのプロトタイプは次のようになる。
void free(void *pointer)
ここで、pointer
の指すメモリブロックが解放される。
関連する関数
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ライブラリに含まれる関数ではない。
使用例
スタック上に10個の整数の配列を作成する一般的な方法は次の通りである。
int array[10];
同様の配列を動的に確保するには、以下のようにmalloc
を使う。
#include <stdlib.h>
/* 10個のintの配列のためのメモリを確保 */
int *ptr = malloc(sizeof(int) * 10);
if (NULL == ptr) {
exit(EXIT_FAILURE); /* メモリを確保できなかったので終了 */
} else {
/* 確保成功 */
/* ここでその配列を使った処理を行う */
/* 処理が完了し、使わなくなったメモリブロックを解放する */
free(ptr);
/* 解放したメモリにアクセスしてはならないことを示すため、NULLを代入しておく */
ptr = NULL;
}
一般的なエラー
malloc
および関連するC言語の関数の使用はバグの発生源になりやすい。
確保エラー
malloc
は必ず成功するとは限らない。利用可能な空きメモリ領域がないとき、プログラムが限界値を超えてメモリを使用しようとしたときなど、malloc
はヌルポインタを返す。環境によって、このような状況の起きる可能性は異なる。もしmalloc
が失敗することを考慮していないプログラムで、malloc
がヌルポインタを返してきたとき、プログラムはNULLに相当するアドレスにアクセスしてクラッシュするだろう。メモリ確保失敗をチェックすることは、ライブラリでは特に重要である。ライブラリはメモリ量が限られた状況でも使用される可能性がある。ライブラリ内でメモリ確保に失敗した場合、呼び出したアプリケーション側にエラーを通知して、アプリケーションプログラムに判断を委ねるのがよいとされる。
プロトタイピング目的で書かれたコードなどでは、メモリ確保失敗をチェックしないこともある。というのもメモリ確保に失敗するという状況は、画像処理などを除いた一般的な用途では珍しく、発生した場合には終了する以外にプログラミング上できることがないからでもある。ごく少量のメモリさえ確保に失敗するような状況では、メモリ確保失敗に対処したとしてもプログラムを正常に続行できる見込みがない。一方で、ライブラリ製品やアプリケーション製品では異常系すなわち例外処理への対処は重要であり、メモリ確保失敗への対処も例外ではない。
メモリリーク
malloc
で確保したメモリブロックを使用しなくなってもfree
で解放せず、次々と新たなメモリブロックを確保していると空きメモリが少なくなってくる。これをメモリリークと呼ぶ。メモリリークによるメモリ消費が無視できない量に達するとページ置換アルゴリズムによってページアウトが発生し、システム性能が低下する。さらに仮想記憶の容量限界に達すると、システム内の他の全プロセスでメモリ確保が失敗してシステムがストールする。メモリリークは、利用のたびにプロセスの起動・終了が行われるプログラムにおいて、特に重要な問題を引き起こさないため、メモリリークが混入した状態で出荷される商用プログラムは多い。しかしながら、連続稼動が要求されるサーバコンピュータ上のプログラム(サービス、デーモン)や、組み込みプログラム等の場合は、メモリリークは死活問題とされる。
解放後の使用
ポインタがfree
関数に渡され、メモリが解放された後でその領域への参照を行っても、その内容は未定義であり利用できない。しかし、ポインタ自体が残っていると誤って使ってしまうことがある。次のコードはその例である。
int *ptr = malloc(sizeof(*ptr)); /* メモリ領域を動的確保 */
...
free(ptr);
/* ptr は解放済みの領域をまだ指したまま */
*ptr = 0; /* 何が起きるかわからない! */
このようなコードは予測不能の振る舞いをする。メモリが解放された後でシステムがその領域を他の用途に転用しているかもしれない。従って解放済みメモリ領域へのポインタ(ダングリングポインタ; dangling pointer)を使った書き込みはプログラム内の別のデータを不正に書き換えてしまう。どういうデータを書き換えたかによって、その後のプログラムの動作も異なる。単なるデータ破壊で済むかもしれないし、クラッシュするかもしれない。特に破壊的なバグとしては、同じポインタを2回free
関数に渡してしまうことであり、これを「二重解放; double free」と呼ぶ。これを防ぐため、解放した後でポインタ変数にNULL
を格納することがある。ポインタ変数が無効な領域を指しているかどうかを後で調べるとき、NULL
が格納されているかどうかで簡単に判定できるようになる。また、free(NULL)
は何もしないことが規格で保証されているので、NULL
を代入しておけば(free
関数を再度呼んだとしても)二重解放の危険はない。この技法は寿命の長いグローバル変数でポインタを定義する場合は必須だが、寿命の短いローカル変数でポインタを定義する場合でもフールプルーフのために用いることができる。
int *ptr = malloc(sizeof(*ptr));
...
free(ptr);
ptr = NULL;
実装
メモリ管理の実装はオペレーティングシステム (OS) と(ハードウェア)アーキテクチャに大きく依存する。OSによってはmallocのためのアロケータを提供しているし、データ領域の制御関数を提供している場合もある。
同じ動的メモリアロケータでmallocだけでなくC++の operator new
も実装していることが多い。そこでこれを malloc ではなく「アロケータ」と呼ぶ。
ヒープ方式
IA-32アーキテクチャでのアロケータの実装には一般にヒープまたはデータセグメントが使用されている(セグメント方式)。 アロケータがメモリを確保するとき、ヒープに未使用領域がない場合はヒープを拡張することでメモリを確保する。
ヒープ方式はフラグメンテーションという問題がある。どのようなメモリ確保方式でもヒープではフラグメントが発生する。つまり、ヒープ上に飛び飛びに使用中領域と未使用領域が存在することになる。優秀なアロケータはヒープを拡張する前に未使用領域を再利用しようとする。しかし性能問題があるため、リアルタイムシステムでは代わりに「メモリプール」という方式を使う必要がある(特定サイズのメモリブロックのプールを予め用意しておく方式)。
ヒープ方式の欠点は、先頭位置が変更できないため、ヒープの最後の位置に使用中ブロックがある限り、ヒープを縮小することができないことである。従って、このような時は実際に使用しているメモリが少ないのにも関わらず、ヒープのアドレス空間に占める領域が拡大し続けるという問題が生じる。
mmapで確保した領域は、縮小したり開放したりすることができる。これらによって開放された領域は、OSのメモリ管理システムに委ねることになる。
dlmalloc
dlmalloc は Doug Lea が1987年に開発を始めた汎用アロケータ。パブリックドメインライセンス(CC0ライセンス)のオープンソースライブラリ。GNU Cライブラリ (glibc) の malloc は、dlmalloc を元に作られている[11]。
ヒープ領域上のメモリは "chunk" という8バイト境界のデータ構造として確保され、その中にヘッダ部と利用可能なメモリがある。chunk および利用中フラグがあるため、8バイトまたは16バイトのオーバヘッドを含めたメモリ確保が必要である。アロケートされていないchunkも他のフリーなchunkへのポインタを持つため、chunkの最小サイズは24バイトとなっている[11]。
アロケートされていないメモリは "bin" と呼ばれる同じサイズのchunkのグループに分けて管理される。binはchunkを双方向連結リストで連結したものである。
小さな領域 (<256B) を確保するときには2の累乗フリーリストが使われる。領域が不足したときは、より大きなサイズ用の領域からプールを確保する[11]。
中程度の大きさの領域は、bitwiseトライ木により管理される。領域が不足したときは、(sbrkによる)ヒープの拡張が行われる。
大きな領域(デフォルト値は >= 256KB[12])は、mmap() が使える環境の場合、mmap() により直接確保される。この大きさならば、ページサイズ(通常4KB[13], CPUのアーキテクチャに依存する)単位でしか割り当てられないことによるオーバーヘッド、システムコールの遅さによるオーバーヘッドはほとんどない。一方で、先に述べたヒープ方式のアロケーションの問題が起こらないので、この方法が使われる。
FreeBSDとNetBSD
FreeBSD 7.0以降と NetBSD 5.0以降では、古い実装 (phkmalloc) をJason Evansが開発したjemallocに置換した。phkmalloc はマルチスレッド環境でのスケーラビリティに問題があった。ロックの衝突を防ぐため、jemallocはCPU毎に分離した "arena" と呼ばれる領域を用意する。実験によれば、スレッド数に比例して1秒間のmalloc回数が増えていくとき、phkmallocとdlmallocではスレッド数に反比例した性能を示した[14]。
OpenBSD
OpenBSDのmalloc
関数の実装はmmap
を使用している。ページサイズ以上の要求はmmap
で行われ、ページサイズ未満ならmalloc
内で管理しているメモリプールから割り当てる。そのメモリプールもmmap
で確保したものである。free
を実行すると、munmap
を使ってプロセスのアドレス空間からアンマップされて解放される。このシステムはアドレス空間のレイアウトをランダム化することによってセキュリティを高めるための設計でもあり、OpenBSDのmmap
が持つギャップページ機能も利用している(mmapで確保した領域が仮想空間上で隣接しないようにする機能)。また、解放後の領域は仮想空間としてマッピングが存在しないため、解放後のアクセスの検出も容易である(普通ならセグメンテーション違反が発生してプログラムが終了する)。
Hoard
Hoardメモリアロケータは、スケーラブルなメモリ確保性能を目指したアロケータである。OpenBSDのアロケータと同様、Hoard はmmap
のみを使用するが、64キロバイトのスーパーブロックと呼ばれる塊ごとにメモリを管理する。Hoardのヒープは論理的にグローバルな1つのヒープとプロセッサ毎のヒープに分けられている。さらにスレッド毎のキャッシュを持ち、制限された数のスーパーブロックを保持できる。確保はスレッド毎またはプロセッサ毎のヒープからのみ行い、解放されたスーパーブロックの多くはグローバルなヒープに戻して他のプロセッサが使えるようにする。このようにしてHoardではスレッド数に対してほぼ比例したスケーラビリティを維持しつつ、フラグメンテーションを最小に抑えている[15]。
TCMalloc (thread-caching malloc)
スレッド毎に小さいメモリ確保のための領域を設ける。大きなメモリ確保にはmmapかsbrkを使用する。TCMallocはGoogleが開発したもので[16]、スレッドが終了した際にローカルな領域を掃除するガベージコレクション機能を備えている。マルチスレッド環境では、glibcのptmallocの2倍以上の性能を発揮するという[17][18]。
カーネル内
OSのカーネルでもアプリケーションと同様にメモリ確保が必要である。カーネル内にもmalloc
相当の関数はあるが、その実装はCライブラリのものとは大きく異なる。例えば、DMA用のバッファには特別な制限が課せられることがあるし、割り込み処理でメモリを動的に確保したい場合もある[19]。このため、カーネルの仮想記憶サブシステムと密に連携した malloc
実装が要求される。
最大確保サイズ
malloc
が確保できるメモリブロックの最大サイズはシステムに依存する。特に物理メモリ量とOSの実装に依存する。理論上の最大値はsize_t
型(メモリ領域のサイズを表す符号なし整数)である。その最大値は 2CHAR_BIT × sizeof(size_t) − 1か、C99標準の定数SIZE_MAXである。C言語標準は一回の確保で保証される最小値を提示している(C89では0x7FFF、C99では0xFFFF)。
C++での利用
C++でもmalloc
関数は利用できるが、この利用は後述の問題を引き起こすため推奨されない。C++では言語の機能としてnew演算子、delete演算子が用意されている[20]。malloc
で確保したメモリ領域に対してdelete
したり、逆にnew
で確保した領域をfree
したりすると結果は未定義となる。malloc
によって生まれたポインタとnew
によって生まれたポインタの混在はバグの温床であり、また、new/delete演算子と違い、malloc/free関数ではクラスのコンストラクタとデストラクタが呼ばれないという違いもあり、C++でのmalloc
は禁じ手の扱いである。
脚注
- ^ a b ISO/IEC 9899:1999 specification. p. 313, § 7.20.3 "Memory management functions"
- ^ Godse, Atul P.; Godse, Deepali A. (2008). Advanced C Programming. p. 6-28: Technical Publications. pp. 400. ISBN 978-81-8431-496-0
- ^ Summit, Steve. “C Programming Notes - Chapter 11: Memory Allocation”. 2011年10月30日閲覧。
- ^ “alloca”. Man.freebsd.org (2006年9月5日). 2011年9月18日閲覧。
- ^ “_malloca”. Microsoft Docs. 2019年2月18日閲覧。
- ^ “gcc manual”. gnu.org. 2008年12月14日閲覧。
- ^ a b “Casting malloc”. Cprogramming.com. 2007年3月9日閲覧。
- ^ C FAQ 日本語訳 7.7
- ^ C FAQ 日本語訳 7.6
- ^ “comp.lang.c FAQ list · Question 7.7b”. C-FAQ. 2007年3月9日閲覧。
- ^ a b c Kaempf, Michel (2001). “Vudo malloc tricks”. Phrack (57): 8 2012年2月2日閲覧。.
- ^ ftp://g.oswego.edu/pub/misc/malloc.c の DEFAULT_MMAP_THRESHOLD
- ^ Sanderson, Bruce (2004年12月12日). “RAM, Virtual Memory, Pagefile and all that stuff”. Microsoft Help and Support. 2012年7月11日閲覧。
- ^ Evans, Jason (2006年4月16日). “A Scalable Concurrent malloc(3) Implementation for FreeBSD”. 2012年3月18日閲覧。
- ^ “Hoard: A Scalable Memory Allocator for Multithreaded Applications” (2000年). 2012年3月18日閲覧。
- ^ TCMalloc : Thread-Caching Malloc
- ^ Ghemawat, Sanjay; Menage, Paul; TCMalloc : Thread-Caching Malloc
- ^ Callaghan, Mark (2009年1月18日). “High Availability MySQL: Double sysbench throughput with TCMalloc”. Mysqlha.blogspot.com. 2011年9月18日閲覧。
- ^ “kmalloc()/kfree() include/linux/slab.h”. People.netfilter.org. 2011年9月18日閲覧。
- ^ Stroustrup, Bjarne (2008). Programming: Principles and Practice Using C++. 1009, §27.4 Free store: Addison Wesley. pp. 1236. ISBN 978-0-321-54372-1
関連項目
外部リンク
- 英語ページ
- Definition of malloc in IEEE Std 1003.1 standard
malloc(1)
– JM Project Linux User Commands マニュアル Linux JMプロジェクト- Gloger, Wolfram; The ptmalloc homepage
- Berger, Emery; The Hoard homepage
- Douglas, Niall; The nedmalloc homepage
- Evans, Jason; The jemalloc homepage
- Berger, Emery; Hoard: A Scalable Memory Allocator for Multithreaded Applications
- Michael, Maged M.; Scalable Lock-Free Dynamic Memory Allocation
- Memory Reduction (GNOME) wiki page with lots of information about fixing malloc
- C99 standard draft, including TC1/TC2/TC3
- Some useful references about C
- ISO/IEC 9899 – Programming languages – C
- Doug Lea. “A Memory Allocator”. 2018年12月26日閲覧。:dlmallocの仕組みを説明
- 日本語の解説
- Jonathan Bartlett (2004年11月16日). “メモリー管理の内側 動的アロケーションの選択肢とトレードオフ、そして実装”. チュートリアル>Linux. IBM. 2018年12月26日閲覧。
- Bartlett, Jonathan; Inside memory management - The choices, tradeoffs, and implementations of dynamic allocation:英語版
- 技術本部 クラウド基盤エキスパート 角馬 文彦 (2007年11月30日). “malloc(3)のメモリ管理構造”. VA Linux Systems Japan. 2018年12月26日閲覧。
- Jonathan Bartlett (2004年11月16日). “メモリー管理の内側 動的アロケーションの選択肢とトレードオフ、そして実装”. チュートリアル>Linux. IBM. 2018年12月26日閲覧。