1

現在、x86 システムでアセンブリ言語を学ぼうとしています。そのため、「ゼロからのプログラミング」という本を準備しています。( http://download.savannah.gnu.org/releases/pgubook/で無料で入手可能)

53 ページでは、コンピューターのスタックの仕組みが説明されています。

コンピュータのスタックは、メモリの最上位アドレスに存在します。pushl という命令を使用して、値をスタックの一番上にプッシュできます。[...] ええと、スタックの「トップ」は実際にはスタックのメモリのボトムです。[...] メモリ内では、スタックはメモリの一番上から始まり、アーキテクチャ上の考慮事項により下に向かって成長します。したがって、「スタックの一番上」と呼ぶときは、スタックのメモリの一番下にあることを思い出してください。

私が得るその部分。スタックのメモリがアドレス 0 で始まり、アドレス 11 (包括的) で終わるとします。つまり、スタックには現在 3 つのワード (1 ピースあたり 4 バイト) があります。私の理解によると、スタックの「トップ」にあるワードは現在、アドレス 8、9、10、および 11 を占有しています (1 ワードは 4 バイトであり、したがってメイン メモリ内の 4 つの格納場所を占有するため)。しかし、本は現在、次のように述べています。

スタック レジスタ %esp には、常にスタックの現在のトップへのポインタが含まれています。

さて、私の例では、%esp レジスターはアドレス 8 を保持します。これは、現在スタックの一番上にあるワードを指します。しかし...

pushl を使用して何かをスタックにプッシュするたびに、%esp が 4 減算され、スタックの新しいトップを指すようになります (各ワードは 4 バイトの長さであり、スタックは下に向かって成長することに注意してください)。

何?まったく逆じゃない?別の 4 バイト サイズのマシン ワードをスタックにプッシュすると、このワードはメイン メモリ アドレス 12 ~ 15 を占有します。%esp レジスタは、現在スタックの一番上にあるワードを指します。それはアドレス 12 から始まります。別のワードをスタックにプッシュする前は、%esp に格納されていたアドレスは 8 でした。したがって、%esp は明らかに減算ではなく 4 が加算されています。彼らはどこから減算を取得しますか? 私は何を取りこぼしたか?私は非常に混乱しています...

助けていただければ幸いです;)

4

3 に答える 3

3

別の 4 バイト サイズのマシン ワードをスタックにプッシュすると、このワードはメイン メモリ アドレス 12 ~ 15 を占有します。

下向きは下位アドレスに向かっていることを意味するため、スタックに別の値をプッシュすることは、4 を減算して新しい場所に値を書き込むことを意味します。したがって、%esp は 4 になります。

  +--------+
8 |12345678| <- top of stack before push
  +--------+
4 |11223344| <- top of stack
  +--------+
0 |00000000|
  +--------+
于 2013-10-26T13:21:42.923 に答える
0

通常、「メモリのアドレスの先頭」は最上位のアドレスを指します。たとえば、スタックは0x00105000;で始まる場合があります。単語を追加すると、 に移動espします (つまり、メモリ アドレス0x00104ffcが下方向に伸びます)。素敵な図については、たとえばここを参照してください。

于 2013-10-26T13:21:46.817 に答える
0

関数を入力し、スタックが 0x100 の場合。通常、スタックは上位アドレスから下位アドレスへとゼロに向かって下向きに成長します。そのため、4 バイトのアイテムを 1 つプッシュすると、スタックの「トップ」は 0xFC になります。別の 0xF8 などをプッシュします。

ここで邪魔になっているもう 1 つのことは、ebp と esp です。

一般に、プロセッサにはスタック ポインターがあります。これは、特定の命令、プッシュ、ポップ、およびおそらくスタック ポインターの相対的なアドレス指定のロードとストアが関連付けられている特殊または場合によっては汎用のレジスターです。また、コンパイラがスタック フレームに対して、スタック ポインタとは異なる汎用 (または専用) レジスタを使用することも珍しくありません。なんで?コンパイラが生成したコードの読み取りとデバッグを少し簡単にします。

たとえば、関数のエントリ時に、スタック ポインターのコピーをこの他のレジスターに作成することができます。レジスターが移動しない関数の期間中、これにより、そのレジスターへの相対アドレス指定を実行して、ローカル変数を見つけることができます。関数パラメーター (このコンパイラーとプロセッサーがスタックにパラメーターを渡す傾向がある場合)。したがって、関数全体を通して、同じオフセットでこれらのローカル変数のいずれかにアクセスできます。スタック フレームを使用しなかった場合、最適化の理由 (スタック メモリの節約) のためにスタック ポインターが関数内で動的である場合、スタック ポインターからローカル変数およびスタック ベースの関数パラメーターへの相対オフセットも動的になります。読み取りとデバッグが難しくなります。(スタックスペースを節約し、他の用途のためにレジスタを返す一方で、両方の最適化)。

いずれにせよ、関数を呼び出す関数は、ネストされた関数が呼び出されるときに「スタックの一番上」にスタック ポインターを持っている必要があります。これにより、その関数は他の関数と同じようにスタックを利用し、その関数を超えて何かを使用/破棄できると想定できます。独自の目的のためのスタックの終わり。

于 2013-10-26T13:22:02.123 に答える