我想知道为什么推送指令首先减去然后插入数据。
通过此实现,堆栈指针指向最后推送的数据。虽然这通常不是问题,但我认为,因为在程序开始时堆栈中没有有效数据,所以首先插入然后减少堆栈指针更有意义。
为什么:
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
他们选择这种操作顺序是否有特殊原因?
答案 0 :(得分:2)
例如,在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字节块。
此外,其他架构可能允许不同类型的推送,因此在存储数据之前将指针移动到按钮上会导致最通用的操作。