CおよびC ++はスタックに大きなオブジェクトをどのように格納しますか?
質問
CとC ++がスタック上に大きなオブジェクトを格納する方法を理解しようとしています。通常、スタックは整数のサイズであるため、大きなオブジェクトがそこにどのように格納されているのか理解できません。それらは単に複数のスタック「スロット」を占有しますか?
解決
スタックはメモリの一部です。スタックポインターは上部を指します。値をスタックにプッシュし、ポップして値を取得できます。
たとえば、2つのパラメーターで呼び出される関数がある場合(1バイトのサイズと他の2バイトのサイズ。8ビットのPCがあると仮定してください)。
両方がスタックにプッシュされると、スタックポインターが上に移動します。
03: par2 byte2
02: par2 byte1
01: par1
関数が呼び出され、戻りアドレスがスタックに配置されます:
05: ret byte2
04: ret byte1
03: par2 byte2
02: par2 byte1
01: par1
OK、関数内に2つのローカル変数があります。 2バイトのうちの1つと4バイトのうちの1つです。これらの場合、スタック上の位置が予約されますが、最初にスタックポインターを保存して、カウントアップによって変数が開始し、カウントダウンによってパラメーターが見つかる場所を確認します。
11: var2 byte4
10: var2 byte3
09: var2 byte2
08: var2 byte1
07: var1 byte2
06: var1 byte1
---------
05: ret byte2
04: ret byte1
03: par2 byte2
02: par2 byte1
01: par1
ご覧のとおり、スペースが残っている限り、スタックに何でも置くことができます。それ以外の場合は、このサイトに名前を付ける現象が発生します。
他のヒント
スタックとヒープはあなたが思うほど違いはありません!
確かに、一部のオペレーティングシステムにはスタック制限があります。 (そのうちのいくつかには、厄介なヒープ制限もあります!)
しかし、これはもう1985年ではありません。
最近、Linuxを実行しています!
私のデフォルトの stacksize は10 MBに制限されています。デフォルトのヒープサイズは無制限です。そのスタックサイズを無制限にすることは非常に簡単です。 (* cough * [tcsh] unstack stacksize * cough *。または setrlimit()。)
stack と heap の最大の違いは次のとおりです。
- stack の割り当ては、ポインタをオフセットするだけです(スタックが十分に大きくなった場合は、新しいメモリページを割り当てる可能性があります)。 ヒープは、適切なメモリブロックを見つけるためにデータ構造を検索する必要があります。 (そして、おそらく新しいメモリページも割り当てます。) 現在のブロックが終了すると、
- stack はスコープ外になります。 delete / freeが呼び出されると、ヒープが範囲外になります。
- ヒープは断片化される可能性があります。 スタックが断片化されることはありません。
Linuxでは、 stack と heap の両方が仮想メモリを介して管理されます。
割り当て時間の観点から、ひどく断片化されたメモリをヒープ検索しても、メモリの新しいページにマッピングすることはできません。 時間的に違いはごくわずかです!
OSによっては、それらがマップされている新しいメモリページを実際に使用する場合のみであることがよくあります( malloc()割り当て中に NOT !)(それは怠evaluationな評価のことです。
( new は、おそらくこれらのメモリページを使用するコンストラクタを呼び出します...)
スタックまたはヒープのいずれかで大きなオブジェクトを作成および破棄することにより、VMシステムをスラッシングできます。システムがメモリを再利用できるかどうかは、OS /コンパイラに依存します。再利用されていない場合、ヒープは再利用できる可能性があります。 (その間に別の malloc()によって再利用されていないと仮定します。)同様に、スタックが再利用されない場合、単に再利用されます。
スワップアウトされたページは、スワップインし直す必要がありますが、それが最大の時間ヒットになります。
これらすべての中で、メモリの断片化が最も心配です!
寿命(スコープから外れたとき)は常に決定要因です。
しかし、プログラムを長時間実行すると、断片化によりメモリフットプリントが徐々に増加します。絶え間ない交換は結局私を殺します!
追加の修正:
男、私は甘やかされています!
ここに何かが追加されていませんでした...私は* I *が地獄のようなものであると考えました。または他の皆がそうでした。または、おそらく両方。または、多分、どちらでもない。
答えが何であれ、何が起こっているのかを知る必要がありました!
...これは長くなるでしょう。我慢して...
過去12年間のほとんどをLinuxでの作業に費やしてきました。そして、その約10年前のさまざまな種類のUnixで。私のコンピューターに関する見方はやや偏っています。甘やかされてしまいました!
私はWindowsで少しやったことがありますが、正式に話すには十分ではありません。悲劇的にも、Mac OS / Darwinでも... Mac OS / Darwin / BSDは十分近いため、私の知識の一部が引き継がれます。
32ビットポインターでは、4 GB(2 ^ 32)のアドレススペースが不足します。
実際には、スタック + HEAP の組み合わせは通常は2〜4 GBのいずれかに制限されます。他のものをマッピングする必要があるためです。
(共有メモリ、共有ライブラリ、メモリマップファイル、実行中の実行可能イメージは常に優れているなど)
Linux / Unix / MacOS / Darwin / BSDでは、次のことができます。実行時に任意の値に HEAP または STACK を人為的に制限します。しかし、最終的にはハードシステムの制限があります。
これは、" limit" と" limit -h" の区別(tcsh単位)です。または(bashで)" ulimit -Sa" と" ulimit -Ha" の組み合わせ。または、プログラムで、 struct  rlimit の rlim_cur と rlim_max のいずれか。
ここからがおもしろいところです。 Martin York's Code に関して。 (ありがとうございました Martin !良い例。いつも試してみてください!)
Martin's はおそらくMacで実行されています。 (かなり最近のものです。彼のコンパイラのビルドは私のものよりも新しいです!)
もちろん、彼のコードはデフォルトではMacで実行されません。ただし、最初に" unlimit stacksize" (tcsh)または" ulimit -Ss unlimited" (bash)を呼び出した場合は正常に実行されます。
重要事項:
古い(廃止)Linux RH9 2.4.xカーネルボックスでのテスト、大量のスタックの割り当て  または  HEAP 、どちらか1つが2〜3 GBを超えます。 (残念ながら、マシンのRAM + SWAPは3.5 GBを少し下回ります。これは32ビットOSです。実行中のプロセスは NOT だけです。 )
つまり、Linuxでの人工的なサイズ以外のスタックサイズと HEAP サイズに制限はありません...
しかし:
Macでは、 65532キロバイトのハードスタックサイズ制限があります。メモリ内での配置方法に関係しています。
通常、理想的なシステムはメモリアドレス空間の一方の端にスタック、もう一方の端に HEAP 、そしてそれらは互いに向かって構築します。彼らが会うとき、あなたは記憶不足です。
Macは、両側を制限する固定オフセットで共有システムライブラリを固定しているように見えます。彼は8 MiB(< 64 MiB)のようなデータのみを割り当てているため、「unstack stacksize」で Martin Yorkのコード を実行できます。 しかし、彼は HEAP を使い切るずっと前に STACK を使い果たします。
Linuxを使用しています。 ごめんなさい。これがニッケルです。より良いOSを手に入れましょう。
Macには回避策があります。しかし、それらはく乱雑になり、カーネルまたはリンカーのパラメーターを微調整する必要があります。
長い目で見れば、Appleが本当に馬鹿げたことをしない限り、64ビットのアドレス空間は、このスタック制限のすべてをReal Soon Nowでいつか廃止します。
断片化への移行:
いつでもスタックに何かをプッシュします 最後に追加されます。そして、現在のブロックが終了するたびに削除(ロールバック)されます。
その結果、スタックに穴はありません。すべて1つの大きな使用済みメモリブロックです。おそらく最後に少しだけ未使用のスペースがあれば、すべて再利用の準備ができています。
対照的に、 HEAP が割り当てられて解放されると、未使用のメモリホールが発生します。これらは、徐々にメモリフットプリントの増加につながります。コアリークが通常意味するものではありませんが、結果は似ています。
メモリの断片化は、 HEAP ストレージを回避する理由ではありませんではありません。コーディングしているときに注意する必要があります。
W
Push
および pop
命令は、ローカルスタックフレーム変数の格納には使用されません。関数の開始時に、関数のローカル変数に必要なバイト数(ワードサイズに合わせて)だけスタックポインターをデクリメントすることにより、スタックフレームが設定されます。これにより、「スタック上」に必要なスペースが割り当てられます。これらの値。すべてのローカル変数は、このスタックフレーム(x86では ebp
)へのポインターを介してアクセスされます。
スタックは、ローカル変数、関数呼び出しから戻るための情報などを格納する大きなメモリブロックです。スタックの実際のサイズはOSによって大きく異なります。たとえば、Windowsで新しいスレッドを作成する場合、 defaultサイズは1 MBです。
スタックで現在使用可能なメモリよりも多くのメモリを必要とするスタックオブジェクトを作成しようとすると、スタックオーバーフローが発生し、問題が発生します。大規模なエクスプロイトコードは、意図的にこれらの条件または類似の条件を作成しようとします。
スタックは整数サイズのチャンクに分割されません。これは単なるフラットなバイト配列です。 「整数」によってインデックスが付けられます。タイプがsize_t(intではない)。現在使用可能なスペースに収まる大きなスタックオブジェクトを作成する場合、スタックポインターをバンプ(またはダウン)することでそのスペースを使用します。
他の人が指摘したように、スタックではなく、大きなオブジェクトにヒープを使用するのが最善です。これにより、スタックオーバーフローの問題が回避されます。
編集: 64ビットアプリケーションを使用していて、OSとランタイムライブラリが適切な場合(mrreeの投稿を参照)、大きな一時オブジェクトをスタック。アプリケーションが32ビットであるか、OSまたはランタイムライブラリが適切でない場合、おそらくこれらのオブジェクトをヒープに割り当てる必要があります。
関数を入力するたびに、その関数のローカル変数に合わせてスタックが大きくなります。たとえば400バイトを使用する largeObject
クラスがある場合:
void MyFunc(int p1, largeObject p2, largeObject *p3)
{
int s1;
largeObject s2;
largeObject *s3;
}
この関数を呼び出すと、スタックは次のようになります(詳細は呼び出し規約とアーキテクチャに基づいて異なります):
[... rest of stack ...]
[4 bytes for p1]
[400 bytes for p2]
[4 bytes for p3]
[return address]
[old frame pointer]
[4 bytes for s1]
[400 bytes for s2]
[4 bytes for s3]
スタックの動作に関する情報については、 x86呼び出し規約をご覧ください。 また、MSDNには、サンプルコードおよび結果のスタック図。
他の人が言ったように、「大規模なオブジェクト」が何を意味するのか明確ではありません...
単純に複数のスタックを占有しますか " slots"?
あなたは単に整数より大きいものを意味すると仮定します。ただし、他の誰かが指摘したように、スタックには整数サイズの「スロット」がありません。 -それは単なるメモリのセクションであり、その中のすべてのバイトは独自のアドレスを持っています。コンパイラは、すべての変数をその変数の first バイトのアドレスで追跡します。これは、アドレス演算子(& var
を使用した場合に得られる値です) )、およびポインターの値は、他の変数のこのアドレスです。コンパイラーは、すべての変数がどの型であるか(変数を宣言したときに指定したもの)、および各型の大きさを知っています-プログラムをコンパイルするとき、それらのスペースの量を計算するために必要な計算を行います変数は、関数が呼び出されたときに必要になり、その結果を関数エントリポイントコード(PDaddyが言及したスタックフレーム)に含めます。
CおよびC ++では、スタックが制限されているため(予想どおり)、大きなオブジェクトをスタックに格納しないでください。通常、各スレッドのスタックは数メガバイト以下です(スレッドの作成時に指定できます)。 " new"を呼び出すときオブジェクトを作成するために、それはスタックに置かれません-代わりにヒープに置かれます。
スタックサイズは制限されています。通常、スタックサイズはプロセスの作成時に設定されます。そのプロセスの各スレッドは、CreateThread()呼び出しで指定されていない場合、デフォルトのスタックサイズを自動的に取得します。したがって、はい:複数のスタック「スロット」が存在する可能性がありますが、各スレッドには1つしかありません。また、スレッド間で共有することはできません。
残りのスタックサイズより大きいオブジェクトをスタックに配置すると、スタックオーバーフローが発生し、アプリケーションがクラッシュします。
したがって、非常に大きなオブジェクトがある場合は、スタックではなくヒープに割り当てます。ヒープは、仮想メモリの量によってのみ制限されます(スタックよりも大きい)。
スタックに置くのが理にかなわないほど十分に大きい(または十分に多い)オブジェクトを持つことができます。その場合、オブジェクトをヒープに配置し、そのオブジェクトへのポインターをスタックに配置できます。これは、値渡しと参照渡しの違いです。
ラージオブジェクトをどのように定義しますか?割り当てられているスタックスペースのサイズよりも大きいか小さいかを話しますか?
たとえば、次のようなものがある場合:
void main() {
int reallyreallybigobjectonthestack[1000000000];
}
システムによっては、オブジェクトを格納するための十分なスペースがないため、セグメンテーション違反が発生する可能性があります。それ以外の場合は、他のオブジェクトと同様に保存されます。実際の物理メモリで話す場合は、オペレーティングシステムレベルの仮想メモリがそれを処理するため、これについて心配する必要はありません。
また、スタックのサイズは、オペレーティングシステムとアプリケーションのレイアウトに完全に依存する整数のサイズではない可能性が高い仮想アドレススペース。
「スタックは整数のサイズ」とは、「スタックポインターは整数のサイズです」という意味です。スタックの最上部を指します。これはメモリの巨大な領域です。まあ、整数より大きい。