FACULTY OF INFORMATICS
Memory management in C++
Bachelor’s Thesis
PAVEL REŽNÝ
Brno, Spring 2022
FACULTY OF INFORMATICS
Memory management in C++
Bachelor’s Thesis
PAVEL REŽNÝ
Advisor: RNDr. Mgr. Jaroslav Bayer
Department of Computer Science
Brno, Spring 2022
Declaration
Hereby I declare that this paper is my original authorial work, which
I have worked out on my own. All sources, references, and literature
used or excerpted during elaboration of this work are properly cited
and listed in complete reference to the due source.
Pavel Režný
Advisor: RNDr. Mgr. Jaroslav Bayer
iii
Acknowledgements
I would like to thank my supervisor RNDr. Mgr. Jaroslav Bayer, for
his guidance throughout this project.
iv
Abstract
Managing a program’s memory is an integral part of programming.
This thesis explores manual and automatic memory management
techniques, focusing on C++. It describes C++ specifics, such as the
RAII principle, smart pointers, built-in support of garbage collection,
and the Boehm garbage collector. This thesis aims to decide if one
of the memory management techniques is the best. That is done by
analyzing three programs, where each program is written three times,
using a different technique every time. The analysis mainly focuses
on memory usage, runtime, and user-friendliness. It is the conclusion
that there is no clear best approach to memory management because
each of them has its advantages and disadvantages, as shown in the
examples.
Keywords
memory management, garbage collection, C++, smart pointers, Boehm
GC, RAII
v
Contents
Introduction 1
1 Memory management 2
1.1 Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Manual memory management . . . . . . . . . . . . . . . 3
1.4 Automatic memory management . . . . . . . . . . . . . 3
1.4.1 Garbage Collection . . . . . . . . . . . . . . . . . 4
2 C++ specifics 7
2.1 Malloc, free, new and delete . . . . . . . . . . . . . . . . 7
2.2 RAII . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.3 Smart pointers . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3.1 unique_ptr . . . . . . . . . . . . . . . . . . . . . . 11
2.3.2 shared_ptr . . . . . . . . . . . . . . . . . . . . . . 12
2.3.3 weak_ptr . . . . . . . . . . . . . . . . . . . . . . . 13
2.3.4 auto_ptr . . . . . . . . . . . . . . . . . . . . . . . 14
2.4 Garbage collectors . . . . . . . . . . . . . . . . . . . . . . 15
2.4.1 C++ GC support . . . . . . . . . . . . . . . . . . 15
2.4.2 Boehm GC . . . . . . . . . . . . . . . . . . . . . . 18
3 Implementation and analysis 22
3.1 Hardware and software used . . . . . . . . . . . . . . . 22
3.2 Programs overview and examples . . . . . . . . . . . . 23
3.2.1 Program #1 . . . . . . . . . . . . . . . . . . . . . 23
3.2.2 Program #2 . . . . . . . . . . . . . . . . . . . . . 25
3.2.3 Program #3 . . . . . . . . . . . . . . . . . . . . . 30
3.3 Analysis results . . . . . . . . . . . . . . . . . . . . . . . 36
3.3.1 Time . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.3.2 Memory . . . . . . . . . . . . . . . . . . . . . . . 36
3.3.3 User-friendliness . . . . . . . . . . . . . . . . . . 37
3.3.4 Overall . . . . . . . . . . . . . . . . . . . . . . . . 37
4 Conclusion 39
vi
Bibliography 40
A Attachments 44
vii
Introduction
Memory management is an essential part of programming. Every
program must manage its memory in some way, or it will eventually
run out of it. Programming languages such as C use manual memory
management, where the programmer must handle the memory on
their own. On the other hand, languages like Java or Python manage
the memory automatically. Furthermore, there are languages where
both options are available. C++ is an example of such language. It
allows the programmer to use new and delete operators to manage the
memory manually, but it also provides support for automatic memory
management by smart pointers. C++ does not have a built-in garbage
collector (GC), but there are external libraries that add it. An example
is the Boehm–Demers–Weiser garbage collector, also known as the
Boehm GC, which is being used in some well-known commercial
applications like the Mozilla project or the Mono project. [1]
This thesis explores multiple ways of managing memory and its
specifics in C++. General terms regarding memory management, such
as stack, heap, and garbage collection, are defined. In C++ specifically,
manual management using the new and delete operators and automatic
management utilizing smart pointers and the Boehm garbage collector
are showcased with code examples. All approaches are analyzed with
the support of test programs. The analysis is mainly concerned with
memory usage, runtime, and user-friendliness.
The first chapter is about what memory management is. A distinc-
tion between manual and automatic memory management is drawn,
and terms like stack, heap, and garbage collection are explained. Also
in this chapter, some of the most commonly used garbage collection
techniques are shown. The second chapter is concerned with the
specifics of memory management in C++. The usage of the new and
delete operators, smart pointers, and garbage collection is explained,
and all alternatives are accompanied by code examples. The analysis is
done in the last chapter. Three programs were written using three dif-
ferent memory management techniques. Each program has a different
focus related to memory management. These programs are analyzed
based on multiple factors in order to select the best technique, if there
is one.
1
1 Memory management
Memory management is a process that is responsible for the allocation
of memory and its recycling. To allocate memory means to assign a
section of a program’s memory to store its data. When an object previ-
ously stored in allocated memory is no longer needed, the memory
it occupies can be recycled and later used again. [2] Every program
needs to receive some amount of memory from the computer’s RAM
to be successfully executed. This memory is assigned to it by the oper-
ating system (OS) when it is run. The program needs to store itself
in the memory alongside its data structures and data values. It may
also need to store run-time systems that it requires in order to be run.
Every memory is finite, and if a program does not manage its memory,
it will eventually run out of it and stop or crash. Data can be stored
either on a stack or a heap. [3, 4]
1.1 Stack
A stack is a part of memory where data is stored based on the Last-In,
First-Out (LIFO) principle, meaning the latest data added to it is the
first to be retrieved. Because of the LIFO principle, stack allocation is
typically faster than heap allocation. A stack is of a fixed size, which is
set by the OS at compile time. It is used to store data the size of which
is known at compile time, such as local variables or object references.
Data stored there gets automatically deallocated when it gets out
of scope. The OS handles memory management of a stack, but the
programmer can cause a stack overflow error, which happens when the
stack runs out of its allocated memory. This error is usually caused by
infinite or deep recursion or large variables being stored on the stack
rather than the heap. [5]
1.2 Heap
A heap is a part of memory where data is allocated based on random
access, meaning that unlike with a stack, there is no defined order of
how data is stored and retrieved. The heap size is set at the program
2
1. Memory management
startup, but it can be expanded if required. It is used to store data
with dynamic sizes, meaning the size of the data might not be known
at the compile time, unlike the data stored on the stack. Such data
includes arrays, objects, and other data structures. The data stored on
the heap has to be accessed using pointers. A pointer is an object that
stores a memory address that can be dereferenced to get the stored
value. [6] Data on the heap will stay allocated until it is manually or
automatically recycled or until the application terminates. [5]
1.3 Manual memory management
Manual memory management requires the programmer to allocate
and recycle the memory manually. This approach was a widely sup-
ported feature of most programming languages in the past. However,
modern languages are more likely to have automatic memory man-
agement and not support manual at all. [7] Of the programming
languages that are used today, the most notable ones that support
manual memory management are C and C++, even though there are
libraries that provide garbage collector extensions to those languages.
With the manual approach, the programmer has better control of the
application’s memory than if automatic memory management was
used, which can be considered an advantage, but it all depends on
the situation. The clear disadvantage is the potential for human error
when managing the memory. One of the common errors is prema-
ture free, which happens when a part of memory is recycled but the
program is still using the data stored there. A pointer to such data
is called a dangling pointer. Another frequent error is a memory leak.
That happens when an object becomes unreachable and the memory
is not recycled. Such an error can lead to the program running out of
memory. [2, 3, 8]
1.4 Automatic memory management
Automatic memory management handles the job of recycling the mem-
ory previously allocated to objects that are no longer reachable. This is
done by automatic memory managers, which are also called garbage
collectors. These managers are usually part of the programming lan-
3
1. Memory management
guage, but they can also be an extension to a language with manual
memory management. Examples of languages with a built-in garbage
collector are Java or Python. The lack of manual management and bet-
ter efficiency can be considered an advantage, but it depends largely
on the situation. The better efficiency can, in some circumstances, also
be considered a disadvantage because, in some cases, memory can
be kept allocated even though it will never be used again. However,
the collector would not mark it for deallocation because it may still be
reachable. [2]
1.4.1 Garbage Collection
Garbage collection is a process of recycling memory belonging to no
longer reachable objects. Such unreachable data is called garbage. [9]
The concept of garbage collection was invented by John McCarthy.
He was a computer scientist known for his work in the field of artificial
intelligence and for creating the computer language Lisp in 1958. [10]
During the development of Lisp, garbage collection was invented as a
new way to recycle memory occupied by unreachable data structures
because the manual way was deemed to be "unattractive." [11]
Garbage collectors work in two phases. The first phase is called
garbage detection, during which the reachable objects are marked as
live. The second phase is called garbage reclamation, and the memory
occupied by garbage is recycled and can be used again by the program.
There are different techniques for both phases. Garbage detection is
generally done by reference counting or by tracing. In reference counting,
each object tracks the number of pointers referencing it. When a new
pointer to an object is created, the number increases, and when a
pointer is destroyed, the number decreases. An object can be marked
as garbage if the number equals zero; hence, no pointers reference
this data. Tracing is done more directly. The collector finds all live
objects by traversing all the pointers it can traverse and marks all other
unreachable objects as garbage. [9]
Garbage collectors need to be able to find all pointers in order
to mark live objects. Precise collectors expect to be provided with
information about the location of the pointers. [12] On the other hand,
conservative collectors do not need any information about the location
of the pointers. They assume that any pattern in memory that could
4
1. Memory management
potentially be a pointer is a pointer, even though that might not be the
case. An example of a conservative collector is Boehm GC. [13]
Mark-Sweep Collection
This technique uses tracing in order to find all live objects. When all
live objects are found, the memory is swept. All objects that were not
marked as live by the garbage detection process are deemed to be
garbage, and the memory they occupy is recycled. The traditional
mark-sweep collection has several problems associated with it. One of
the problems is memory fragmentation. After allocating and deallo-
cating objects of various sizes, there might be small, mostly unusable
regions of memory that might not be big enough to be used again. That
is a problem, especially when dealing with limited memory or allo-
cating large objects. Another problem is the locality of reference—the
tendency of object references to cluster in a small region of memory.
[14] Since no objects are ever moved, after some time, objects of differ-
ent ages are next to each other in memory, which negatively affects
the locality of reference. [9]
Mark-Compact Collection
The mark-compact collection is a variation of the mark-sweep collec-
tion, but it addresses some of the significant problems. This technique
adds a compaction step where the memory is compacted into two
distinct regions. All live objects are moved in memory to one end,
which leaves the rest of the memory as a single free region. The com-
paction process is often done by a linear scan of the memory, where
every live object is moved in memory to its previous live neighbor.
This compaction step fixes the memory fragmentation problem, and
it also allows for a simple allocation of large objects. The locality of
reference problem is also fixed because all live objects are compacted
to one end of the memory. [9]
5
1. Memory management
Copying Collection
A common example of this technique is the semispace collector. In
that collector, the heap is split into two separate halves, also called
semispaces, while only one semispace is being used at a time. Garbage
detection works similarly to previous techniques. Instead of compact-
ing the live objects to one end of the memory, this collection technique
copies and compacts all live objects to the currently unused semispace,
leaving the garbage behind. After the copying is done, the semispace
with the garbage is cleaned at once. [9]
Generational Collection
The major problem of copying or compacting collectors is that there
are objects that live for multiple collections and must be moved every
time. Even though most (>80%, dependent on language and program
[9]) objects live for a very short time, usually becoming garbage even
before collection is done, those who make it through the first collection
are likely to survive through more and will be copied many times.
Generational collection fixes this problem by segregating objects by
age into separate areas of memory. Areas with older objects are col-
lected less often, and areas with younger objects are collected more
often. Each area holds a generation of objects—objects of similar age.
Objects are allocated to an area usually called the new generation or
the nursery. If they survive long enough in this frequently collected
area, they are advanced to the old generation area where, to save time,
they are collected less often. Depending on the specific algorithm’s
implementation, there might be more than two areas, where each area
contains older objects than the previous one. [9]
6
2 C++ specifics
C++ is a programming language that was released in 1985 by Bjarne
Stroustrup as a direct descendent of C. It was made to be compatible
with C, but not as a superset. There are valid programs written in C
that cannot be run in C++ without prior modifications. However, C++
retains all programming techniques from C, and therefore almost all
trivial C programs can be run in C++, and the rest can be converted
rather quickly. [15]
C++ allows the programmer to manually manage the memory
by using the new and delete operators, or even C-style malloc and free
functions. However, it also provides automatic memory management,
either by using smart pointers or garbage collection libraries.
2.1 Malloc, free, new and delete
For manual memory management, C++ offers the new and delete
operators. Due to the compatibility with C, it also allows the use
of malloc and free functions. There are differences between the two
approaches, but both are usable. However, the C-style functions for
allocation are to be used only in rare situations, mainly because the
new and delete operators have superior features. Such a rare situation
might be allocating memory, which will be managed by a C library.
Malloc allocates a block of memory. It takes a size_t parameter
size, which is the requested size of memory in bytes to be allocated. It
returns a void*, pointing to the beginning of the newly allocated block
of memory. If the allocation fails, a null pointer is returned. [16]
Free deallocates a block of memory. It takes a void* parameter ptr,
which is a pointer previously allocated by malloc. If ptr does not point
to a memory allocated by malloc, it causes undefined behavior. The
function returns nothing. [17]
7
2. C++ specifics
# include < stdlib .h >
int main () {
// Allocates 5 bytes of memory
void * ptr = malloc (5) ;
// Frees allocated memory
free ( ptr ) ;
// Allocates enough memory to store one int .
// Ptr_1 is of type int * , so malloc ’s return value
must be cast .
int * ptr_1 = static_cast < int * >( malloc (1 * sizeof ( int )
));
// Checks if memory was successfully allocated .
if ( ptr_1 == nullptr ) { exit (1) ; }
// Assigns value to previously allocated memory .
* ptr_1 = 10;
// Frees allocated memory
free ( ptr_1 ) ;
}
Operator new is a function that allocates memory. It takes size_t
count as its parameter, the requested number of bytes to be allocated.
It returns a pointer to the allocated memory. Unlike the malloc function,
it throws a bad_alloc exception when it fails. [18]
New is an operator with specific behavior. It firstly allocates mem-
ory by calling the operator new function, passing the size of the re-
quested object type as the function parameter. If that is successful,
it constructs and initializes the object and returns a pointer of the
appropriate type. [19]
Function operator delete deallocates memory previously allocated
by operator new function. It takes a pointer to a memory block to be
deallocated as its argument. This function returns nothing. [20]
Delete is a special operator. It takes a pointer to an object previously
created by a new operator. Any other pointer causes undefined behav-
ior. It firstly calls the appropriate destructor, and after that, it calls the
operator delete function to deallocate the memory. [21]
8
2. C++ specifics
// Only need for operator new
# include <new >
int main () {
// Allocates 10 bytes of memory
void * ptr = operator new (10) ;
// Deallocates the memory .
operator delete ( ptr ) ;
// Allocates memory and creates an int with a value
of 5.
auto ptr1 = new int (5) ;
// No need to check if the allocation has been
successful because an exception would have been
thrown if not .
// Deallocates the memory
delete ptr1 ;
}
Using the new and delete operators is the correct way of manually
allocating and deallocating memory in C++. Some of the advantages
of this approach include returning a pointer with the proper data type,
throwing exceptions, or calling the constructors and destructors.
2.2 RAII
Resource Acquisition Is Initialization, or RAII, is a programming tech-
nique used in several programming languages, but it is mainly as-
sociated with C++. This technique binds resource acquisition, such
as memory allocation or database connection, to the object’s lifetime
and guarantees that the acquired resource is available to any function
that may use the object. The binding is done by encapsulating the
resource acquisition into a class. The class constructor acquires the
resource and throws an exception if it fails. When the object goes out
of scope, the class destructor then handles the release of the resource.
RAII requires the created class object to be allocated on the stack and
have a defined lifetime. Objects allocated on the heap would require
the explicit delete call, which the stack-allocated objects do not need
9
2. C++ specifics
because they will be implicitly deleted when they go out of scope. [22]
If this technique is followed, the risk of memory leaks is eliminated,
and the exception safety is ensured. [15] The C++ standard library
classes, such as std::string, std::vector, and others, follow the RAII prin-
ciple. Custom classes can be wrapped in the provided RAII wrappers,
also known as smart pointers. [22]
class Object {
private :
int * a ;
public :
// The constructor allocates the memory .
Object () {
a = new int (5) ;
}
// The destructor deallocates the memory .
~ Object () {
delete ( a ) ;
}
};
int main () {
Object o ;
}
2.3 Smart pointers
A smart pointer is a class template defined in the std namespace in
the memory header file. Smart pointers are used to ensure that all
allocated resources, such as memory, are deallocated correctly. It is an
object declared on the stack that gets a raw pointer to a heap-allocated
object passed to it as an argument. Because it is stored on the stack, the
destructor is called when it gets out of scope. The destructor ensures
the deallocation of the memory. The managed object can be accessed
by pointer operators, such as -> and *. The method get returns a pointer
to the managed object. There are multiple types of smart pointers, and
they all differ in their ownership of the managed object. [23]
10
2. C++ specifics
2.3.1 unique_ptr
A unique_ptr owns the managed object uniquely, meaning that no
other smart pointer can own it. A unique_ptr can not be copied, used
in algorithms requiring copying, and can not be passed by value to a
function. The only way to transfer ownership is by moving. After the
move, only one entity owns the managed object. [24] The unique_ptr
object has two components. First is the pointer to the object itself.
The second one is a deleter. The deleter is used to delete the managed
object and deallocate the memory. A programmer can supply a custom
deleter. Otherwise, the delete operator is used as the default deleter.
[25]
The function make_unique<T> constructs an object of type T and
returns a unique_ptr of type T managing the created object. It was
introduced in C++14 as a more desirable way of creating unique_ptrs.
Symmetry, simplicity, and safety were the cited reasons for the ad-
dition. Symmetry because shared_ptr already had such a function.
Simplicity because type T has to be mentioned only once instead of
twice by using this function and safety because this function does not
require the use of the new operator. [26]
# include < memory >
// Unique_ptr must be passed by reference .
void func ( std :: unique_ptr < int >& arg ) {}
int main () {
// Before C ++14
std :: unique_ptr < int > ptr ( new int (5) ) ;
// Using the make_unique function
auto ptr1 = std :: make_unique < int >(5) ;
// An object managed by ptr1 has been moved to ptr2 .
// Ptr1 no longer owns the object .
auto ptr2 = std :: move ( ptr1 ) ;
func ( ptr2 ) ;
// Unique_ptr cannot be copied .
// auto ptr3 = ptr2 ;
}
11
2. C++ specifics
2.3.2 shared_ptr
A shared_ptr is a smart pointer used in situations where an object has
more than one owner who manages its lifetime. After a shared_ptr is
initialized, it can be copied, passed to functions by value, and assigned
to another shared_ptr instance. When a copy is made, it points to the
same object. A shared_ptr holds a pointer to the object itself and a
pointer to the so-called control block. A control block is a dynamically-
allocated object that contains the pointer to the managed object or the
managed object itself, the proper deleter and allocator, and the number
of shared_ptrs and weak_ptrs that currently own the managed object.
If an object is managed by more than one shared_ptr, they all point
to the same control block. Whenever a new shared_ptr is created, the
control block increases the number of shared_ptrs currently owning
the object. Whenever a shared_ptr is destroyed, the control block
decreases the number. After all shared_ptrs owning a particular object
are destroyed, the control block destroys the object and deallocates
the memory. Deletion of the control block object happens after no
shared_ptrs and weak_ptrs are pointing to the managed object. [27]
The make_shared<T> function is a preferable way of constructing a
shared_ptr. As with the make_unique<T>, it also does not require the
use of the new operator. If the shared_ptr is constructed this way, the ob-
ject and control block allocations are done simultaneously. Otherwise,
they are done separately. If done separately, the control block holds
the pointer to the managed object. If the function make_shared<T> is
used, then the control block holds the object itself. [27]
The function use_count returns the number of shared_ptrs that
own the current object. [27]
# include < memory >
# include < iostream >
int main () {
// Creates two shared_ptrs
std :: shared_ptr < int > ptr1 ( new int (5) ) ;
auto ptr2 = std :: make_shared < int >(10) ;
// Ptr3 now co - owns the same object as ptr2 .
auto ptr3 = ptr2 ;
// Ptr2 and ptr3 own the same object .
12
2. C++ specifics
std :: cout << ( ptr2 . get () == ptr3 . get () ) ; // True
// The number of shared_ptrs owning each object .
std :: cout << ptr1 . use_count () << " ␣ " << ptr2 .
use_count () << " ␣ " << ptr3 . use_count () ; // 1 2 2
// Ptr2 is now empty .
ptr2 = nullptr ;
// Ptr3 is the last shared_ptr owning the object .
std :: cout << ptr3 . use_count () ; // 1
ptr3 = nullptr ;
// An object previously owned by ptr2 and ptr3 is now
destroyed .
}
2.3.3 weak_ptr
A weak_ptr does not own any object, and it has only a reference to an
object owned by a shared_ptr. It also contains a pointer to the control
block. The existence of a weak_ptr does not prevent the deletion of
the object managed by a shared_ptr. Therefore, it can reference an
already deleted object. A weak_ptr can assume temporary ownership
of the object if it is converted to a shared_ptr. Suppose the object
managed by the original shared_ptr is to be destroyed while there is a
temporary shared_ptr created from a weak_ptr. In that case, the life
of that object is extended until the temporary shared_ptr is destroyed.
Before accessing the stored object, the weak_ptr must be locked into a
shared_ptr. A weak_ptr can be used to solve a cyclic problem between
shared_ptrs by changing one of the shared_ptrs to a weak_ptr. [28]
The lock is a function that creates a shared_ptr that manages the
referenced object. The function expired checks whether the referenced
object still exists. [28]
# include < memory >
# include < iostream >
int main () {
auto ptr1 = std :: make_shared < int >(5) ;
// Assigns shared_ptr to weak_ptr .
std :: weak_ptr < int > weak = ptr1 ;
13
2. C++ specifics
// A weak_ptr needs to be locked before accessing the
object .
std :: cout << ( ptr1 . get () == ( weak . lock () ) . get () ) ; //
True
// Ptr1 is now empty .
ptr1 = nullptr ;
// Check if the referenced object was already deleted
.
std :: cout << weak . expired () ; // True
// The referenced object by weak can no longer be
accessed because it has been deleted .
}
2.3.4 auto_ptr
An auto_ptr is a smart pointer that manages an object during its lifetime
and handles the memory deallocation. It can transfer ownership of the
managed object by copying, but that can be fundamentally unsafe in
certain situations. It can even cause a program crash, especially when
using the sort algorithm or other algorithms dependent on copying.
The reason for an auto_ptr being unsafe is that it moves from lvalues
using copy syntax instead of move syntax. Because of the copy seman-
tics, an auto_ptr may not be placed in standard containers, but it could
still be placed into used-defined containers or an auto_ptr array. These
containers can still be sorted, which is problematic. It was deprecated
in C++11 and later removed in C++17. In C++11, unique_ptr was
introduced as its successor. It has superior functionality, and it fixes
the lvalues copying problem. [29, 30]
# include < memory >
# include < algorithm >
// This code will not compile with C ++11 or newer .
int main () {
// Create an auto_ptr
std :: auto_ptr < int > ptr1 ( new int (5) ) ;
// Ptr2 now owns the object from ptr1 .
14
2. C++ specifics
std :: auto_ptr < int > ptr2 = ptr1 ;
// Ptr1 is empty .
// An auto_ptr can not be inserted into a standard
container .
// std :: vector < std :: auto_ptr < int > > vec ;
// But it can be placed into an auto_ptr array .
std :: auto_ptr < int > array [2];
array [0]. reset ( new int (1) ) ;
array [1]. reset ( new int (2) ) ;
// Based on the sort implementation , this code might
crash .
// std :: sort ( array , array + 2 , sorting_function () ) ;
}
2.4 Garbage collectors
C++ has a long history of manual memory management. Until the ad-
dition of smart pointers in C++98 and C++11, there were no means of
automatic memory management, but there was an apparent demand
for it. Over the years, many community-developed garbage collectors
have been released and used, with Boehm GC being the most notable
of them all. This demand for garbage collection support continued,
resulting in the addition of minimal support for garbage collection
in C++11. Nevertheless, the added features were later deemed not
useful and will be removed in C++23. [31]
2.4.1 C++ GC support
In the year 2007, a group led by Hans-J. Boehm and Michael Spertus
proposed adding a partially conservative garbage collection to C++.
It should have been "programmer-directed garbage collection," [32]
meaning that the garbage collection would have been optional. There
were multiple reasons for this proposal. They hoped that this would
attract more users because they believed that a significant number of
programmers would not choose C++ as the language for their project,
either because of its memory management complexity or the lack of
15
2. C++ specifics
garbage collection. They also claimed that the existing C++ users
would benefit regardless of whether they decided to use the collector
or not. The addition of garbage collection to the existing projects would
have been effortless, and even if the programmer would choose not
to use the garbage collection, it could still be used to "collect litter."
[32] In that case, the collector would run only very infrequently, and
it would only deallocate leaked memory, providing more memory
safety. Their proposed garbage collection would also have a significant
performance advantage over the smart pointers technique. [32]
However, their proposal was not accepted. Nevertheless, later in
2008, a community reached a compromise, which resulted in the addi-
tion of "minimal support for garbage collection" [33] in C++11. Only
a few pieces from the original proposal were carried over. Most im-
portantly, this only added support for garbage collection; the garbage
collector itself was not included. The plan was to add more features
or even a full garbage collector in later standards. The features added
were designed to control what a garbage collector implementation can
and cannot do. [33]
One of the introduced features was a safely-derived pointer. It is a
pointer that is created by the default definition of the operator new or
derived from it by proper pointer arithmetic, conversions, or reinter-
pretations. [33] The following code is an example of a pointer that is
not safely-derived. This code has been shown in Bjarne Stroustrup’s
C++11 FAQ. [34]
int main () {
int * p = new int ;
p += 10;
// A GC might run here , and the memory address might
get changed .
p -= 10;
// We can not be sure that the int remains there .
* p = 10;
}
If a pointer that is not-safely-derived and has not been declared
reachable is dereferenced, that causes undefined behavior. [33]
A concept of pointer safety was introduced. There are three mod-
els of pointer safety; strict, preferred, and relaxed. Get_pointer_safety
is a function that returns the safety model selected by the current
implementation. In the relax model, safely-derived pointers and non-
16
2. C++ specifics
safely-derived pointers are both treated the same. The preferred model
is the same as the relaxed one, but a leak detector may be active, and
the implementation may hint that dereferencing a non-safely-derived
pointer is not desirable. And lastly, the strict model indicates that a
garbage collector might be active and only safe-derived pointers might
be dereferenced and deallocated. [33]
The function declare_reachable was added. This function allows a
programmer to mark an object referenced by a pointer as reachable,
which means that even if all pointers to that object are destroyed, a
garbage collector will not collect it. This declaration can be reversed
by calling the function undeclare_reachable. [33]
Declare_no_pointers introduces an option to mark a region of mem-
ory as free of pointers. After such declaration, a garbage collector will
not search this region. This can be reversed by the undeclare_no_pointers
function. [33]
# include < memory >
// Consider a presence of a GC implementation .
void reachable_test () {
int * ptr = new int (5) ;
// Declare ptr reachable
std :: d eclare _reac hable ( ptr ) ;
// Can be reversed
// std :: u nd e c la r e _r e a ch a b le ( ptr ) ;
// Declare that the next 100 bytes of memory starting
at ptr contain no pointers .
std :: d e cl a r e_ n o _p o i nt e r s ( reinterpret_cast < char * >( ptr )
, 100) ;
// Can be reversed
// std :: u n d e c l a r e _ n o _ p o i n t e r s ( reinterpret_cast < char * >(
ptr ) , 100) ;
}
17
2. C++ specifics
int main () {
reachable_test () ;
// All pointers to object declared in reachable_test
are destroyed .
// When a GC runs , it should deallocate that object ,
but because it was declared reachable , it would
not collect it as garbage
// It would also not search the next 100 bytes of
memory starting at ptr .
}
JF Bastien and Alisdair Meredith, the authors of the proposal to re-
move GC support, claim that there are clear examples of applications
where garbage collection is useful, but the current support is not help-
ful. There are multiple successful garbage collectors, but even they do
not rely on the support provided in the standard. The current garbage
collection support has many issues and inconsistencies, mainly in the
area of allocating safely-derived pointers. Also, there is no apparent
difference between relaxed and preferred safety policies. The stan-
dard library implementations have also not changed to support strict
pointer safety policy since the minimal GC support addition, and it
would require changes that are likely not to come. They also noted that
no compiler provides any support for the garbage collection features.
It was decided that the minimal support for garbage collection will
be removed, not deprecated, in C++23. [35]
2.4.2 Boehm GC
The Boehm-Demers-Weiser conservative garbage collector is an open-
source library written by Hans-J. Boehm. It provides a replacement
for C malloc and C++ new. After a heap-allocated object goes out of
scope, the garbage collector will automatically deallocate the memory.
The collector uses the mark-sweep algorithm, but it can also use the
generational collection, which is available only on some systems with
"varying restrictions." [1]
The collector can be downloaded from the official GitHub page
[36] or from Boehm’s personal site. [1] It can be installed on Windows,
UNIX, and MacOS, while the documentation is mainly concerned
with UNIX-based systems. The C and C++ toolchains, git, automake,
autoconf, and libtool are required to be installed before the installation
18
2. C++ specifics
of the collector itself. The following is a guide from the GitHub page
on downloading and installing the collector. [36]
git clone git : // github . com / ivmai / bdwgc . git
cd bdwgc
git clone git : // github . com / ivmai / libatomic_ops . git
./ autogen . sh
./ configure
make -j
make check
Note that this thesis attachment contains a bdwgc folder. That is
the Boehm GC in version 8.3.0 that was used to compile the programs
described in the next chapter. Also, note that since the collector was
downloaded, the development team has made changes to the project
structure. Hence, the described way to compile a project described
below is no longer functional with the new project structure, but it
works with the attached collector.
Programs using the Boehm garbage collector must include gc_c.h
in C or gc_cpp.h in C++. On "some" [37] platforms, it is necessary to
call GC_INIT to initialize the collector. It is not clear from the docu-
mentation on which platforms it should be called, but it states that
it "can never hurt, and is thus generally good practice." [37] It is not
entirely clear from the documentation how a C++ program should
be compiled. But after some experimentation, I have determined that
the collector works properly after being compiled like this.
g ++ - I$PATH_TO_GC$ / include / $PATH_TO_GC$ / extra / gc . c
$PATH_TO_GC$ / gc_badalc . cc - lgctba $fileName$ . cpp
The collector is written in C, mainly for use in C. However, the
official distribution comes with a C++ interface by John R. Ellis and
Jesse Hull that allows the collector to be used in C++ programs. It de-
scribes all heap-allocated objects as either collectable or uncollectable,
where the collector will deallocate all collectable objects, and the un-
collectable must be deleted manually. Objects deriving from classes
will be collectable if the class inherits from gc. Other objects will be
collectable if they are allocated with the new (GC) ... [38]
19
2. C++ specifics
# include " gc_cpp . h "
// A class Object inherits from a class gc .
class Object : public gc {};
int main () {
GC_INIT () ;
// Obj is collectable .
Object * obj = new Object () ;
// Ptr is collectable .
int * ptr = new ( GC ) int (5) ;
// Ptr1 is uncollectable , and if left like this ,
would cause memory leak
int * ptr1 = new int (5) ;
}
A collectable object can also be placed inside standard C++ con-
tainers because the collector provides an allocator. If collectable objects
are placed in, for example, a vector that goes out of scope, these objects
will be destroyed appropriately by the collector. The program must
include gc_allocator.h. [38]
# include < vector >
# include " gc_cpp . h "
# include " gc_allocator . h "
int main () {
// Create a vector with the GC allocator .
std :: vector < int * , gc_allocator < int * > > vec ;
for ( int x = 0; x < 1000; x ++) {
// Push a collectable object into the vector .
vec . push_back ( new ( GC ) int ( x ) ) ;
}
}
Hans-J. Boehm points out some advantages of the collector over
manual memory management. Programs using garbage collection save
on development time. He claims that 30%–40% of development time
is used to handle memory management in programs that use complex
data structures. Using garbage collection prevents memory leaks and
saves time on complex manual memory handling. He also states that
programs using a garbage collector are likely to use less memory and
run at least as fast as programs using manual management. The speed
20
2. C++ specifics
is related to object size. Programs allocating small objects should run
at a comparable speed to the manual, but programs with larger objects
might end up being slower. [39]
21
3 Implementation and analysis
In this chapter, I analyze different memory management techniques
in C++. The analysis compares the same programs written using
different memory management methods. There are three different
programs, each with a different focus. The techniques under consider-
ation are manual memory management with new and delete operators,
automatic memory management with smart pointers, and the Boehm
garbage collector. The analysis itself measures the program’s total
memory usage, heap size, heap usage, and total runtime. Memory
usage measures the resident set size (RSS) and virtual memory size
(VSZ). RSS is the amount of non-swapped memory used by the pro-
cess, and VSZ is the size of the virtual memory allocated to the process.
[40]
3.1 Hardware and software used
RSS and VSZ are measured using a graph.sh script. [41] This script
was created by Ben Marchant to log the current RSS and VSZ of a
process and generate a corresponding graph. It has been modified to
fit my needs better. The original version takes measurements every
second, which has been changed to 0.1 seconds. After the process has
terminated, the script would make and display a graph created from
the logged numbers. That feature has also been deleted. The modified
script is attached to the thesis as graph.sh. To use this script, it needs to
be run with a process name as its parameter. After it is run, the script
waits until the process with the corresponding name becomes active.
When the process terminates, the script ends as well, and a file with
logged data is created. The following example shows how the script
should be run with a process named a.out.
./ graph . sh a . out
Heap size and usage are measured by Valgrind’s tool Massif. Note
that only programs using manual memory management and smart
pointers can be passed to Massif. Programs using the Boehm garbage
collector are not suitable for Valgrind analysis. Nevertheless, the col-
lector offers functions that provide information about the heap size
22
3. Implementation and analysis
and usage. Because of that, programs using the Boehm GC contain
a commented-out section that includes a thread that logs the heap
size and usage every 0.01 seconds. This section is only used when
analyzing the heap. Testing an empty program running the thread
showed no meaningful impact on the heap at all. There were only 80 B
allocated on the heap.
Runtime is recorded using the Unix time command. Each program
is run multiple times to form an average time.
All programs are run on a desktop computer with a MSI H97 PC
Mate(MS-7850) motherboard, Intel(R) Xeon(R) CPU E3-1231 v3 @
3.40GHz CPU, 24GB of DDR3 1600MHz RAM, GeForce GTX 1060 6GB
graphics card and KINGSTON SA400S3 960GB SSD. The computer is
running Ubuntu 20.04.3 LTS 64-bit operating system.
Programs are compiled using GCC version 9.3.0 with the C++14
standard. Boehm GC version 8.3.0 is used.
3.2 Programs overview and examples
In this chapter, I describe the analyzed programs, show examples from
their code, and perform the analysis. Note that later in the chapter, the
graphs representing the heap size and usage do not have x-axis values.
That is intentional because heap analysis was done using a method
that took more time than the time and memory analysis; hence the
time values would not align.
The attached file howToCompile.txt describes the way each program
was compiled.
3.2.1 Program #1
Programs from this example are attached to this thesis as emptyMan-
ual.cpp, emptySmartPointers.cpp and emptyGC.cpp.
The programs in this example are essentially empty. The purpose
of this test is to determine if any of the used techniques require more
memory just because they include a library and how this might in-
23
3. Implementation and analysis
fluence the execution time. All three programs contain the following
code.
int main () {
for ( int x = 0; x < 1000000000; x ++) {
trick_optimalization (x);
}
}
This for loop is present in order to keep the program running
long enough so the graph.sh script can log memory usage. Because all
programs are compiled with -O3 flag, the trick_optimalization function
is present. This function forces the compiler to actually execute the
loop.
int count = 0;
void t r i c k _ o p t i m a l i z a t i o n ( int x ) {
count += x ;
}
Program using smart pointers contains the following include.
# include < memory >
The memory library is necessary for the use of smart pointers. The
program using the Boehm garbage collector also has one include and
one extra method call in its main.
# include " gc_cpp . h "
int main () {
GC_INIT () ;
for ( int x = 0; x < 1000000000; x ++) {
trick_optimalization (x);
}
}
All C++ programs using the Boehm garbage collector must include
the gc_cpp.h library. GC_INIT is a function that initializes the collector.
The documentation states that it is recommended to call it as a first
thing in main, so I consider it a crucial part of the collector. Therefore,
it is included even in an empty program. [1]
24
3. Implementation and analysis
Table 3.1: Program #1 execution time.
Manual Smart Pointers Boehm GC
∼0.0917s ∼0.0897s ∼0.0929s
Table 3.2: Program #1 memory usage.
Manual Smart Pointers Boehm GC
RSS 584 KiB 588 KiB 2967 KiB
VSZ 2364 KiB 2364 KiB 6592 KiB
The analysis of each program’s execution time revealed almost no
impact on the runtime of such small programs. The program using
smart pointers was the quickest, but the difference between that and
the one using Boehm GC, which was the slowest, was only 0.0032s.
On the other hand, the analysis of memory usage revealed some dif-
ferences. Memory usage of programs using manual memory manage-
ment and smart pointers was almost the same. The program using the
Boehm garbage collector used significantly more memory.
3.2.2 Program #2
Programs from this example are attached to this thesis as smallMan-
ual.cpp, smallSmartPointers.cpp and smallGC.cpp.
The primary goal of this example is to analyze the heap size and
usage while deallocating objects and allocating new ones to take their
place. The main question in this example is whether all programs can
deallocate the memory quickly enough to reuse it instead of request-
ing more heap memory from the OS. All programs consist of two
functions.
// ? is replaced in each implementation with a code
corresponding to specific memory management .
void create_objects ( std :: vector <? >& vec , int size ) ;
void delete_objects ( std :: vector <? >& vec , int amount ) ;
Programs in this example firstly create ten million ints allocated
on the heap and then store the pointers in a vector. The next step is to
25
3. Implementation and analysis
delete half of them and immediately create enough new ints to bring
the total back to the starting amount.
int main () {
std :: vector <? > vec ;
create_objects ( vec , 10000000) ;
delete_objects ( vec , 5000000) ;
create_objects ( vec , 5000000) ;
}
Table 3.3: Program #2 execution time.
Manual Smart Pointers Boehm GC
∼0.5096s ∼0.5161s ∼0.3827s
The execution time difference was noticeable in this example. The
program using the Boehm garbage collector ended up being the quick-
est. Both manual memory management and smart pointers took about
the same time, but they were about 34% slower than the Boehm GC.
Figure 3.1: Memory usage in manual
26
3. Implementation and analysis
Figure 3.2: Memory usage in smart pointers
Figure 3.3: Memory usage in Boehm GC
The program written using the Boehm garbage collector ended
up using the least amount of memory. The memory usage peak was
365 MiB/369 MiB (RSS/VSZ). The other two programs peaked at the
same values—383 MiB/453 MiB.
27
3. Implementation and analysis
Figure 3.4: Heap usage in manual
Figure 3.5: Heap usage in smart pointers
28
3. Implementation and analysis
Figure 3.6: Heap usage in Boehm GC
The peak heap size and usage for programs using manual mem-
ory management and smart pointers ended up identical again. Both
peaked at a heap size of 384 MiB and usage of 224 MiB. The Boehm
GC program’s maximum heap size was smaller than the previous two
programs, peaking at 355 MiB, but the maximum usage was much
higher—351 MiB. The collector was run four times during the program
execution. There is a clear difference between the first two programs
and the program using Boehm GC. The graphs show that the heap size
and usage peaked when the first object allocation concluded. The ob-
jects’ deletion can be observed in the heap size reduction. Heap usage
also decreased, but not that significantly. After the secondary object
creation, the heap size increases once again to almost peak value, with
the heap usage also slightly increasing to near peak value. Meanwhile,
the garbage collector cycles can be clearly seen in the graph represent-
ing memory size and usage in the program using Boehm GC. The
heap size peaked before the program’s termination, signifying that
the collector could not recycle the memory quickly enough to reuse
it. The program had to request more heap memory for the secondary
allocation.
29
3. Implementation and analysis
3.2.3 Program #3
Programs from this example are attached to this thesis as largeMan-
ual.cpp, largeSmartPointers.cpp and largeGC.cpp.
This example is designed to test longer-lasting programs, which
allocate objects of different sizes and lifetimes and contain different
variables. The program’s main function looks like this.
int main () {
Holder h ;
for ( int x = 0; x < 100; x ++) {
long_life (2 , 1 , & h ) ;
short_life (100 , 10) ;
}
}
Functions long_life and short_life both create new objects. Long_life
allocates objects which are stored in the Holder object and are deal-
located when the program terminates. On the other hand, short_life
creates objects that go out of scope moments after their creation. Both
functions create objects of two types, small and large. The first two
parameters of each function set how many small, respectively large
objects to create. This is an example of small objects in the program
using smart pointers.
class C l a s s W i t h B o t h A l l o c a t i o n s {
int a = 5;
std :: unique_ptr < int > b = std :: make_unique < int >(10) ;
};
class Cl as sW it hH ea pA rr ay {
private :
std :: vector < std :: unique_ptr < int > > a ;
public :
C las sW it hH ea pA rr ay ( int size ) {
for ( int x = 0; x < size ; x ++) {
a . push_back ( std :: make_unique < int >( x ) ) ;
}
}
};
30
3. Implementation and analysis
There are multiple classes, which contain different variables. Class-
WithBothAllocations contains int variable, and it also has a unique_ptr,
which points to a heap-allocated object. ClassWithHeapArray is a class
that contains a vector of heap-allocated objects. All objects are allo-
cated in the constructor. This class is considered small when the vector
contains ten objects and large when it contains ten thousand objects.
class C l a ss W i th A l lC o n te n t {
private :
int a = 5;
std :: unique_ptr < int > b = std :: make_unique < int
>(10) ;
int c [10] = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10};
std :: vector < std :: unique_ptr < int > > d ;
std :: unique_ptr < ClassWithBothAllocations > e ;
std :: vector < std :: unique_ptr <
ClassWithBothAllocations > > f ;
public :
C l as s W it h A ll C o nt e n t ( int size ) {
for ( int x = 0; x < size ; x ++) {
d . push_back ( std :: make_unique < int >( x ) ) ;
f . push_back ( std :: make_unique <
ClassWithBothAllocations >() ) ;
}
e = std :: make_unique < ClassWithBothAllocations
>() ;
}
};
This is an example of a class that combines multiple different types
of variables, including heap allocations of various types or a vector
containing objects that hold other heap-allocated objects. This class
can also be considered small and large at the same time, based on the
size of the vectors.
Table 3.4: Program #3 execution time.
Manual Smart Pointers Boehm GC
∼2.532s ∼2.470s ∼1.434s
31
3. Implementation and analysis
The execution times show that the Boehm GC is again the quickest
technique. The program using smart pointers is about 42% slower
than that, and the program using manual memory management is
slower then the smart pointers by approximately 3%.
Figure 3.7: Memory usage in manual
Figure 3.8: Memory usage in smart pointers
32
3. Implementation and analysis
Figure 3.9: Memory usage in Boehm GC
Program using smart pointers used the most memory by a little bit.
It peaked at 276 MiB/281 MiB (RSS/VSZ). Meanwhile, the program
using Boehm GC used slightly less memory, 262 MiB/265 MiB. In this
example, manual memory management ended up being the best for
memory usage. It also used 281 MiB of VSZ, but it used only 260 MiB
of RSS.
33
3. Implementation and analysis
Figure 3.10: Heap usage in manual
Figure 3.11: Heap usage in smart pointers
34
3. Implementation and analysis
Figure 3.12: Heap usage in Boehm GC
The heap size and usage analysis show that programs using man-
ual memory management and smart pointers peaked at the same
values. The maximum heap size was 220 MiB, and the maximum us-
age was 109 MiB. On the other hand, the program using Boehm GC
used much more heap than the other programs. The maximum size
was not that much higher, only 254 MiB, but the usage was more than
two times higher, peaking at 254 MiB as well. As seen in the graph, the
garbage collector appears to run every time the heap usage reaches
the maximum heap size. However, because of the limitations of this
collector and the nature of this example, not all available garbage
is cleared. Thus, the program ended up with much higher memory
usage. The garbage collector was run 30 times during the program’s
execution.
35
3. Implementation and analysis
3.3 Analysis results
This chapter is concerned with the results of the analysis. Results from
all three programs are combined into an overall conclusion.
3.3.1 Time
Programs using Boehm GC ended up being the quickest in all exam-
ples except the first one. It was the slowest in that example, which was
most likely caused by the garbage collector initialization. Manual mem-
ory management was quicker than smart pointers only once, but the
time differences in all three examples between these two techniques
were not that significant.
3.3.2 Memory
In the first example, the RSS and VSZ were almost the same for manual
and smart pointers. RSS for the Boehm GC was about five times bigger,
and VSZ was about 2.8 times bigger. Such a significant discrepancy is
caused by calling CG_INIT in the main function of the program. It was
called because it is a recommended step to initialize the collector, but
if it was not called, the discrepancy would have shrunk a lot. Without
the call, RSS would be only three times bigger. Meanwhile, the VSZ
would stay about the same.
In the second example, the Boehm GC used the least amount of
memory. It used about 4.7% less RSS and about 18.5% less VSZ than
manual and smart pointers, both of which used the same. It is inter-
esting to note that garbage collection used only 4 MiB more VSZ than
RSS. Meanwhile, both other programs used about 70 MiB more VSZ
than RSS. The Boehm GC heap size was about 7.5% smaller than both
other programs, but the heap usage was about 57% higher. Again, it
is interesting to note that the program using the Boehm GC did not
ask for more heap than it needed. Meanwhile, both other programs
allocated much more heap memory than they actually needed.
In the third example, the program using manual memory man-
agement used the least amount of RSS. Smart pointers used about
6% more and Boehm GC about 1% more. Meanwhile, VSZ was the
same for manual and smart pointers, but Boehm GC used about 5.5%
36
3. Implementation and analysis
less VSZ. Heap usage and size were the same for manual and smart
pointers, but the Boehm GC used significantly more heap. The heap
size was 15% bigger, and the peak usage more than doubled.
3.3.3 User-friendliness
Manual memory management is easy to use. Only basic knowledge
of programming is needed. Online documentation is extensive and
accessible. The main downfall of this approach is the potential for
human error, mainly memory leaks, but that is to be expected.
Smart pointers require a bit more knowledge of C++ but are still
easy to use. Documentation is again very vast. The potential for mem-
ory leaks is eliminated.
Using the Boehm GC is easy, but it is slightly harder to learn. The
main problem is the lack of consistent and accurate documentation.
There are two official sites for this collector, Boehm’s personal site [1]
and the official project’s GitHub [36]. I have often found inconsisten-
cies, such as different class names, non-existing files, or different file
extensions. For example, Boehm’s site says to include the gc_alloc.h
header file for proper GC allocators, but that file does not exist. How-
ever, there is a gc_allocator.h file, which is the correct file as correctly
described on the GitHub page. The GitHub page also says to compile
a project with the libgc.a file, but such a file does not exist.
The documentation for C++ is minimal and lacks essential infor-
mation. How to correctly compile the project with the collector is also
unclear. A clear advantage is the elimination of the need for manual
memory management. Programs using the collector cannot be tested
using Valgrind, but the collector has internal functions that provide
similar information.
3.3.4 Overall
I believe that it cannot be said that there is a clear best technique. All
approaches have advantages and disadvantages, and all can be useful
for specific tasks. Manual memory management is probably the most
distinct approach. There are some situations where manual memory
37
3. Implementation and analysis
management is necessary, and therefore the choice is clear. Then, if
manual memory management is not desired, both smart pointers and
garbage collection are valid options. It appears that garbage collection
is better suited for programs that use smaller objects or do not allocate
and deallocate objects without a stop, so the collector can properly
delete all inaccessible objects. The Boehm GC seems to struggle in
dealing with large objects, especially when asked to deallocate a lot of
them at one time. In such programs, smart pointers look to be a better
choice.
38
4 Conclusion
This thesis aimed to explore memory management techniques in C++,
explain general terms regarding memory management, and analyze
programs using different memory management techniques based on
a program’s performance, memory usage, and user-friendliness.
In the first chapter, I described heap and stack, and a distinction
was drawn between manual and automatic memory management.
The topic of garbage collectors was explored, showcasing some of the
well-known algorithms used in garbage collection.
Memory management specifics in C++ were described in the sec-
ond chapter. Manual management using new and delete operators, even
C-style malloc and free, all different types of smart pointers, and C++
support for garbage collection were explored. Also, the RAII principle
and the Boehm garbage collector were presented. All techniques were
accompanied by code examples.
The last chapter was concerned with the implementation and anal-
ysis of three programs. All programs were written three times, using
different technique every time. The analysis was primarily interested
in execution time, memory usage, and user-friendliness. The overall
analysis conclusion was that there is no clear best technique to use, and
all three have advantages and disadvantages. Their usage depends on
the project and the programmer’s style.
The follow-up to this thesis could analyze more sophisticated pro-
grams to see if there is an area where one technique is clearly bet-
ter than others. Another interesting study might concern other C++
garbage collectors and their performance against the Boehm GC or
even implementing a new garbage collector.
39
Bibliography
1. BOEHM, Hans. A garbage collector for C and C++ [online] [visited
on 2021-10-29]. Available from: https://www.hboehm.info/gc/.
2. Overview [online]. Memory Management Reference [visited on
2021-08-23]. Available from: https://www.memorymanagement.
org/mmref/begin.html#manual-memory-management.
3. GILMORE, Stephen. Advances in Programming Languages: Mem-
ory management [online]. Edinburgh: The University of Edin-
burgh, 2007 [visited on 2021-08-10]. Available from: https://
homepages . inf . ed . ac . uk / stg / teaching / apl / handouts /
memory.pdf.
4. BRINKERHOFF, Delroy A. Memory Management: Stack And Heap
[online]. 2015 [visited on 2021-10-08]. Available from: http://
icarus.cs.weber.edu/~dab/cs1410/textbook/4.Pointers/
memory.html.
5. FERRES, Leo. Memory management in C: The heap and the stack [on-
line]. Universidad de Concepcion, 2010-10-07 [visited on 2021-
08-10]. Available from: https://cs.gmu.edu/~zduric/cs262/
Slides/teoX.pdf.
6. MYERS, Bob. Pointer Basics [online]. Florida State University
[visited on 2021-08-10]. Available from: https://www.cs.fsu.
edu/~myers/c++/notes/pointers1.html.
7. Memory management in various languages [online]. Memory Man-
agement Reference [visited on 2021-08-23]. Available from: https:
//www.memorymanagement.org/mmref/lang.html.
8. Memory Management Glossary [online]. Memory Management
Reference [visited on 2021-08-31]. Available from: https://www.
memorymanagement.org/glossary/.
9. WILSON, Paul R. Uniprocessor garbage collection techniques.
In: International Workshop on Memory Management. 1992, pp. 1–42.
10. HYMAN, Paul. John McCarthy, 1927–2011. Communications of the
ACM. 2012, vol. 55, no. 1, pp. 28–29.
40
BIBLIOGRAPHY
11. MCCARTHY, John. History of LISP. In: History of programming
languages. 1978, pp. 173–185.
12. RAFKIND, Jon; WICK, Adam; REGEHR, John; FLATT, Matthew.
Precise garbage collection for C. In: Proceedings of the 2009 inter-
national symposium on Memory management. 2009, pp. 39–48.
13. BOEHM, Hans-Juergen. Space efficient conservative garbage col-
lection. ACM SIGPLAN Notices. 1993, vol. 28, no. 6, pp. 197–206.
14. DENNING, Peter J. The locality principle. In: Communication
Networks And Computer Systems: A Tribute to Professor Erol Gelenbe.
World Scientific, 2006, pp. 43–67.
15. STROUSTRUP, Bjarne. Bjarne Stroustrup’s FAQ [online]. 2021-
07-23 [visited on 2021-10-18]. Available from: https : / / www .
stroustrup.com/bs_faq.html.
16. malloc - C++ Reference [online]. Cplusplus.com [visited on 2021-
10-21]. Available from: https://www.cplusplus.com/reference/
cstdlib/malloc/.
17. free - C++ Reference [online]. Cplusplus.com [visited on 2021-
10-21]. Available from: https://cplusplus.com/reference/
cstdlib/free/.
18. operator new - C++ Reference [online]. Cplusplus.com [visited
on 2021-10-21]. Available from: https://www.cplusplus.com/
reference/new/operator%20new/.
19. new expression - C++ Reference [online]. cppreference.com [vis-
ited on 2021-11-13]. Available from: https://en.cppreference.
com/w/cpp/language/new.
20. operator delete - C++ Reference [online]. Cplusplus.com [visited
on 2021-10-21]. Available from: https://www.cplusplus.com/
reference/new/operator%20delete/.
21. delete expression - C++ Reference [online]. cppreference.com [vis-
ited on 2021-11-13]. Available from: https://en.cppreference.
com/w/cpp/language/delete.
22. RAII - c++reference.com [online]. cppreference.com [visited on
2021-10-25]. Available from: https://en.cppreference.com/w/
cpp/language/raii.
41
BIBLIOGRAPHY
23. Smart pointers (Modern C++) [online]. Docs.microsoft.com, 2021-
03-08 [visited on 2021-10-26]. Available from: https://docs.
microsoft . com / en - us / cpp / cpp / smart - pointers - modern -
cpp?view=msvc-160.
24. How to: Create and use unique_ptr instances [online]. Docs.microsoft.com,
2021-11-12 [visited on 2021-11-26]. Available from: https : / /
docs.microsoft.com/en- us/cpp/cpp/how- to- create- and-
use-unique-ptr-instances?view=msvc-170.
25. std::unique_ptr - c++reference.com [online]. cppreference.com [vis-
ited on 2021-10-26]. Available from: https://en.cppreference.
com/w/cpp/memory/unique_ptr.
26. LAVAVEJ, Stephan T. make_unique [online]. open-std.org, 2013-
03-15 [visited on 2021-10-27]. Available from: http://www.open-
std.org/jtc1/sc22/wg21/docs/papers/2013/n3588.txt.
27. std::shared_ptr - c++reference.com [online]. cppreference.com [vis-
ited on 2021-10-28]. Available from: https://en.cppreference.
com/w/cpp/memory/shared_ptr.
28. std::weak_ptr - c++reference.com [online]. cppreference.com [vis-
ited on 2021-10-28]. Available from: https://en.cppreference.
com/w/cpp/memory/weak_ptr.
29. std::auto_ptr - c++reference.com [online]. cppreference.com [vis-
ited on 2021-10-28]. Available from: https://en.cppreference.
com/w/cpp/memory/auto_ptr.
30. HINNANT, Howard E. Rvalue Reference Recommendations for Chap-
ter 20 [online]. open-std.org, 2005-08-26 [visited on 2021-10-28].
Available from: http://www.open-std.org/jtc1/sc22/wg21/
docs/papers/2005/n1856.html.
31. History of C++ - c++reference.com [online]. cppreference.com [vis-
ited on 2021-10-31]. Available from: https://en.cppreference.
com/w/cpp/language/history.
32. HANS-J. BOEHM, Michael Spertus. Transparent Programmer-Directed
Garbage Collection for C++ [online]. 2007-06-20 [visited on 2021-
10-31]. Available from: http://www.open-std.org/jtc1/sc22/
wg21/docs/papers/2007/n2310.pdf.
42
BIBLIOGRAPHY
33. HANS-J. BOEHM Michael Spertus, Clark Nelson. N2670: Minimal
Support for Garbage Collection and Reachability-Based Leak Detection
(revised) [online]. 2008-06-13 [visited on 2021-10-31]. Available
from: http : / / www . open - std . org / jtc1 / sc22 / wg21 / docs /
papers/2008/n2670.htm.
34. STROUSTRUP, Bjarne. C++11 - the new ISO C++ standard [on-
line]. 2016-08-19 [visited on 2021-11-23]. Available from: https:
//www.stroustrup.com/C++11FAQ.html.
35. JF BASTIEN, Alisdair Meredith. P2186R2: Removing Garbage Col-
lection Support [online]. 2021-04-16 [visited on 2021-10-31]. Avail-
able from: http://www.open-std.org/jtc1/sc22/wg21/docs/
papers/2021/p2186r2.html.
36. BOEHM, Hans-J. Boehm-Demers-Weiser Garbage Collector [online].
GitHub, [n.d.] [visited on 2021-11-30]. Available from: https:
//github.com/ivmai/bdwgc.
37. BOEHM, Hans. Using the Garbage Collector: A simple example [on-
line] [visited on 2021-11-25]. Available from: https : / / www .
hboehm.info/gc/simple_example.html.
38. BOEHM, Hans. Garbage Collector Interface [online] [visited on
2021-11-25]. Available from: https://www.hboehm.info/gc/
gcinterface.html.
39. BOEHM, Hans. Advantages and Disadvantages of Conservative Garbage
Collection [online] [visited on 2021-11-25]. Available from: https:
//www.hboehm.info/gc/issues.html.
40. ps(1) — Linux manual page [online]. 2020-06-04 [visited on 2022-
03-27]. Available from: https://man7.org/linux/man-pages/
man1/ps.1.html.
41. MARCHANT, Ben. graph.sh [online]. 2020-12-10 [visited on 2021-
11-05]. Available from: https://gist.github.com/nicolasazrak/
32d68ed6c845a095f75f037ecc2f0436.
43
A Attachments
• bdwgc folder
– Boehm garbage collector.
• graph.sh
– Modified script used to log RSS and VSZ during runtime
of a UNIX process.
• emptyManual.cpp
– Program #1 using manual memory management.
• emptySmartPointers.cpp
– Program #1 using smart pointers.
• emptyGC.cpp
– Program #1 using Boehm garbage collection.
• smallManual.cpp
– Program #2 using manual memory management.
• smallSmartPointers.cpp
– Program #2 using smart pointers.
• smallGC.cpp
– Program #2 using Boehm garbage collection.
• largeManual.cpp
– Program #3 using manual memory management.
• largeSmartPointers.cpp
– Program #3 using smart pointers.
• largeGC.cpp
– Program #3 using Boehm garbage collection.
• howToCompile.txt
– File describing the programs compilation.
44