关于如何在 Assembly 中使用堆栈,我几乎没有疑问。据我所知,%rsp 寄存器用作堆栈指针。要在汇编代码中在堆栈上分配新内存,只需从 %rsp 中减去所需的数量,然后将其向后移动。然后,您可以通过向 %rsp 添加特定值来访问新分配的内存。
但是,当我使用 GCC 将 C 代码编译为汇编时,有时会得到奇怪的结果。
当我做一些这样的功能时:
int fun(int arg1, int arg2)
{
return arg2;
}
我希望是这样的:
fun: pushq %rbp
movq %rsp, %rbp
sub $8, %rsp
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -4(%rbp), %eax
addq $8, %rsp
popq %rbp
ret
相反,我得到了这个:
fun:
pushq %rbp
movq %rsp, %rbp
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl 16(%rbp), %eax
popq %rbp
ret
它实际上并没有移动堆栈指针,它只是使用它后面的空间。当我传递 7 个参数时,它变得更加奇怪:
int fun(int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7)
{
return arg7;
}
现在汇编代码:
fun:
pushq %rbp
movq %rsp, %rbp
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl %edx, -12(%rbp)
movl %ecx, -16(%rbp)
movl %r8d, -20(%rbp)
movl %r9d, -24(%rbp)
movl 16(%rbp), %eax
popq %rbp
ret
在上一个示例之后,我预计代码不会从 %rsp 中减去任何内容。使用较小的地址完全没问题,那里什么都没有。但是 16(%rsp) 呢?它应该指向已经在堆栈中分配的空间,它不会覆盖一些东西吗?
最后但并非最不重要的一点是,如果我编写那个简单的函数:
void fun(int arr[], int n)
{
int i = 0;
while(i < n)
{
++arr[i++];
}
}
汇编代码:
fun:
.LFB0:
pushq %rbp
movq %rsp, %rbp
movq %rdi, -24(%rbp)
movl %esi, -28(%rbp)
movl $0, -4(%rbp)
jmp .L2
.L3:
movl -4(%rbp), %eax
leal 1(%rax), %edx
movl %edx, -4(%rbp)
cltq
leaq 0(,%rax,4), %rdx
movq -24(%rbp), %rax
addq %rdx, %rax
movl (%rax), %edx
addl $1, %edx
movl %edx, (%rax)
.L2:
movl -4(%rbp), %eax
cmpl -28(%rbp), %eax
jl .L3
nop
popq %rbp
ret
如您所见,指向 arr 的指针存储在 -24(%rsp) 到 -16(%rsp) 中。整数 n 存储在 -28(%rsp) 到 -24(%rsp) 中,整数 i 存储在 -4(%rsp) 到 (%rsp) 中。-16(%rsp) 到 -4(%rsp) 之间的空间呢?为什么不用?这背后有什么特别的原因吗?