プログラムカウンターが次に実行される命令のアドレスを指している場合、フレームポインターは何をしますか?
6 に答える
スタックポインタのより安定したバージョンのようなものです
一部のローカル変数とパラメーターのストレージは、通常、関数呼び出し後にスタック ポインターを元のレベルに戻すだけで自動的に解放されるスタック フレームに割り当てられます。
ただし、スタック ポインターは、新しい呼び出しレベルのスタックに引数をプッシュするために頻繁に調整され、メソッドへのエントリで少なくとも 1 回は独自のローカル変数を割り当てるために調整されます。スタック ポインターを調整する理由は他にもあります。
このすべての調整により、パラメーター、ローカル、および一部の言語では中間の字句スコープに到達するためのオフセットの使用が複雑になります。コンパイラーが追跡することはおそらくそれほど難しくありませんが、プログラムがデバッグされている場合、デバッガー (人間またはプログラム) も変化するオフセットを追跡する必要があります。
技術的に不要なオーバーヘッドである場合は、現在のフレームを指すようにレジスタを割り当てる方が簡単です。x86 では、これは%ebp
. 関数へのエントリでは、スタック ポインタとの固定関係がある場合があります。
デバッグに加えて、これにより例外管理が簡素化され、スタック ポインターへの調整を排除または最適化することで元が取れる場合もあります。
プログラムカウンターについて言及したので、一般にフレームポインターは完全にソフトウェア構造であり、事実上すべてのマシンがレジスタ+オフセットアドレッシングモードを実行できる範囲を除いて、ハードウェアが実装するものではないことに注意してください。x86 などの一部のマシンは、フレームの作成と復元のためのアドレッシング モードとマクロ命令の形式でハードウェア サポートを提供します。ただし、コア命令の方が高速であり、マクロ操作が非推奨になる場合があります。
これは完全にコンパイラに依存しているため、実際にはCの質問ではありません。
ただし、スタックフレームは、現在の関数とその親関数について考えるのに便利な方法です。通常、フレームポインターは、(指定されたスタックの深さに対して)スタック上の特定の場所を指します。この場所から、渡されたパラメーターとローカル変数を見つけることができます。
次に例を示します。たとえば、1つの引数を取り、1からその引数までのすべての数値の合計を返す関数を呼び出したとします。Cコードは次のようになります。
unsigned int x = sumOf (7);
: :
unsigned int sumOf (unsigned int n) {
unsigned int total = 0;
while (n > 0) {
total += n;
n--;
}
return total;
}
この関数を呼び出すために、呼び出し元は7をスタックにプッシュしてから、サブルーチンを呼び出します。関数自体がフレームポインタを設定し、ローカル変数にスペースを割り当てるため、次のコードが表示される場合があります。
mov r1,7 ; fixed value
push r1 ; push it for subroutine
call sumOf ; then call
retLoc: mov [x],r1 ; move return value to variable
: :
sumOf: mov fp,sp ; Set frame pointer to known location
sub sp,4 ; Allocate space for total.
: :
その時点で(に続いてsub sp,4
)、次のスタック領域があります。
+--------+
| n(7) |
+--------+
| retLoc |
+--------+
fp -> | total |
+--------+
sp -> | |
+--------+
また、フレームポインタの上にあるアドレスとフレームポインタの下にあるローカル変数を使用して、渡されたパラメータを見つけることができることがわかります。
[fp+8]
関数は、でメモリの内容を使用して、渡された値(7)にアクセスできます(fp+8
この例では、これらのセルのそれぞれは4バイトです)。また、でメモリの内容を使用して、独自のローカル変数(total
)にアクセスすることもできます。他のローカルには、などの対応する下位アドレスがあるため、ゼロを減算しても効果はありませんが、命名法を使用しました。[fp-0]
fp-0
fp-0
fp-4
fp-8
スタックを上下に移動すると、フレームポインターも移動します。通常、関数を呼び出す前に前のフレームポインターをスタックにプッシュして、関数を終了するときに簡単に回復できるようにします。ただし、関数内ではスタックポインターが乱暴に移動する可能性がありますが、フレームポインターは通常一定のままであるため、関連する変数をいつでも見つけることができます。
ここで、例とすべてを使って良い議論をします。
つまり、FPはスタック上の関数のフレーム内の固定スポットを指しているため(関数の実行中には変更されません)、渡されたすべての引数と関数のローカル(「自動」)変数にFPからのオフセットでアクセスできます。 (SPは関数の実行中に変更できますが、PCは間違いなく変更します;-)。
通常は差出人住所です(ただし、たとえば最後の引数を過ぎた場合もあります)。重要なのは、フレームポインターはメソッドの存続期間中は固定され、スタックポインターは実行中に移動する可能性があるということです。
これは実装に大きく依存します(さらに、実際には言語の概念ではなく、マシンの概念です)。
あなたが提供したコメントから別の答えに持ち上げられました:
うわー...スタックポインタ?...それはプログラムカウンターの同義語ですか?
コールスタックについて読んでください。基本的に、呼び出しスタックは、現在のメソッドにローカルなデータ(ローカル変数、メソッドへのパラメーター、および呼び出し元への戻りアドレス)を格納します。スタックポインタは、新しいスペースが割り当てられる構造の最上部を指します(スタックポインタを「上」に移動することにより)。
フレームポインタは、現在のフレーム(現在のローカル関数)のメモリ領域を指します。通常は、現在のローカル関数のリターンアドレスを指します。
まだ誰もこれに反応していないので、試してみます。フレームポインタ(メモリが機能する場合)は、スタックポインタとともにスタックの一部です。スタックは、スタックフレーム(アクティベーションレコードと呼ばれることもあります)で構成されます。スタックポインタはスタックの最上位を指しますが、フレームポインタは通常、リターンアドレスの場所など、フレーム構造内の固定点を指します。ウィキペディアの写真と一緒に、より詳細な説明があります。