为什么推送堆栈先从堆栈指针中减去,然后再插入数据?



我想知道为什么推送指令先减去然后插入数据。

通过此实现,堆栈指针指向推送的最后一个数据。尽管这通常不是问题,但我认为由于在程序开始时堆栈中没有有效数据,因此首先插入然后减少堆栈指针更有意义。

为什么会这样:

sub  $8,%rsp        # subtract 8 from rsp
mov  reg,(%rsp)     # store, using rsp as the address

而不是这个:

mov  reg,(%rsp)     # store, using rsp as the address
sub  $8,%rsp        # subtract 8 from rsp

他们选择这种操作顺序有什么特殊原因吗?

例如,在 32 位 x86 体系结构中,您可以将不同的大小值推送到堆栈中。无法事先知道要推送的大小值,因此指针无法事先移动,在这种情况下是在存储之后。

因此,假设您将有以下代码:

PUSH EAX
PUSH BX
PUSH ECX

如果我们先存储然后减去,我们会得到这个(假设 ESP 是 100,忽略 ESP/SP 混合使用可能出现的问题):

MOV EAX, (%ESP) // EAX -> 100..103
SUB %ESP, 4 // ESP = 96
MOV BX, (%SP) // BX -> 96..97
SUB %SP, 2. // ESP = 94
MOV ECX, (%RSP) // ECX -> 94..97
SUB %RSP, 4 // ESP = 90

查看减法如何始终将上一个大小用于下一个大小。这意味着推送 BX 首先会导致堆栈中有两个未使用的字节,然后推送 ECX 实际上会覆盖堆栈中 BX 的值。首先执行减法时,它始终使用推送到堆栈的当前值的大小:

SUB %ESP, 4 // ESP = 96
MOV EAX, (%ESP) // EAX -> 96..99
SUB %SP, 2. // ESP = 94
MOV BX, (%SP) // BX -> 94..95
SUB %RSP, 4 // ESP = 90
MOV ECX, (%RSP) // ECX -> 90..93

现在没有任何东西被覆盖,没有出现任何差距,每个人都很高兴。

当然,这假设堆栈向下增长,就像通常一样。如果堆栈向上移动,则添加将在存储之后以保持地址正确。

在 x64 模式下,它会更简单,因为值(据我所知)总是作为 8 字节块推送。

此外,其他体系结构可能允许不同类型的推送,因此在存储数据之前在推送时移动指针会导致最通用的操作。

相关内容

最新更新