为什么 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
var1
与 RSP 的偏移量为 32,因为影子 space 是从 RSP[= 开始的前 32 个字节65=] 调用 ExitProcess
。其他变量 var2
、var3
、var4
和 var5
都从偏移量 >= 64 开始。编译器生成了 56 的调整到 RSP。 return 地址在 RSP+56 并且 main
在 RSP+64 有影子 space 到RSP+96,因此 var2
到 var5
被放置在分配给 main
.
的影子 space 中
我试图查看 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
var1
与 RSP 的偏移量为 32,因为影子 space 是从 RSP[= 开始的前 32 个字节65=] 调用 ExitProcess
。其他变量 var2
、var3
、var4
和 var5
都从偏移量 >= 64 开始。编译器生成了 56 的调整到 RSP。 return 地址在 RSP+56 并且 main
在 RSP+64 有影子 space 到RSP+96,因此 var2
到 var5
被放置在分配给 main
.