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

Why isn't MSVC allocating a 32 byte shadow space in the generated assembly code?

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

// Test.c
int main() {int var1 = 1;}

以上程序生成以下 .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

它只分配了24个字节。当我声明 4 个变量时,它分配相同的数量,并且由于每个变量是 4 个字节,它必须意味着 24 个字节中的 16 个字节用于声明的变量,留下 8 个字节用于阴影 space.
只有在声明 5 个变量时,它才会分配 40 个字节的影子 space。为什么只分配8字节的影子space?
我使用命令 CL Test.c /Fa

编译了程序

这里的RSP减去24和shadowspace没有任何关系。 Shadow space 仅在 main 调用其他 64 位 Microsoft ABI 兼容函数时适用。您的 main 函数是叶函数(它不调用任何其他函数),因此不需要为影子 space 分配额外的 space。如果您修改 main 以调用 C/C++ 库或 WinAPI 中的内容,您会发现额外的 space 会被添加到影子中space 进行这样的调用。

鉴于您的函数正在处理 32 位值(并且没有数组)并且不调用任何其他内容,我认为它没有理由需要对齐到 16 字节边界或添加额外的填充,但是这似乎是在做什么。堆栈上的 return 地址使堆栈错位 8。减去 24 使其在 16 字节边界上对齐,并在变量后填充。

这可能是由于在编译时代码生成效率低下(如 /O1/O2 等)或编译器将局部变量 space 填充到首选数量.理论上,在这种情况下它不必分配任何堆栈 space。它可以重用 return 地址上方的影子 space,而 C/C++[=65= 会为 main 函数分配地址] 启动代码。

注意:通过优化,除非您将 var1 设为 volatile 变量,否则代码将被完全删除。编译器应该认识到你写的代码除了 return 返回给调用者之外没有做任何事情。


下面的例子调用ExitProcess来显示添加阴影space;重用 C/C++ 为局部变量调用 main 的启动代码分配的影子 space;并为无法放入阴影 space 的变量使用一些堆栈 space。由于名为 ExitProcess 的 WinAPI 需要在调用它之前分配 32 字节的影子 space。如果你从这个例子中删除它,编译器将不会为它分配额外的 space。

test.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;
}

如果你用 /O2 optimizations 编译它以获得最大速度,使用 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

var1RSP 的偏移量为 32,因为影子 space 是从 RSP[= 开始的前 32 个字节65=] 调用 ExitProcess。其他变量 var2var3var4var5 都从偏移量 >= 64 开始。编译器生成了 56 的调整到 RSP。 return 地址在 RSP+56 并且 mainRSP+64 有影子 space 到RSP+96,因此 var2var5 被放置在分配给 main.

的影子 space 中