Cではmalloc()
、ヒープ内のメモリ領域を割り当て、その領域へのポインタを返します。それだけです。メモリは初期化されておらず、すべてがゼロまたはその他であるという保証はありません。
Javaでは、呼び出しnew
はと同じようにヒープベースの割り当てを行いmalloc()
ますが、さらに多くの便利さ(または必要に応じてオーバーヘッド)も得られます。たとえば、割り当てるバイト数を明示的に指定する必要はありません。コンパイラーは、割り当てようとしているオブジェクトのタイプに基づいてそれを計算します。さらに、オブジェクトコンストラクターが呼び出されます(初期化の方法を制御する場合は、引数を渡すことができます)。new
戻ったとき、初期化されたオブジェクトがあることが保証されます。
しかし、はい、呼び出しの終わりに、の結果malloc()
とnew
ヒープベースのデータのチャンクへの単なるポインタの両方です。
質問の後半では、スタックとヒープの違いについて質問します。コンパイラ設計に関するコースを受講する(または本に関する本を読む)ことで、はるかに包括的な答えを見つけることができます。オペレーティングシステムに関するコースも役立ちます。スタックとヒープに関するSOに関する質問と回答も数多くあります。
そうは言っても、私は、概要が多すぎないことを望み、違いをかなり高いレベルで説明することを目的とした一般的な概要を説明します。
基本的に、ヒープとスタックの2つのメモリ管理システムを使用する主な理由は、効率のためです。二次的な理由は、それぞれが特定の種類の問題で他よりも優れていることです。
スタックは概念として理解するのが少し簡単なので、スタックから始めます。この関数をCで考えてみましょう...
int add(int lhs, int rhs) {
int result = lhs + rhs;
return result;
}
上記はかなり簡単なようです。名前付きの関数を定義し、add()
左右の加数で渡します。関数はそれらを追加し、結果を返します。発生する可能性のあるオーバーフローなど、すべてのエッジケースのものを無視してください。この時点では、議論に密接な関係はありません。
add()
機能の目的は、かなり簡単だが、私たちは、そのライフサイクルについて何言うことができますか?特にそのメモリ使用量は必要ですか?
最も重要なのは、コンパイラーがデータ型の大きさと使用される数をアプリオリに(つまり、コンパイル時に)知っていることです。lhs
そしてrhs
引数はsizeof(int)
、各バイト4。変数result
もsizeof(int)
です。コンパイラーは、add()
関数が4 bytes * 3 ints
合計12バイトのメモリーを使用していることを通知できます。
ときにadd()
関数が呼び出され、ハードウェアレジスタと呼ばれるスタックポインタがスタックの先頭を指していること、それにアドレスを持つことになります。add()
関数が実行する必要のあるメモリを割り当てるために、関数エントリコードが実行する必要があるすべてのことは、1つのアセンブリ言語命令を発行してスタックポインタレジスタ値を12だけデクリメントすることです。これにより、スタックに3つのストレージが作成されます。ints
1つずつについてlhs
、rhs
、とresult
。単一の命令は1クロックティック(10億分の1秒、1 GHz CPU)で実行される傾向があるため、単一の命令を実行して必要なメモリ領域を取得することは、速度の面で大きな利点です。
また、コンパイラーの観点からは、配列のインデックス付けに非常によく似た変数へのマップを作成できます。
lhs: ((int *)stack_pointer_register)[0]
rhs: ((int *)stack_pointer_register)[1]
result: ((int *)stack_pointer_register)[2]
繰り返しますが、これはすべて非常に高速です。
ときにadd()
関数が終了し、それはきれいにしています。これは、スタックポインタレジスタから12バイトを減算することによって行われます。これはへの呼び出しに似ていますがfree()
、CPU命令を1つだけ使用し、ティックを1つだけ使用します。非常に高速です。
次に、ヒープベースの割り当てを考えます。これは、必要なメモリ容量がアプリオリにわからない場合に関係します(つまり、実行時にのみそれについて学習します)。
この関数を考えてみましょう:
int addRandom(int count) {
int numberOfBytesToAllocate = sizeof(int) * count;
int *array = malloc(numberOfBytesToAllocate);
int result = 0;
if array != NULL {
for (i = 0; i < count; ++i) {
array[i] = (int) random();
result += array[i];
}
free(array);
}
return result;
}
addRandom()
関数はコンパイル時にcount
引数の値がどうなるかを認識していないことに注意してください。このためarray
、次のようにスタックに配置する場合のように定義しようとしても意味がありません。
int array[count];
Ifはcount
巨大であり、それは、私たちのスタックが大きすぎる成長し、他のプログラム・セグメントを上書きする可能性があります。このスタックオーバーフローが発生すると、プログラムがクラッシュします(さらに悪化します)。
したがって、実行時までに必要なメモリの量がわからない場合は、を使用しますmalloc()
。次に、必要なときに必要なバイト数を要求し、malloc()
それだけ多くのバイト数を提供できるかどうかを確認します。可能であれば、それを元に戻し、できなければ、呼び出しがmalloc()
失敗したことを伝えるNULLポインターを取得します。特に、プログラムはクラッシュしません!もちろん、プログラマーであるあなたは、リソース割り当てが失敗した場合、プログラムの実行を許可しないことを決定できますが、プログラマーが開始した終了は、偽のクラッシュとは異なります。
ですから、効率性を見るために戻ってくる必要があります。スタックアロケーターは非常に高速です-割り当てる1つの命令、割り当てを解除する1つの命令、そしてコンパイラーによって実行されますが、スタックは既知のサイズのローカル変数のようなもののために意図されているため、かなり小さい傾向があります。
一方、ヒープアロケータは数桁遅くなります。テーブルを検索して、ユーザーが必要とするメモリ量を販売できる十分な空きメモリがあるかどうかを確認する必要があります。それは確か何も他のものを作るためにメモリを販売するん後にそれは(この簿記は、予備のメモリへの割り当て必要になることがあり、そのブロックを使用することができ、それらのテーブルを更新する必要があり、それ自体のために、それは、販売することを計画するものに加えて)。アロケータは、スレッドセーフな方法でメモリを確実に提供するために、ロック戦略を採用する必要があります。そして記憶がついにfree()
dは、異なる時間に発生し、通常は予測可能な順序ではありません。ヒープの断片化を修復するために、アロケーターは連続したブロックを見つけてそれらをつなぎ合わせる必要があります。それを実現するために1つ以上のCPU命令を必要とするように思える場合、そのとおりです。非常に複雑で時間がかかります。
しかし、ヒープは大きいです。スタックよりもはるかに大きい。それらから多くのメモリを取得することができ、コンパイル時に必要なメモリ量がわからない場合に最適です。したがって、大きすぎるものを割り当てようとするとクラッシュするのではなく、速度を落としてマネージメモリシステムを低下させます。
それがあなたの質問のいくつかに答えるのに役立つことを願っています。上記のいずれかについてご不明な点がありましたらお知らせください。