2009-10-23 4 views
9

C++では、最適化がないと仮定すると、次の2つのプログラムは同じメモリ割り当てマシンコードで終了しますか?スタックポインタを調整する命令だけのカップルが、自動メモリ割り当ては実際にC++でどのように機能しますか?

int main() 
{ 
    int *p = new int; 
    delete p; 
} 

は、メモリのブロックを上の割り当て -

int main() 
{  
    int i; 
    int *p; 
} 

int main() 
{ 
    int *p = new int; 
    delete p; 
} 
+9

どのように同じメモリ割り当てを使用できますか?最初のものはint(スタック上)とポインタ(スタック上)のサイズを割り当てます。 2番目の関数は、スタック上のポインタとヒープ上のintのためのスペースを割り当てます。 – bobbyalex

+1

基本的に私の質問は「実行時に、スタックとヒープの違いは何ですか? – Tarquila

+2

ここをクリックしてください:http://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap – Alex

答えて

17

いいえ、最適化なし...

int main() 
{  
    int i; 
    int *p; 
} 

はほとんど何もしませんヒープを解放してから解放します。これは大変な作業です(ヒープ割り当ては簡単な操作ではありません)。

+4

+1、ヒープ割り当ては、メモリオーバヘッドについてのみ話すために、多くのブック保持情報を呼び出します。 –

2

あなたはスタックとヒープについて知りません。あなたの最初の例は、スタックにいくつかのメモリを割り当てるだけです。メモリは範囲外になるとすぐに削除されます。 malloc/newを使用して取得されたヒープ上のメモリは、空き/削除を使用して削除するまで、そのまま残ります。

+1

"範囲外になるとすぐに削除されます" - コンパイラはこれを行うコードを入れますか? – Tarquila

+0

コンパイラは、スタックと、スコープ規則に必要なスタックフレームを処理します。そうですが、スタック上の何かを削除することは、スタックポインタを減少させるだけなので、ヒープ割り当てのようなものではありませんおよび割り当て解除。 – workmad3

+0

はい、スタックポインタを後ろに移動します。スタックは固定サイズです(少なくとも、それはふりをすることができます)。 – gnud

2

最初のプログラムでは、変数はすべてスタックにあります。あなたはダイナミックメモリを割り当てていません。 'p'はスタックに座っているだけです。逆参照するとゴミが出ます。 2番目のプログラムでは、実際にはヒープ上に整数値を作成しています。この場合、 'p'は実際には有効なメモリを指しています。

*p = 5; 

これは、最初のプログラムではなく、2番目のプログラムで有効です。これは、実際にpを参照解除して意味のあるものにすることができます。希望が役立ちます。ヒープ上

6
int i; 
    int *p; 

^スタック上の一の整数であり、1つの整数ポインタの割り当て

int *p = new int; 
delete p; 

^整数のサイズのスタックとブロック上の1つの整数ポインタの割り当て

EDIT:

スタックセグメントとヒープセグメントの相違点

alt text http://www.maxi-pedia.com/web_files/images/HeapAndStack.png

void another_function(){ 
    int var1_in_other_function; /* Stack- main-y-sr-another_function-var1_in_other_function */ 
    int var2_in_other_function;/* Stack- main-y-sr-another_function-var1_in_other_function-var2_in_other_function */ 
} 
int main() {      /* Stack- main */ 
    int y;      /* Stack- main-y */ 
    char str;      /* Stack- main-y-sr */ 
    another_function();   /*Stack- main-y-sr-another_function*/ 
    return 1 ;     /* Stack- main-y-sr */ //stack will be empty after this statement       
} 

任意のプログラムは、それがスタックセグメントと呼ばれる特殊なmemoyメモリ位置にすべての変数を格納し実行を開始するたびに。たとえば、C/C++の場合、最初に呼び出される関数はmainです。最初にスタックに置かれます。メイン内の変数は、プログラムの実行時にスタックに置かれます。今度はmainが最初の関数と呼ばれるので、最後の関数は任意の値を返します(またはスタックからポップされます)。

newを使用してメモリを動的に割り当てると、ヒープセグメントと呼ばれる特別なメモリロケーションが使用されます。ヒープ上に実際のデータが存在する場合でも、ポインタはスタック上にあります。

+0

これは参考になります。あなたのダイアグラムでは、矢印でヒープを描きました。スタックとヒープの間に距離(プログラムに属さないメモリ)があることを意味すると思いますか? – bobobobo

+0

各プロセスには4GBの仮想メモリが割り当てられているため、メモリはこれらのセグメントに分散されます。 – Xinus

23

何が起きているのかをよりよく理解するために、一度に1つのプロセスしか実行できない16ビットプロセッサで動作する非常に基本的なオペレーティングシステムしかないとしましょう。つまり、一度に実行できるプログラムは1つだけです。さらに、すべての割り込みが無効になっているようなふりをしましょう。

私たちのプロセッサには、スタックと呼ばれる構造があります。スタックは、物理メモリに課せられた論理構造です。私たちのRAMがアドレスE000〜FFFFに存在するとしましょう。これは実行中のプログラムがこのメモリをどんな方法でも使用できることを意味します。私たちのオペレーティングシステムが、E000〜EFFFがスタックであり、F000〜FFFFがヒープであると言っているとしましょう。

スタックは、ハードウェアと機械の指示によって維持されます。それを維持するために必要なことはあまりありません。私たち(または私たちのOS)が行う必要があるのは、スタックの開始アドレスを適切に設定することです。スタックポインタは、ハードウェア(プロセッサ)内に存在する物理エンティティであり、プロセッサ命令によって管理される。この場合、スタックポインタはEFFFに設定されます(スタックがBACKWARDSを成長すると仮定しますが、これはかなり一般的です)。 Cのようなコンパイル言語では、関数を呼び出すと、渡された引数をスタックの関数にプッシュします。各引数には一定のサイズがあります。 intは通常16または32ビット、charは通常8ビットなどです。私たちのシステムでは、intとint *が16ビットのふりをしましょう。各引数について、スタックポインタはsizeof(引数)によってDECREMENTED( - )され、引数はスタックにコピーされます。スコープ内で宣言した変数は、同じ方法でスタックにプッシュされますが、その値は初期化されません。

2つの例に似た2つの例を考えてみましょう。スタックに 1)プッシュeeep:当社の16ビットシステム上で、ここで何が起こる

int hello(int eeep) 
{ 
    int i; 
    int *p; 
} 

は次のとおりです。つまり、sizeof(int)が2であるため、スタックポインタをEFFDにデクリメントしてから実際にeeepをEFFEにコピーします(スタックポインタの現在の値から1を引いたものです。割り当て後)。時には、両方で実行できる命令があります(レジスタに収まるデータをコピーしていると仮定します。そうでなければ、データ型の各要素をスタック上の適切な場所に手動でコピーする必要があります)。 )。

2)iのスペースを作成します。これはおそらく、スタックポインタをEFFBにデクリメントすることを意味します。

3)pのスペースを作成します。これは、おそらくスタックポインタをEFF9にデクリメントすることを意味します。

私たちのプログラムは、変数がどこにあるのかを記憶しています(eeepはEFFE、iはEFFC、pはEFFA)。覚えておくべき重要なことは、スタックがBACKWARDSをカウントしても変数がFORWARDSで動作していることです(これは実際にエンディアンに依存しますが、その点は& eeep == EFFE、EFFFではありません)。

機能が閉じたとき、我々は単に6で(++)、スタックポインタをインクリメントは、(C++の種類、大きさ2のがスタックにプッシュされていない、3「オブジェクト」からです。今

、それは、インターネット上で説明するのはほぼ不可能だと、それを達成するための非常に多くの方法があるので、あなたの2番目のシナリオは、はるかに困難を説明することです。

int hello(int eeep) 
{ 
    int *p = malloc(sizeof(int));//C's pseudo-equivalent of new 
    free(p);//C's pseudo-equivalent of delete 
} 

eeepおよびpはまだ前にとしてスタックにプッシュし、割り当てられていますしかし、この場合は、pを関数呼び出しの結果に初期化します。malloc(またはnew、newはC++で多くのことを行います。必要に応じて、必要に応じて、必要に応じて、必要に応じて、作成者など)は、HEAPと呼ばれるこのブラックボックスに行き、空きメモリのアドレスを取得します。私たちのオペレーティングシステムは、私たちのためにヒープを管理しますが、いつ私たちがメモリを必要としているのか、それが終わった時を知らせる必要があります。

例では、malloc()を呼び出すと、OSはこれらのバイトの開始アドレスを与えることで2バイトのブロック(システム上のsizeof(int)は2)を返します。最初の呼び出しでF000のアドレスが指定されたとしましょう。 OSは、アドレスのF000とF001が現在使用中であることを追跡します。 free(p)を呼び出すと、OSはpが指し示すメモリブロックを見つけ、sizeof(star p)が2であるため2バイトを未使用としてマークします。代わりに、より多くのメモリを割り当てると、アドレスF002が新しいメモリの開始ブロックとして返される可能性があります。 malloc()自体は関数であることに注意してください。 pがmalloc()の呼び出しのためにスタックにプッシュされると、pはpのサイズに合わせてスタック上に十分な空き領域を持つ最初の開かれたアドレスでスタックに再度コピーされます(おそらくEFFB、 sizeof(p)は2です)、スタックポインタは再びEFF9にデクリメントされ、malloc()はそのローカル変数をこの位置からスタックに配置します。 mallocが終了すると、スタックからすべての項目がポップされ、スタックポインタが呼び出される前の状態にスタックポインタが設定されます。空の星であるmalloc()の戻り値は、私たちの使用のためにいくつかのレジスタ(通常は多くのシステムのアキュムレータ)に配置される可能性があります。

実装では、両方の例が本当に簡単ではありません。新しい関数呼び出しのためにスタックメモリを割り当てるときは、新しい関数が値を永久に消去しないように状態を保存する(すべてのレジスタを保存する)ことを確認する必要があります。これは通常、スタック上にプッシュすることも含みます。同じように、通常はプログラムカウンタレジスタを保存して、サブルーチンが復帰した後に正しい場所に戻ることができます。メモリマネージャは、どのメモリが割り当てられているか、割り当てられていないかを「記憶」するために、自分のメモリを使い切ります。仮想メモリとメモリのセグメンテーションはこのプロセスをさらに複雑にし、メモリ管理アルゴリズムはメモリの断片化(それ自体の話題全体)を防ぐために、ブロックを周りに移動して保護しなければならず、これが仮想メモリにつながります同じように。 2番目の例は、実際には最初の例と比較して、ワームの大きな缶です。さらに、複数のプロセスを実行すると、各プロセスが独自のスタックを持ち、複数のプロセス(つまり、自身を保護する必要があります)によってヒープにアクセスすることができるため、この作業は非常に複雑になります。さらに、各プロセッサのアーキテクチャは異なります。いくつかのアーキテクチャでは、スタックポインタをスタック上の最初の空きアドレスに設定することを期待していますが、他のアーキテクチャでは、それを最初のフリーでない場所に向けることを期待しています。

私はこれが助けてくれることを望みます。私にお知らせください。

注意すべての上記の例は、過度に簡略化された架空のマシン用です。実際のハードウェア上では、これは少し毛深くなります。

編集:アスタリスクが表示されません。我々は例に(ほとんど)同じコードを使用する場合、私たちは、それぞれ「例1」と「例2」と「こんにちは」置き換えて、それは価値がある何のための単語「スター」


でそれらを置き換えますwndows上でintelの次のアセンブラ出力を取得します。

 
    .file "test1.c" 
    .text 
.globl _example1 
    .def _example1; .scl 2; .type 32; .endef 
_example1: 
    pushl %ebp 
    movl %esp, %ebp 
    subl $8, %esp 
    leave 
    ret 
.globl _example2 
    .def _example2; .scl 2; .type 32; .endef 
_example2: 
    pushl %ebp 
    movl %esp, %ebp 
    subl $8, %esp 
    movl $4, (%esp) 
    call _malloc 
    movl %eax, -4(%ebp) 
    movl -4(%ebp), %eax 
    movl %eax, (%esp) 
    call _free 
    leave 
    ret 
    .def _free; .scl 3; .type 32; .endef 
    .def _malloc; .scl 3; .type 32; .endef 
+0

これは何か面倒なことが予想されるため、ほとんどエラーを見つけることができません。それらにコメントしてくださいと私はそれが良い点だと思う場合は更新されます。 –

+0

FF002 => F002。とても素敵なエントリー!私はあなたが話すメモリレイアウトのいくつかのアスキーダイアグラムでより明確にすることができると思いますが、それはもっと多くの作業になります。 :) – Bill

+0

これはコミュニティウィキですので、誰かがダイアグラムを追加したい場合は(良い提案です!)気軽に行ってください。 –

関連する問題