Avenue Code Snippets

How to Prevent Memory Leaks

Written by Rodrigo Novaes | 1/13/21 5:00 PM

Resource management is one of the most challenging tasks involved in software development. Even with out-of-the-box solutions like garbage collection (GC), there are still several ways to cause defects through poor resource allocation/deallocation.

In memory handling, virtual machines and operating systems have evolved to aid developers in writing safer programs, avoiding leaks and data corruption. But what other options do we have when we can't hire help from an assistant? This article presents a mental experiment that gives the reader a different approach to memory management without using out-of-the-box features.

Keywords: C++. Smart Pointers. RAII. Memory Management. Resource Acquisition.

Introduction

I want to propose a mental experiment: Imagine yourself as a parent in a house. A few years ago, you moved to the OS country, met someone with whom you fell in love, and then decided to build a life together. In addition to managing your house chores, you are a very renowned software engineer whose love for his/her work has reached your children: you have two beautiful kids named Stack and Heap.

Stack and Heap are similar in several ways. For instance, they both enjoy playing with the same toys, have the same taste in TV shows and comic books, and are avid fans of One Piece. However, they also have some important differences: while Heap is very adaptive and eager to learn new things, Stack can be quite inflexible and short-tempered. On the other hand, Stack is very obedient and organized, while Heap can't even get her toys without being ordered to. This can cause a lot of trouble sometimes, like last week when you tripped on a Spiderman action figure and almost fell to the ground.

Now, a house requires a lot of effort to work properly. You and your partner have divided all tasks efficiently, but regardless of this, managing Stack and Heap causes an overload in your daily activities. It would be easier if Heap was more self-managed, just like Stack, who always knows where his toys are. In fact, his organization skills are so impressive that he also knows where Heap keeps her toys. This may have something to do with the fact that Heap is short and depends on Stack to grab toys in the wardrobe for her.

Anyway, the problem starts when Stack loses track of a toy that Heap is playing with. Now he isn't able to get the toys together and has to depend on his parents to organize the house. The problem intensifies when you, the parent, also forget to order Heap to get her toys! Now you may inadvertently trip on any number of action figures in the future.

Over the years, you've made some attempts to help Heap become more organized. You tried to hire an assistant named GC, who aided you in house chores and kept an eye on Heap. However, GC has a very tight schedule: the house has several tasks to be fulfilled, like cooking, washing, cleaning, paying bills, and more. Imagine the time loss GC will have when interrupting all these activities in order to collect Heap's stuff around the house. Moreover, it's quite complicated to determine when GC will have the opportunity to interrupt all the tasks and clean up after Heap. It can happen now or later; you never know.

Well, certainly many houses have their assistants, and they have grown smarter and more efficient over time: they can do more than one task at once and still collect the unorganized children's toys, but it doesn't diminish the cost of doing it. Trust me: hiring GC is still expensive, even though it's practical.

Time passes, and your household problems have taken their toll. You often trip on things that Heap leaves behind, like that one time when you stumbled and fell on your TV, smashing it to pieces. You concluded that having Heap play freely is dangerous, and you decided to visit a child therapist.

Dr. Boost Stroustrup, PhD., suggested you take advantage of Stack's organization skills. Since Heap can't reach her toys in the wardrobe, meaning that someone always has to pick them up for her, you could ask Stack to keep track of Heap's steps while they play. Therefore, whenever Heap leaves a toy behind, Stack will always make sure to bring it back to the wardrobe, freeing the unwanted items from the floor of your house.

You decide to give Dr. Stroustrup's suggestions a try, and they work wonders. Stack and Heap love playing together, and since Stack knows each one of Heap's steps, your house is as clean as possible! Also, your kids have a bond that's way stronger than before, since Stack is a lot more  involved in Heap's life.

You and your partner are now looking forward to seeing how your children will grow. Will your family move to a better place, enjoying the fact that the interactions between Stack and Heap have improved your conditions? Will you have a well-deserved holiday trip to Nlogonia now that your bank account is fatter without GC's costs and expenses? There are plenty of options, but of course, they depend on your opportunities.

An Analogy for Business

Analogies are an art and they serve a purpose: helping us to understand a concept. In this analogy, the house was a computer process, the children were the Stack and Heap memory areas, and the parent was you, the programmer.

Other concepts were explored as relevant pieces of the story, such as Garbage Collector (GC), a program responsible for collecting heap-allocated areas, and Resource Acquisition is Initialization (RAII), a concept that assigns the ownership of a heap-allocated object to a stack-allocated object [1][2].

In this article, we will explore the sibling approach: Stack playing together with Heap to achieve resource-safety [2]. This is an instance of the RAII idiom [2], which has been standardized in C++'s libraries since C++11 and implemented under the well-known name of smart pointers [3].

What Will I Learn?

After reading this article, it's expected that the reader will:

  1. Learn more about memory management and a different technique to handle it;
  2. Discover that the author is a passionate fan of One Piece.

The author also recommends that you read about GC, a solution embedded into Java Virtual Machines (JVMs) for dynamic memory management [4]. There are clear and well-written articles referenced in this blog that will help you [5][6].

RAII and Smart Pointers

C++ has been in the market for a while. Unlike some languages, like Java, every resource allocated in a C/C++ program needs to be returned to the operating system (OS) manually [4]. This was considered a tedious operation, since every unreleased resource is considered a leak and leaks can lead to critical problems [1]. Some examples of these are [2]:

  1. Resource leaks: the resource will spend memory space even though there's no reference to it (Heap's forgotten toys occupy space in the house);
  2. Access through invalid pointer: a resource deallocated from memory is still accessible through a reference, but you can read nothing besides trash from it (let's assume that Heap has a spider toy that you just collected; if you see another spider and collect it again, there shouldn't be a major problem unless the spider is not a toy!);
  3. Memory corruption: a resource is deallocated from memory, but you try to write information to it anyway (you try to clean a space in the house but trip on the Spiderman action figure along the way).

However, C/C++ has a powerful feature that allows one big enhancement in memory management: static object allocation [1][4]. Unlike Java, whose JVM allocates every object in the Heap, hence using implicit dynamic object allocation, C++ allows the programmer to create objects in the Stack. Also, Stroustrup's language has operators dedicated to an object's allocation and deallocation. The first operator is well-known across object-oriented (OO) programming languages, often referred to as constructor. The second operator is a destructor and is called whenever an object is freed in the Heap memory area. The code snippet in Listing 1 shows an example using C++.

#include <iostream>
class Integer {
private:
int *m_value;
public:
/**
* Instance of `Integer` will have a reference to a value in `t_value`.
* We can call this "resource acquisition".
*/
Integer(int t_value) {
m_value = new int(t_value);
}
/**
* Instances of `Integer` will be deleted. When deleted, `Integer` will
* free whatever is stored in `m_value`, releasing the memory from its resource.
* We can call this "resource release".
*/
~Integer() {
delete m_value;
}
/**
* Returns the value stored as a resource inside `Integer`.
*/
int getValue() const {
return *m_value;
}
};
int main(void) {
Integer a(100); // Resource acquisition: the constructor is called and the
// statically instantiated in the stack.
std::cout << a.getValue() << std::endl; // Do something with `a` - in this case,
// printing its resource.
return 0;
}
// After termination, all objects in stack are automatically released, meaning that `delete a` is implicitly invoked. Hence, not only `a` but `a.m_value` is also released.

Listing 1 – An implementation of an integer wrapper class. The resource acquisition is done whenever an integer object is created in Stack (refer to the main function). When main's scope is finished after the return statement, all Stack objects are collected automatically. Hence the integer's destructor is called, deallocating its Heap-allocated member.

As stated in Listing 1, the key concept for understanding RAII is resource ownership. If a class C has one or more members whose type is a reference (a.k.a. pointers), a static instance of C will be automatically collected from the Stack by calling C's destructor, meaning that all of its dynamic-allocated members can be freed as long as their respective destructors are properly invoked [1][2].

In the analogy at the beginning of this article, Stack and Heap's toys are resources, or in other words, objects. When you applied the suggestion of asking for Stack's help, you actually made Stack own Heap's resources. Since Stack owns his and Heap's resources, he will always know when and how he should collect his and his sister's toys.

However, as a programmer, you still need to tell a class C how its objects should be destructed – especially those Heap-allocated member objects – and that does not provide the same comfort as hiring GC. That's why we should take a look at the following section.

Smart Pointers

If you have come to terms with resource ownership, then you're more than ready to learn about smart pointers. In Listing 1, you saw what would be a small reference to a Java's integer wrapper class, which is merely a container for an integer value stored in the heap. In C++, heap-allocated objects need to be explicitly deallocated by the program, meaning that the responsibility lies on you: the programmer.

However, we can make use of RAII to delegate this responsibility to Stack-allocated objects instead. Prior to C++11, the Boost C++ libraries distributed headers containing classes we call smart pointers, which are nothing more than the same integer wrapper class from Listing 1 enhanced by the template design pattern [7][8].

From C++11 onward, the C++ standard library implemented its own version of smart pointers, which has been maintained and has evolved since then as part of the <memory> header [3]. Please consider Listing 2 as an enhancement of Listing 1, where we now use a smart pointer as the integer resource's owner.

#include <iostream>
#include <memory> // use smart pointer
class Integer {
private:
std::unique_ptr<int> m_value;
public:
/**
* Resource acquisition is made by `m_value`, a smart pointer. The acquisition
* is as simple as instantiating a new object, but using a static constructor
* rather than the `new` operator.
*/
Integer(int t_value) {
m_value = std::unique_ptr<int>(new int (t_value));
}
/**
* No action is needed in `Integer`'s destructor, since its resources are all
* owned by other stack-allocated objects - in this case, a `unique_ptr`.
*/
~Integer() {}
/**
* Returns the value owned by `m_value`.
*/
int getValue() const {
return *m_value.get();
}
};
int main(void) {
Integer a(100); // Resource acquisition is done inside the constructor, by the
// smart pointer in `Integer`
std::cout << a.getValue() << std::endl;
return 0;
}
// After termination, all objects are released from stack and heap without
// a single call to the `delete` operator. Pretty neat, huh?

Listing 2 – An implementation of an integer wrapper class. The resource acquisition is done by the smart pointer unique_ptr inside integer. Since all objects are allocated in Stack, any dynamic allocation will be automatically released by the implicit call to the objects' destructors. In other words, integer's destructor will be implicitly called, as well as unique_ptr's. Hence, the Heap-allocated value will be freed after main's termination.

Some might say that the perfect design of an owner is of one that does not know anything about the resource it owns [2]. In this context, it might seem that our analogy fails: Stack is Heap's brother, so it's more than likely that he'll know about his sister's toys.

However, imagine that Stack and Heap will grow into two marvelous teenagers and, at some point, Heap will get into the habit of logging her daily events into a diary. Stack's help will still be necessary to grab Heap's diary at the wardrobe (resource acquisition), but he's so obedient and polite that he'll never look into the contents. Stack respects Heap's privacy as much as smart pointers respect their resources' encapsulation.

Conclusion

From what was observed, it's reasonable to affirm that garbage collection is not required to achieve high-level memory management. RAII is a feature that avoids all of the main leaks stated at the beginning of this article, with the benefit of adding low overload to objects' allocation and deallocation [2].

Moreover, having garbage collection doesn't imply full resource deallocation, since resources can be collected from several sources besides memory, like streams and sockets. Even Java, with JVM's out-of-the-box memory management, requires a closer look at how programmers handle their resources [9]. With RAII, however, it's possible to delegate any kind of deallocation to stack-allocated objects, meaning that destructors can close streams, sockets, and perhaps even Heap's wardrobe.

In this article, we explored the concepts involving RAII using an analogy with household chores. All implementations were done using C++11, whose smart pointers implementations were retrieved directly from the memory header. In addition, the smart pointer implementation used was a unique_ptr, whose features are [3]: 

  1. Resource acquisition;
  2. Resource release;
  3. Assurance of a single and unique reference to the resource.

There are other types of smart pointers, though. They are shared_ptr, weak_ptr and auto_ptr (which has been deprecated since C++14 and removed as of C++17). Each one has its own features and appropriate use cases; I recommend this as further reading [3].

I hope that this article offers helpful reflections for you. It might be said that there are several ways in which programming is similar to parenthood; you have to educate your program to be humble, smart, effective, and efficient. Even though it fails sometimes, it's important to never give up on it and to never stop doing what you can so that it'll turn into something great in the future. The curious thing is that I don't have any children. If I did, I'd make sure to introduce them to One Piece.

 

References

[1] MICROSOFT. Object lifetime and resource management (RAII). Microsoft Documentations, Nov. 19th 2019. Available on: <https://docs.microsoft.com/en-us/cpp/cpp/object-lifetime-and-resource-management-modern-cpp?view=msvc-160>. Last accessed: Nov. 26th 2020.

[2] B. Stroustrup, H. Sutter, G. dos Reis. A brief introduction to C++’s model for type and resource-safety. Dec. 2015. Available on: <https://www.stroustrup.com/resource-model.pdf>. Last accessed: Nov. 26th 2020. 

[3] CPP Reference. Standard library header <memory>. Apr. 1st 2020. Available on: <https://en.cppreference.com/w/cpp/header/memory>. Last accessed: Nov. 26th 2020.

[4] Oracle. Java Garbage Collection Basics. Available on: <https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html>. Last accessed: Nov 26th 2020.

[5] R. Silva. How Java Garbage Collection Works and Why You Should Care - Part I. Aug. 12th 2020. Available on: <https://blog.avenuecode.com/how-java-garbage-collection-works-and-why-you-should-care-part-1>. Last accessed: Nov 26th 2020.

[6] R. Silva. How Java Garbage Collection Works and Why You Should Care - Part II. Available on: <https://blog.avenuecode.com/how-java-garbage-collection-works-and-why-you-should-care-part-2>. Last accessed: Nov 26th 2020.

[7] Boost C++ Libraries. Smart Pointers. 2002. Available on: <https://www.boost.org/doc/libs/1_63_0/libs/smart_ptr/smart_ptr.htm>. Last accessed: Nov 27th 2020.

[8] C. Caballero. Design Patterns: Template Method. Sep. 24th 2019. Available on: <https://medium.com/better-programming/design-patterns-template-method-5400dde7bb72>. Last accessed: Nov 27th 2020.

[9] Baeldung. Understanding Memory Leaks in Java. Feb. 12th 2020. Available on: <https://www.baeldung.com/java-memory-leaks>. Last accessed: Nov 27th 2020.