Search code examples
c++heap-memoryallocationstack-memory

Returning instances allocated in the heap vs in the stack


It's been many, many years since the last time I did something in C++. These days, someone asked me for some help on a school project in C++, and I was intrigued by a "feature" of the language I've seen, which worked fine but I expected it not to work.

As I remember, I can have instances of classes created either on the heap or on the stack:

int main() {
  MyClass *inHeap = new MyClass();
  MyClass inStack = MyClass();
}

As far as I can tell, the first variable, inHeap, will make the compiler reserve some stack in the main stack frame enough to hold a pointer (4 bytes? 8 bytes? something like that), which points to the actual instance of the object which lives in the heap.

Also, the second variable, inStack, will make the compiler reserve stack enough to hold the complete instance of MyClass right there in the main stack frame.

Now, on to my question. Suppose I have a function that is supposed to return an instance of MyClass. At first, I thought it could only return instances in the heap:

MyClass *createInHeap() {
  return new MyClass();
}
int main() {
  MyClass* inHeap = createInHeap();
}

But what I've seen is the following:

MyClass createInStack() {
  MyClass c = MyClass();
  return c;
}
int main() {
  MyClass inStack = createInStack();
}

What exactly is going on here?

  • Is the memory for the instance of MyClass reserved in the stack frame of createInStack? If this is the case, will this code force the instance to be copied to the stack frame of main when the function createInStack returns? How is this copy performed, ie, is it just automatically calling the copy constructor for me in the main function?

  • Another possibility I thought about was that the compiler would be smart enough to already reserve the memory for the MyClass instance in the main stack frame. Does this exist? Is this some kind of optimization to avoid making a possibly expensive copy?

As a final question, when I have an instance created in the stack, when exactly is its destructor called? When the scope in which it was created finishes?


Solution

  • If you are doing something like this:

    MyClass createInStack() 
    {
      MyClass return_value;
      return return_value;
    }
    
    int main() 
    {
      MyClass inStack = createInStack();
    }
    

    Then yes, c is logically created on the stack in createInStack() and then a copy of it is logically returned and then copied into inStack in main.

    There is a common misconception that returning by value is inefficient because of all this logical copying. However, due to named return value optimization, this isn't what actually happens in practice. The construction step in the calling function is just deferred to the called function. It would be something like this (pseudocode):

    void createInStack(MyClass &return_value)
    {
      return_value.MyClass(); // construct return_value (not actually valid syntax)
    }
    
    int main()
    {
      MyClass inStack; // except don't call the constructor
      createInStack(inStack);
    }
    

    So as you can see, no actual copying happens.

    In addition, the compiler may do other optimizations. It may even decide that inStack is never used and just not create it, but you can be pretty sure that at least the named return value optimization will avoid a lot of copying.