コンパイラは常に自己整合性のあるコードを生成するため、フレーム ポインタを無効にしても問題ありませんが、フレーム ポインタについてなんらかの仮定を行う (たとえば、の値に依存するなどして) 外部コードや手作りコードを使用しない限りは問題ありませんrbp
。
割り込みはフレーム ポインター情報を使用しません。最小限のコンテキストを保存するために現在のスタック ポインターを使用する場合がありますが、これは割り込みの種類と OS に依存します (ハードウェア割り込みはおそらくリング 0 スタックを使用します)。
詳細については、インテルのマニュアルを参照してください。
フレーム ポインターの有用性について:
何年も前に、いくつかの単純なルーチンをコンパイルし、生成された 64 ビット アセンブリ コードを調べた後、私はあなたと同じ質問をしました。
当時私が自分自身のために書いたたくさんのメモを読んでも構わないのであれば、ここにあります。
注: 何かの有用性について尋ねることは、少し相対的です。現在のメインの 64 ビット ABI のアセンブリ コードを書いていると、スタック フレームを使用することがますます少なくなっていることに気付きました。ただし、これは私のコーディング スタイルと意見です。
フレーム ポインターを使用して関数のプロローグとエピローグを記述するのが好きですが、不快な直接的な回答も好きなので、次のように考えています。
はい、フレーム ポインターは x86_64 ではほとんど役に立ちません。
特に人間にとっては、完全に役に立たないわけではありませんが、コンパイラはもう必要としません。そもそもフレーム ポインターがある理由をよりよく理解するには、いくつかの履歴を思い出すことをお勧めします。
リアル モード (16 ビット) の時代に戻る
Intel CPUが「16ビットモード」のみをサポートしていたとき、スタックへのアクセス方法にいくつかの制限がありました。特に、この命令は(そして今でも)違法です
mov ax, WORD [sp+10h]
sp
ベースレジスタとして使用できないためです。このような目的で使用できる指定レジスタは、たとえばbx
、またはより有名なbp
.
今日では、誰もが目を向ける詳細ではありませんが、bp
他のベース レジスタよりも有利な点があります。デフォルトでは、 (by 、、など)の暗黙的な使用とss
同様に、セグメント/セレクタ レジスタとしての使用が暗黙的に含まれます。それ以降の 32 ビット プロセッサ。
プログラムがメモリ全体に散在し、各セグメント レジスタが異なる領域を指し、同じように動作したとしても、結局のところ、それが設計者の意図でした。sp
push
pop
esp
bp
sp
そのため、通常はスタック フレームが必要であり、その結果、フレーム ポインターが必要でした。
bp
スタックを 4 つの部分に効果的に分割しました:引数領域、戻りアドレス、古い bp (単なる WORD)、およびローカル変数領域です。各領域は、アクセスに使用されるオフセットによって識別されます。引数と戻りアドレスの場合は正、 old の場合はゼロbp
、ローカル変数の場合は負です。
拡張実効アドレス
Intel CPU が進化するにつれて、より広範な 32 ビット アドレッシング モードが追加されました。
具体的には、任意の 32 ビット汎用レジスタをベース レジスタとして使用する可能性です。これには の使用が含まれますesp
。
このような指示であること
mov eax, DWORD [esp+10h]
現在は有効ですが、スタック フレームとフレーム ポインターの使用は終わりに近づいているようです。
少なくとも当初はそうではなかったようです。
完全に使用できるようになったのは事実ですがesp
、前述の 4 つの領域でのスタックの分離は、特に人間にとって依然として有用です。
フレーム ポインターがないと、プッシュまたはポップによって引数またはローカル変数のオフセットが に相対的に変更されesp
、一見非直感的に見えるコードが形成されます。次の C ルーチンを cdecl 呼び出し規約で実装する方法を検討してください。
void my_routine(int a, int b)
{
return my_add(a, b);
}
フレームスタックの有無
my_routine:
push DWORD [esp+08h]
push DWORD [esp+08h]
call my_add
ret
my_routine:
push ebp
mov ebp, esp
push DWORD [ebp+0Ch]
push DWORD [ebp+08h]
call my_add
pop ebp
ret
一見すると、最初のバージョンは同じ値を 2 回プッシュしているように見えます。ただし、実際には 2 つの別々の引数をプッシュします。最初のプッシュが低下esp
するため、同じ実効アドレス計算が 2 番目のプッシュを別の引数にポイントします。
ローカル変数 (特にそれらの多く) を追加すると、状況がすぐに読みにくくなります:mov eax, [esp+0CAh]
ローカル変数または引数を参照しますか? スタック フレームでは、引数とローカル変数のオフセットが固定されています。
コンパイラでさえ、最初はフレーム ベース ポインタを使用して指定された固定オフセットを優先していました。この動作は gcc で最初に変更されます。
デバッグ ビルドでは、スタック フレームは効果的にコードを明確にし、(熟練した) プログラマーが何が起こっているかを簡単に追跡できるようにし、コメントで指摘されているように、スタック フレームをより簡単に回復できるようにします。
ただし、最新のコンパイラは数学に優れており、スタック ポインターの移動を簡単にカウントesp
し、スタック フレームを省略して から適切なオフセットを生成して、実行を高速化できます。
CISC でデータ アライメントが必要な場合
SSE 命令が導入されるまで、Intel プロセッサは、RISC 兄弟と比較して、プログラマに多くを要求することはありませんでした。
特に、データ アライメントを要求することはありませんでした。4 の倍数ではないアドレスで 32 ビット データにアクセスできましたが、大きな不満はありませんでした (DRAM データ幅によっては、レイテンシが増加する可能性があります)。
SIMD パラダイムがハードウェアで効率的に実装されるようになり、16 バイト境界でのアライメントが重要になるにつれて、SSE は 16 バイト境界でアクセスする必要がある 16 バイト オペランドを使用しました。
メインの 64 ビット ABI で必要になりました。スタックは段落 (つまり、16 バイト) に配置する必要があります。
現在、通常、プロローグの後にスタックが整列されるように呼び出されますが、その保証に恵まれていない場合は、次のいずれかを行う必要があります
push rbp push rbp
mov rbp, rsp mov rbp, rsp
and spl, 0f0h sub rsp, xxx
sub rsp, 10h*k and spl, 0f0h
rbp
これらのプロローグの後に何らかの方法でスタックがアラインされますが、フレーム ポインター自体がアラインされていないため、アラインメントが必要なローカル変数にアクセスするために負のオフセットを使用することはできなくなりました。
を使用する必要があります。ローカル変数の整列領域の上部を指すrsp
プロローグを配置できrbp
ますが、引数は不明なオフセットになります。
複雑なスタック フレーム (おそらく複数のポインター) を配置できますが、昔ながらのフレーム ベース ポインターの鍵はその単純さにありました。
したがって、フレーム ポインターを使用してスタック上の引数にアクセスし、スタック ポインターをローカル変数にアクセスできます。
残念なことに、引数を渡すためのスタックの役割は縮小されており、少数の引数 (現在は 4 つ) については使用さえされておらず、将来的にはおそらくさらに使用されることが少なくなるでしょう。
したがって、フレーム ポインタをローカル変数 (ほとんど) にも引数 (ほとんど) にも使用しませんが、何に使用するのでしょうか?
元の のコピーを保存するrsp
ため、関数の終了時にスタック ポインターを復元するには、amov
で十分です。スタックがand
反転不可能な に揃えられている場合は、元のコピーが必要です。
実際、一部の ABI は、標準のプロローグの後にスタックが整列されることを保証するため、通常どおりフレーム ポインターを使用できます。
一部の変数は位置合わせを必要とせず、位置合わせされていないフレーム ポインターでアクセスできます。これは通常、手作りのコードに当てはまります。
一部の関数には、5 つ以上のパラメーターが必要です。
概要
フレーム ポインターは、ローカル変数と引数にアクセスするときの単純さと明快さのために、32 ビット マシンでも有用であることが証明されている 16 ビット プログラムの痕跡パラダイムです。
ただし、64 ビット マシンでは、厳密な要件は簡素化と明確化のほとんどがなくなりますが、フレーム ポインタはデバッグ モードで使用されたままになります。
フレーム ポインターを使用して楽しいことを行うことができるという事実について: 確かにそのようなコードは見たことがありませんが、どのように機能するかはイメージできます。
ただし、これは私がいつも見ている方法であるため、フレームポインターのハウスキーピングの役割に焦点を当てました。
すべてのクレイジーなことは、フレーム ポインターと同じ値に設定された任意のポインターで実行できます。私は後者に、より「特別な」役割を与えます。
たとえばVS2013はrdi
「フレームポインタ」として使用することがありますが、使用しない場合は実際のフレームポインタとは見なしませんrbp/ebp/bp
。
私にとっての使用rdi
は、フレームポインタの省略の最適化を意味します:)