C++ memory leak without virtual destructor

By , last updated November 29, 2019

In this post I will talk about detecting and debugging memory leaks in a C++11 program, Visual Studio’s built in heap profiler and Intel Parallel Studio 2019. And also the importance of virtual destructors when implementing derived subclasses.

TLDR: Base classes needs their virtual destructors or the behavior is undefined.

TLDR2: I forgot about the C++ rule of three/five when implementing an inheritance hierarchy.

Background

I’ve been doing some rather large refactoring of some legacy code. It can best be described as moving pieces from a large file into smaller modules, which actually does one thing. The monstrocity have evolved several years and the refactoring was very overdue.

Each module receives input data, does some computations and sends the result to the next module. It’s designed around a very simple inheritance model, implemented with modern C++ (C++17).

There is a non-templated base class, a templated base class and each module inherits from the templated base class. The work extracting the bits and pieces from the monolithic file into modules, was very mundane. But great care had been taken to avoid memory leaks, by using std::unique_ptr where raw pointers were previously used. Some places required std::shared_ptr, but those were few and far apart.

When most of the code was ported and the whole rewrite actually did build and run, I could not avoid notice the memory increased steadily for every item it processed.

Oh no.

Detecting C++ memory leaks

From the tools available, Visual Studio’s built in heap profiler usually can catch the lowest hanging fruits easily. The advantage the heap profiler / diagnostic tools have over other tools, it that it’s lightweight and doesn’t reduce debug performance too much.

In hindsight, the heap profiler did point me into the right direction. But at the time it didn’t make any sense. The error always stemmed from std::vector‘s assign routine. And it always was the same module.

To confuse the matters even more, the error did only occur during Release builds. It was not possible to reproduce in Debug builds.

Bringing in the big guns (Intel Parallel Studio 2019), came with the same type of location, in std::vector assign.

It didn’t make any sense. An error in the standard vector was highly unlikely.

It seemed like, the destructor was never called.

Stepping to the leaks

Next step was actually to step into the module, which had the leak. Nothing in particular. The vector got assigned it’s data. But what was most interesting, was stepping into the destructor, or what would be the destructor of class members containing said data. This was the definitive smoking gun. No destructors called.

As per C++11 standard ยง5.3.5/3:

If the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined.

In other words, in some cases of inheritance, if you don’t have destructors the behavior is undefined.

It means that anything can happen. It might crash or leak memory or it may appear to work as expected. You never know.

The fix

virtual ~ModuleBase() = default