49

アセンブリでスタックを使用しましたが、プッシュ ebp とポップ ebp についてはわかりませんでした。

.intel_syntax noprefix

.include "console.i"

.text

askl:   .asciz  "Enter length: "
askb:   .asciz  "Enter breadth: "
ans:    .asciz  "Perimeter = "

_entry:

    push    ebp     # establishing stack-frame
    mov ebp, esp
    sub esp, 12

    Prompt  askl
    GetInt  [ebp-4]     # length
    Prompt  askb
    GetInt  [ebp-8]     # breadth

    mov eax, [ebp-4]    # eax = l
    add eax, [ebp-8]    # eax = l + b
    add eax, eax    # eax = 2 * (l + b)
    mov [ebp-12], eax

    Prompt  ans
    PutInt  [ebp-12]
    PutEoL

    mov esp, ebp
    pop ebp     # unwinding stack-frame
    ret

.global _entry

.end
4

2 に答える 2

81

多分あなたはこれについて疑問に思っています:

push    ebp
mov ebp, esp
sub esp, 12

これらの行は、アセンブリ関数のプロローグとして知られています。最初の 2 つの命令は、前のベース ポインター (ebp) を保存し、スタック上のその位置 (戻りアドレスのすぐ下) を指すように EBP を設定します。これにより、EBP がフレーム ポインタとして設定されます。

このsub esp,12行は、関数内のローカル変数のスペースを節約しています。そのスペースは、 のようなアドレス指定モードでアドレス指定できます[ebp - 4]。関数引数のプッシュ/ポップ、またはcall戻りアドレスをプッシュする命令自体、または呼び出す関数のスタック フレームは、現在の ESP で、この予約済みスペースの下で発生します。

最後に、次のものがあります。

mov esp, ebp         ; restore ESP
pop ebp              ; restore caller's EBP
ret                  ; pop the return address into EIP

これは、プロローグとは逆の動作 (つまり、エピローグ) であるため、前のコンテキストを復元できます。これは、スタック フレームの「破棄」と呼ばれることがあります。

(EBP は、すべての標準的な x86 呼び出し規則で不揮発性、つまり呼び出しが保持されます。変更した場合は、呼び出し元の値を復元する必要があります。)

このleave命令は、これら 2 つの命令とまったく同じことを行い、コード サイズを節約するために一部のコンパイラで使用されます。(enter 0,0非常に遅く、使用されることはありません ( https://agner.org/optimize/ ); leavemov + pop とほぼ同じくらい効率的です。)


EBP をフレーム ポインタとして使用することはオプションであり、コンパイラは最適化されたコードのほとんどの関数に対してそれを行わないことに注意してください。代わりに、個別のメタデータを保存して、スタックの巻き戻し/バックトレースを可能にします。

于 2010-09-03T17:35:57.560 に答える
51

ebpベース ポインターまたはフレーム ポインターと呼ばれます。関数に入ると、それをプッシュします (呼び出し関数の値を保存するため)。esp次に、スタック ポインタであるを にコピーして、関数のスタック フレームを指すようにしますebpebp関数の最後でebp、呼び出し元の関数の値が復元されるようにポップします。

正確に何が起こっているのかを明確にするために、push命令は指定されたレジスタ(ebpこの場合)から値をスタックに置き、スタックポインタを適切な量だけ減らします。操作は逆です。popスタック ポインタをインクリメントし、スタックから値を取得して、指定されたレジスタに格納します。

于 2010-09-03T17:26:57.610 に答える