我们为什么要这样做:
这是有历史原因的。在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