Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ stack overflow exception on main function execution

I have the code listed below and it reports a stack overflow when I run it. I use pass by value to showTest(). What I expect is that it will make a copy of the Test struct to the stack (pushed to the stack), then at the end of the function call the Test struct will be released (popped off of the stack). So I make a call for three times. It is supposed to push onto the stack and pop off at the end of each function call.

I don't see any stack issue if it pushes on and pops off of the stack each time the function is called. However, when I run this code, it reports a stack overflow exception on the first line of main. (I use Visual Studio 2017.)

If I remove one of the showTest() function calls then I can get it work.

Any feedback would be highly appreciated.

#include <iostream>

struct Test
{
  static int Userid;
  int data[100000] = { };

  Test()
  {
    ++Userid;
  };
};

int Test::Userid = 0;

void showTest(Test i_myint)
{
  std::cout << "test" << std::endl;
}

int main()
{
  Test *pint = new Test();
  showTest(*pint);
  Test *pint2 = new Test();
  showTest(*pint2);
  Test *pint3 = new Test();
  showTest(*pint3);
  return 0;
}
like image 585
Mohamad Khaizul Zakaria Avatar asked Jul 09 '18 05:07

Mohamad Khaizul Zakaria


1 Answers

What evidently happens here is deferred stack popping.

In C and C++ the common calling convention is that the caller pops arguments from the stack. As a common optimisation, many compilers don't pop arguments after each call, but do that after a number of calls and pop all the accumulated arguments together. This saves a few instructions, at the expense of a larger stack which may overflow.

In MSVC, when optimisations are disabled, the compiler allocates and checks the stack beforehand for all the calls it will need in a given function. This is why the program crashes even before it prints anything.

See corresponding assembly

Some of the first instructions in main are

    mov      eax, 1200120       ; 00124ff8H
    call     __chkstk
    sub      rsp, rax

This number is exactly what is needed to fit three instances of the object on the stack.

When optimisations are enabled, the compiler is smart enough to reuse the stack, so there's no crash.

Optimised assembly

    mov      eax, 400032          ; 00061aa0H
    call     __chkstk
    sub      rsp, rax

Enough for a single instance.

like image 130
n. 1.8e9-where's-my-share m. Avatar answered Sep 20 '22 00:09

n. 1.8e9-where's-my-share m.