为什么 MSVC 不在生成的汇编代码中分配 32 字节影子空间?

Chr*_*sen 1 c assembly x86-64 masm visual-c++

我试图查看 MSVC 如何分配其 32 字节的影子空间,但似乎它只分配 8 字节的影子空间。

// Test.c
int main() {int var1 = 1;}
Run Code Online (Sandbox Code Playgroud)

上面的程序生成以下 .asm 文件:

var1$ = 0

main    PROC
; Test.c
    sub rsp, 24                    ; allocates 24 bytes
    mov DWORD PTR var1$[rsp], 1
    xor eax, eax
    add rsp, 24
    ret 0
main    ENDP
Run Code Online (Sandbox Code Playgroud)

它只分配24个字节。当我声明 4 个变量时,它会分配相同的数量,并且由于每个变量都是 4 个字节,因此必然意味着 24 个字节中的 16 个字节用于声明的变量,留下 8 个字节用于影子空间。
仅当声明 5 个变量时,它才会分配 40 字节的影子空间。为什么它只分配8字节的影子空间?
我使用命令编译了程序CL Test.c /Fa

Mic*_*tch 5

这里从RSP中减去 24与影子空间没有任何关系。main仅当调用某些其他 64 位 Microsoft ABI 兼容函数时,影子空间才适用。您的main函数是一个叶函数(它不调用其他任何函数),因此不需要为影子空间分配额外的空间。如果您修改main为调用C/C++库或 WinAPI 中的某些内容,您会发现将为影子空间添加额外的空间来进行此类调用。

鉴于您的函数正在处理 32 位值(并且没有数组)并且不调用任何其他内容,我看不出它为什么需要对齐 16 字节边界或添加额外的填充,但这就是它看起来的样子正在做。堆栈上的返回地址使堆栈错位 8。减去 24 使其在 16 字节边界上对齐,并在变量后面进行填充。

这可能是由于未使用任何优化(如等)进行编译或编译器将局部变量空间填充到首选数量时代码生成效率低下/O1的结果。/O2理论上,在这种情况下不必分配任何堆栈空间。main它可以重用返回地址上方的影子空间,该空间是由C/C++启动代码分配给该函数的。

注意:通过优化,代码将被完全消除,除非您创建了var1一个volatile变量。编译器应该认识到您编写的代码除了返回给调用者之外不执行任何操作。


ExitProcess添加了以下示例调用以显示阴影空间;重用由调用局部变量的C/C++启动代码分配的影子空间main;并为变量使用一些堆栈空间,它无法容纳在影子空间中。作为一个被调用的 WinAPI,ExitProcess在调用它之前需要分配 32 字节的影子空间。如果从本示例中删除它,编译器将不会为其分配额外的空间。

测试.c

// Test.c

// Get prototype for ExitProcess
#include <windows.h>

int main() 
{
    volatile int var1 = 1;
    volatile int var2 = 2;
    volatile int var3 = 3;
    volatile int var4 = 4;
    volatile int var5 = 5;

    // Since this is a WinAPI call it needs shadow space allocated
    ExitProcess(var1+var2+var3+var4+var5);

    // We won't get this far
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

如果您使用最大速度/O2优化CL Test.c /Fa /O2来编译它,您可能会看到类似以下内容的内容:

var1$ = 32
var5$ = 64
var4$ = 72
var3$ = 80
var2$ = 88

main    PROC
    sub rsp, 56                 ; 00000038H
    mov DWORD PTR var1$[rsp], 1
    mov DWORD PTR var2$[rsp], 2
    mov DWORD PTR var3$[rsp], 3
    mov DWORD PTR var4$[rsp], 4
    mov DWORD PTR var5$[rsp], 5

    mov edx, DWORD PTR var5$[rsp]
    mov eax, DWORD PTR var4$[rsp]
    add edx, eax
    mov ecx, DWORD PTR var3$[rsp]
    add ecx, edx
    mov edx, DWORD PTR var2$[rsp]
    add edx, ecx
    mov ecx, DWORD PTR var1$[rsp]
    add ecx, edx

    call    QWORD PTR __imp_ExitProcess
    int 3
main    ENDP
Run Code Online (Sandbox Code Playgroud)

var1与RSP的偏移量为 32,因为影子空间是从RSP开始调用的前 32 个字节ExitProcess。其他变量var2var3var4都从偏移量 >= 64 开始。编译器对RSPvar5生成了 56 的调整。返回地址位于RSP+56处,影子空间位于RSP+64RSP+96 处,因此to被放置在为 分配的影子空间中。mainvar2var5main