Heap allocation of local variables

Contents

In a few occasions I have seen functions that allocate certain variables on the heap instead of using variables with automatic storage:

void foo() {
  SomeStuff* somevar = new SomeStuff;

  // do something for a few lines...

  delete somevar;
}

This code has several issues. The first one that should jump into everyone’s face is the manual memory management.

Step 1: Use smart pointers

Smart pointers have been in the game for many years now, and as soon as I see a `delete` the first reflex is to check the object lifetime, possible execution paths etc. and look if I can just simply replace the plain pointer by a smart pointer. Execution paths are important to check, because the code might give the ownership of the pointer to some other object and not always reach the delete. In this case, let’s assume we have checked all the possibilities, and maybe aside from some memory leaks when exceptions are thrown, the object always gets destroyed. So we replace the pointer by a smart pointer of our choosing. I recommend `unique_ptr`, together with C++14’s `make_unique`, or in absence of a C++11 compiler use `boost::scoped_ptr`.

void foo() {
  auto somevar = std::make_unique<SomeStuff>();

  // do something for a few lines, but no delete...
}

What have we gained? First, we got a bit of exception safety, because there are no more memory leaks when the code throws exceptions. The smart pointer will always delete the object, regardless of how the function is exited. Then, we got rid of the mental burden to track object lifetimes and ownership. The smart pointer has a clear semantic of those, and it is not easy to mess them up after we got rid of the manual memory management.

Step 2: Move to stack allocation

Having switched to smart pointers and their clear ownership and lifetime semantics, we get to see a fact that was not as easy to see before as it is now: The variable is a plain local variable. It has exactly one owner, in this case the surrounding scope, i.e. the function `foo()`, and it has to be destroyed when the owner gets destroyed. We now the exact type of the variable, i.e. no polymorphy is involved. So there is no need to have a pointer. Instead, we can make it a local variable with automatic storage:

void foo() {
  SomeStuff somevar;

  // do something for a few lines...
}

We have again gained some simplifications: Stack allocations are cheaper than heap allocations, so we got some performance for free. And we got rid of another mental burden: Pointers can be null pointers. In almost all of the cases when I did this, I could throw out some null pointer checks in the same function that had been useless right from the start. In addition, some of the functions that get passed the variable can be modified to take it by reference instead of by pointer, if they get called only in this function or if the other functions have been refactored similar to this one. The refactoring of the callees could have happened before as well, but changing the pointers into local variables made it much easier since we don’t have to care about the possibility of null pointers any more.

Why heap allocation of local variables?

“But wait” you say, “`Somestuff somevar;` could have been done from the start, there never was a need for pointers or smart pointers in this function.” You are right. I never completely understood why someone would allocate local variables on the heap in a normal desktop application. Sure, there have been times where the stack was severely limited, and on some embedded environments that may still be the case. But in general there is always enough room on the stack. I had conversations with several colleagues on the topic, and some told me that it might be a habit from the ancient days when memory was sparse. If that is true, it is a habit that has to change. Default to stack variables, period.

Big objects

One colleague argued that very big objects should be an exception and local variables of those should be allocated on the heap. He was wrong. It is unreasonable for the user of a class to need to know whether objects of that class are too big to be allocated on the stack, and how big “too big” actually is. Normal “big” objects with many members might have a size of a few hundred bytes, but that is not generally too big. One still needs thousands or millions of them to blow a normal stack, and that won’t usually be the case under normal conditions.

It can however be the case if you allocate large arrays of big objects or call deeply nested recursions. Both cases are somewhat special and the programmer who writes them should keep in mind that they might blow the stack. However, I still would stick to stack allocation until a profiler measures an alarming rate of stack allocations or an edge case unit test actually crashes due to a stack overflow.

Really huge objects

But what if there is a class with really huge objects? There probably is not. It is hard to write a class that has thousands of members or to compose it from a few really big members that are themselves composed to form really big objects. And if there is such a huge class, than the programmer of that class is at fault, because he created an edge case that made our life so much harder than it needs to be, either because he did not split it up when it was appropriate or because he did not care for the users of his class. You should default to stack variables, and the authors of the few very large classes should enable you to do so and do the heap allocation themselves, nicely wrapped inside their own class.

Bottom line: Use stack allocation for local variables by default. Switch to heap allocation only if you are sure you have a problem with the stack and when your profiler told you where that problem is.

 

Previous Post
Next Post

7 Comments


  1. Hi Arne,

    Thanks for the clear explanation on this post.

    can you tell me any use cases where we have to use heap allocation for local variables?

    Reply


  2. Looks like the template argument to std::make_unique got lost. The code should read “std::make_unique” less-than “SomeStuff” greater-than.

    Reply

  3. Another case in when you use factory functions and polymorphism in general, e.g.:

    void foo(const configuration& configuration) {
    auto shape = factory::createShape(configuration);
    shape->draw();
    }

    Reply

    1. Thanks for the comment! I had mentioned polymorphism in an earlier revision but kicked it out. Will add it again.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *