我们为什么要这样做:

这是有历史原因的。在16位代码中......

...... x86 CPU不允许所有寄存器用于内存寻址。

...... 地址相对于“段”(例如16 * ss或16 * ds)。

由于无法直接使用sp访问内存(例如10(%sp) - 这在16位代码中不可能),您必须先将sp复制到另一个寄存器,然后再访问内存(例如将sp复制到bp,然后执行10(%bp))。

当然,也可以使用bx、si或di代替bp。

但是,第二个问题是段:使用这些寄存器之一将访问由ds寄存器指定的段。要访问堆栈上的内存,我们必须执行ss:10(%bx)而不是10(%bx)。隐式地使用bp访问包含堆栈的段(速度更快且指令比显式指定段短一个字节)。

在32位(或64位)代码中,这一切都不再需要。我刚刚使用现代C编译器编译了一个函数。结果是:

movl 12(%esp), %eax

imull 8(%esp), %eax

addl 4(%esp), %eax

ret

正如您所看到的,ebp寄存器没有被使用。

然而,现代代码仍然使用ebp有两个原因:

更容易创建函数。即使您的函数包含push和pop指令,您也知道第一个参数始终位于8(%ebp)处。如果使用esp,则每个push或pop操作都会更改第一个参数的位置。

使用alloca函数:该函数将以可能对编译器不可预测的方式修改esp寄存器!因此,您需要一个原始esp寄存器的副本。

alloca使用示例:

push %ebp

mov %esp, %ebp

call GetMemorySize # This will set %eax

# ---- Start of alloca() ----

# The alloca "function" will reserve N bytes on the

# stack while the value N is calculated during

# the run-time of the program (here: by the function

# GetMemorySize)

or $3, %al

inc %eax

# This has the same effect as multiple "push"

# instructions. However, we don't know how many

# "push" instructions!

sub %eax, %esp

mov %esp, %eax

# From this moment on, we would not be able to "restore"

# the original stack any more if we didn't have a copy

# of the stack pointer!

# ---- End of alloca() ----

push %eax

mov 8(%ebp), %eax

push %eax

call ProcessSomeData

mov %ebp, %esp

pop %ebp

# Of course we need to restore the original value

# of %esp before we can do a "ret".

ret

2025-09-26 04:08:33