我想知道为什么推送指令先减去然后插入数据。
通过此实现,堆栈指针指向推送的最后一个数据。尽管这通常不是问题,但我认为由于在程序开始时堆栈中没有有效数据,因此首先插入然后减少堆栈指针更有意义。
为什么会这样:
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 字节块推送。
此外,其他体系结构可能允许不同类型的推送,因此在存储数据之前在推送时移动指针会导致最通用的操作。