Visual Studio C++ 的函数序言

Function Prologue by Visual Studio C++

我在VS2019社区版写了一个很简单的C++函数,对应的反汇编有问题

函数:

void manip(char* a, char* b, long* mc) {
    long lena = 0;
    long lenb = 0;
    long lmc;
    long i, j;
    for (; a[lena] != NULL; lena++);
    for (; b[lenb] != NULL; lenb++);
    lmc = lena + lenb + *mc;
    for (i=0; i < lena; i++) a[lena] = a[lena] + lmc;
    for (j=0; j < lenb; j++) b[lenb] = b[lenb] + lmc;
}

反汇编(节选):

void manip(char* a, char* b, long* mc) {
00007FF720DE1910  mov         qword ptr [rsp+18h],r8  
00007FF720DE1915  mov         qword ptr [rsp+10h],rdx  
00007FF720DE191A  mov         qword ptr [rsp+8],rcx  
00007FF720DE191F  push        rbp  
00007FF720DE1920  push        rdi  
00007FF720DE1921  sub         rsp,188h  
00007FF720DE1928  lea         rbp,[rsp+20h]  
00007FF720DE192D  mov         rdi,rsp  
00007FF720DE1930  mov         ecx,62h  
00007FF720DE1935  mov         eax,0CCCCCCCCh  
00007FF720DE193A  rep stos    dword ptr [rdi]  

在前三行中,我们将参数放入堆栈中的帧指针之前。之后帧 rbp 指针被压入。困扰我的是以下三行:

00007FF720DE1921  sub         rsp,188h  
00007FF720DE1928  lea         rbp,[rsp+20h]  
00007FF720DE192D  mov         rdi,rsp

在上面的三行中,据我了解,第一行在堆栈上保留了 space。

问题:

  1. I do not understand why this huge space (188h) is reserved while we need just enough to save 5 longs, which are no more than 5*4=20 (16h) bytes.
  2. Second line is calculation of new frame pointer, but I don't understand how did we get 20h(32).
  3. I also don't get the significance of 3rd line.

这是MSVC调试模式;它保留额外的 space 并用 0xCC 毒化它(在罐头中使用 rep stosd 又名 memset,因此 mov rdi,rsp 设置目的地)以帮助检测越界访问错误。 (即使 none 的当地人的地址被占用并且 none 是数组...)

额外的堆栈数量惊人space;我不知道 MSVC 是如何选择保留多少的。在 Release 模式下(-O2 优化 https://godbolt.org/z/GY7xTYWKq),它当然根本不会触及堆栈。

调试模式必须添加一些不是 MSVC 命令行默认设置的额外选项,因为我无法在 https://godbolt.org/z/nGo9516b7 上使用 MSVC2015 19.10 或 19.28 重现此代码生成。在将传入的寄存器参数溢出到影子 space 之后,我只是得到 sub rsp, 40,甚至没有将 RBP 设置为帧指针。 (我猜是因为它是叶函数。)

lea rbp,[rsp+20h] 似乎将 RBP 设置为可能的帧指针,但它并不指向 return 地址正下方的已保存 RBP。通过一些代码显示它是如何使用它的,也许我们可以弄清楚。 (查看它的 asm 输出,而不是反汇编,因此您可以获得局部变量的符号名称)。


顺便说一句,如果您想真正了解循环逻辑的工作原理,那么针对循环优化的 asm 更易读

您的代码充满了从堆栈重新加载指针和 movsxd 符号扩展,因为您使用有符号整数作为不是指针宽度的数组索引,并且编译器没有优化到指针增量或至少到 64 位整数。 (Signed-overflow being UB allows this optimization.)

的大部分内容都适用于编写优化后有趣的函数。