コンパイラがスタックを管理する方法とスタック上の値へのアクセス方法の具体例を提供するために、視覚的な描写に加えてGCC
、i386をターゲットアーキテクチャとするLinux環境で生成されたコードを見ることができます。
1.スタックフレーム
ご存知のように、スタックは、関数またはプロシージャによって使用される実行中のプロセスのアドレス空間内の場所です。つまり、ローカルに宣言された変数と関数に渡される引数用にスタック上に空間が割り当てられます関数の外部で宣言された変数(グローバル変数など)のスペースは、仮想メモリの別の領域に割り当てられます)。関数のすべてのデータに割り当てられたスペースは、スタックフレームと呼ばれます。これは、複数のスタックフレームの視覚的な描写です(コンピューターシステム:プログラマーの視点から)。
2.スタックフレーム管理と変数の場所
特定のスタックフレーム内のスタックに書き込まれた値がコンパイラーによって管理され、プログラムによって読み取られるようにするには、これらの値の位置を計算し、メモリーアドレスを取得する方法が必要です。これには、スタックポインターとベースポインターと呼ばれるCPU内のレジスタが役立ちます。
ebp
慣例により、ベースポインターには、スタックの最下部またはベースのメモリアドレスが含まれます。スタックフレーム内のすべての値の位置は、参照としてベースポインターのアドレスを使用して計算できます。これは上の図に示されてい%ebp + 4
ます。たとえば、ベースポインタに4を加えたメモリアドレスです。
3.コンパイラ生成コード
しかし、取得できないのは、スタック上の変数がアプリケーションによって読み取られる方法です-xを整数として宣言して割り当て、x = 3とし、ストレージがスタック上に予約され、その値3が格納される場合そこに、同じ関数で宣言してyを4と言って割り当て、それに続いて別の式でxを使用します(たとえばz = 5 + x)プログラムはどのようにxを読み取ってzを評価しますか?スタック上でy未満ですか?
Cで書かれた簡単なサンプルプログラムを使用して、これがどのように機能するかを見てみましょう。
int main(void)
{
int x = 3;
int y = 4;
int z = 5 + x;
return 0;
}
このCソーステキスト用にGCCによって生成されたアセンブリテキストを調べてみましょう(わかりやすくするために少し整理しました)。
main:
pushl %ebp # save previous frame's base address on stack
movl %esp, %ebp # use current address of stack pointer as new frame base address
subl $16, %esp # allocate 16 bytes of space on stack for function data
movl $3, -12(%ebp) # variable x at address %ebp - 12
movl $4, -8(%ebp) # variable y at address %ebp - 8
movl -12(%ebp), %eax # write x to register %eax
addl $5, %eax # x + 5 = 9
movl %eax, -4(%ebp) # write 9 to address %ebp - 4 - this is z
movl $0, %eax
leave
観察されるのは、変数x、y、およびzがそれぞれアドレス%ebp - 12
、%ebp -8
および%ebp - 4
にあることです。言い換えれば、スタックフレーム内の変数の位置はmain()
、CPUレジスタに保存されたメモリアドレスを使用して計算され%ebp
ます。
4.スタックポインターを超えたメモリ内のデータは範囲外です
私は明らかに何かが欠けています。スタック上の場所は変数の有効期間/スコープにすぎず、スタック全体が常にプログラムから実際にアクセス可能であるということですか?もしそうなら、値を取得できるようにスタック上の変数のアドレスのみを保持する他のインデックスがあることを意味しますか?しかし、その後、スタックのポイントは、値が変数アドレスと同じ場所に格納されていることだと思いましたか?
スタックは仮想メモリ内の領域であり、その使用はコンパイラによって管理されます。コンパイラは、スタックポインタを超える値(スタックの最上部を超える値)が参照されないようにコードを生成します。関数が呼び出されると、スタックポインターの位置が変化して、いわば「境界外」ではないと見なされるスタック上のスペースが作成されます。
関数が呼び出されて返されると、スタックポインターはデクリメントおよびインクリメントされます。スタックに書き込まれたデータは、スコープ外になっても消えませんが、コンパイラは%ebp
またはを使用してこれらのデータのアドレスを計算する方法がないため、このデータを参照する命令を生成しません%esp
。
5.まとめ
CPUによって直接実行できるコードは、コンパイラーによって生成されます。コンパイラは、スタック、関数のスタックフレーム、およびCPUレジスタを管理します。GCCがi386アーキテクチャで実行するコード内のスタックフレーム内の変数の場所を追跡するために使用する戦略の1つ%ebp
は、スタックフレームの場所への参照および変数値の書き込みとして、スタックフレームベースポインターのメモリアドレスを使用することですのアドレスへのオフセットで%ebp
。