Function Prologue by Visual Studio C++

Viewed 244

I wrote a very simple C++ function in VS2019 Community Edition and I have a question in corresponding disassembly.

Function:

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

Disassembly (Excerpt):

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]  

In the first three lines we are placing the arguments in stack before the frame pointer. The frame rbp pointer is pushed after that. What troubles me are following three lines :

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

Of the three lines above, the first one as I understand reserves the space on the stack.

Questions:

  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.
1 Answers

This is MSVC debug mode; it reserves extra space and poisons it with 0xCC (with rep stosd aka memset in a can, hence the mov rdi,rsp to set the destination) to help detect out-of-bounds access errors. (Even though none of the locals have their address taken and none are arrays...)

It's a surprising amount of extra stack space; I don't know how MSVC chooses how much to reserve. In Release mode (-O2 optimization https://godbolt.org/z/GY7xTYWKq), it of course doesn't touch the stack at all.

Debug mode must be adding some extra options that aren't default for MSVC's command line, because I can't reproduce this code-gen on https://godbolt.org/z/nGo9516b7 with MSVC2015 19.10 or 19.28. I just get sub rsp, 40 after spilling the incoming register args to the shadow space, and not even setting up RBP as a frame pointer. (I guess because it's a leaf function.)

lea rbp,[rsp+20h] seems to be setting up RBP as a presumably frame pointer, but it's not point at the saved RBP right below the return address. With some code showing how it uses it, maybe we could figure that out. (Look at its asm output, not disassembly, so you can get symbolic names for local vars).


And BTW, the optimized asm for the loops is much more readable if you want to actually see how the loop logic works.

Your code is full of reloads of the pointers from the stack, and movsxd sign-extension because you used signed integers as array indexes that weren't pointer-width, and the compiler didn't optimize into pointer-increments or at least into 64-bit integers. (Signed-overflow being UB allows this optimization.)

Much of How to remove "noise" from GCC/clang assembly output? applies in terms of writing functions that are interesting to look at when optimized.

Related