我有一个C函数,它可以进行一些SSE计算。当我用GCC编译它时,我得到了下一个代码
/* Start of function */
mov (%rdi),%rax
movslq %ecx,%rcx
...
mov 0x8(%rdi),%rax
pxor %xmm12,%xmm3
movaps %xmm0,-0x28(%rsp)
movaps %xmm6,%xmm1
...
movaps 0x50(%rax,%rcx,1),%xmm2
movaps 0x60(%rax,%rcx,1),%xmm15
pxor %xmm2,%xmm0
pxor %xmm2,%xmm6
movaps -0x28(%rsp),%xmm2
pxor %xmm15,%xmm5
pxor %xmm15,%xmm2
movaps 0x70(%rax,%rcx,1),%xmm15
movaps (%rax,%rcx,1),%xmm11
mov 0x10(%rdi),%rax
movaps %xmm15,-0x18(%rsp)
pxor %xmm11,%xmm4
pxor %xmm12,%xmm11
pxor %xmm15,%xmm12
看看movaps
指令——它在栈顶上访问内存:
movaps %xmm15,-0x18(%rsp)
这不是访问未定义的内存吗?为什么GCC生成了如此错误的代码?
在汇编级没有"未定义内存"这回事。gcc可以自由地发出代码,以任何它认为合适的方式访问堆栈,只要行为符合预期。
我对为什么会发生这种情况的猜测是,这是一个叶函数,调整堆栈指针是徒劳的。您可以尝试通过检查程序集是否有任何call
指令来验证这一点。(您也可以检查C源代码,但内联可能会降低其可靠性。)
某些平台的ABI明确允许这种欺骗行为,包括x86-64。来自AMD64 ABI文档:
%rsp指向的位置之外的128字节区域被视为保留,不得通过信号或中断进行修改处理程序。因此,函数可能会将此区域用作临时数据这在函数调用中是不需要的。特别是叶子函数可以将该区域用于其整个堆栈帧,而不是在序言和尾声中调整堆栈指针。该区域被称为红色区域。
这篇博客文章可能会引起对这个主题的有趣阅读。