In this lesson,
we will talk about a hypothetical example to present the
dangers of dynamic memory allocation.
Let's imagine that we are working on a simple video game.
This game does not have an end.
The goal is to beat as many enemies as you can,
which will increase your high score.
You can only fight one enemy at a time,
and we are responsible for rendering these enemies on the computer screen.
The game will be over once the player is beaten by one of the enemies.
This is the code which could hypothetically run this simple game.
Of course,
this code wouldn't work because we are just pretending that I
included some game engine library which provides me with all of
these useful classes for creating a video game.
In essence, a video game is just a never‑ending while loop.
In each iteration, the game is checking if something changed.
Maybe a key on the joystick was pressed or some enemy
from the game is attacking you.
Either way, once the program verifies the changes,
it needs to render what's supposed to be on the screen.
Finally, if you exit the game by pressing some button or losing,
we will break out of the while loop and the game is over.
We are not interested in game development,
so we can imagine that all of this logic is already implemented.
The part that we are responsible for is loading enemies.
We can imagine that this variable stores a Boolean value.
So if the new enemy is supposed to be loaded in the game,
we will call this loadEnemy function.
Here we declared a pointer to a current_enemy since only
one enemy can exist on the screen.
Once this enemy is defeated, there will be another enemy in sight.
We will load a new enemy with this loadEnemy function,
point this pointer to a new current_enemy,
and so on.
Let's check out this loadEnemy function.
As you can see,
we are here allocating space on the heap for this new Enemy character,
and we store its address in this en pointer.
Every enemy has some special power,
and this power is randomly generated so that the game doesn't get predictable.
Since we are working with a pointer here,
I had to first dereference it to get to the power property of the object.
There is a simpler way to do this,
but more on that in one of the following modules.
Finally,
we are returning a pointer to this enemy from the
heap back to the main function.
Once the function is finished,
this local en pointer will be popped off the stack.
But that's okay because the address of the allocated enemy on
the heap is now assigned to the current_enemy pointer and the
enemy is now rendered on the screen.
This object takes up a large amount of memory on the heap
because we need to also load textures and other game‑related
properties of this enemy character, properties which are too big for stack.
So once we beat the first enemy,
this Boolean will again be true because there should be another enemy in sight.
We need to allocate space for a new one and point the
current_enemy pointer to that place in memory,
which means the whole process is repeated.
Notice that every enemy allocates a different amount of memory
because enemies have different properties.
In practice, the Enemy object could contain pointers to other dependencies.
In this case, something like textures.
But to keep things simple,
let's just say that all of the memory each enemy needs is
contained within the Enemy object itself.
The player will beat the second enemy and the game continues.
After some time, the player will start to notice that the game is getting slower.
You can see that we are allocating memory with new,
but we are not de allocating it with delete.
When you work on large projects, this is an easy mistake to make,
especially if the code is larger and more complicated with
pointers being passed around all over the place.
When the game loads the next enemy,
the previous enemy still stays in memory on the heap.
And since we redirected our pointer to the new enemy,
we have no way of knowing where these old enemies are,
which means we cannot deallocate that memory anymore.
With each new enemy, the memory is slowly being taken away, but never released.
We can almost imagine that it's leaking, hence the term memory leak.
Once the player beats a large amount of enemies,
the program has a problem with finding the
sufficient amount of memory on the heap.
Finally, once the program is unable to find enough memory to load new enemies,
the game will crash.
This will probably leave you with the bad allocation exception and
your gaming company with bad reviews and shame.
You can see that there are still some free unallocated bytes on the heap,
but each object needs to have the right amount of consecutive bytes,
large enough to store the whole object in one piece.
Since the heap doesn't stack objects next to each
other, this problem is not unusual.
Now you can see why releasing memory is important and why
we cannot afford to have memory leaks.
Newer languages like Python and Ruby have garbage collectors which pay
attention to unused memory and remove unnecessary objects.
Of course, this comes at a cost, which is why C++ programs usually run faster,
but on the other hand, they are arguably more verbose and more complicated.
C++ developers wanted to give us the best of both worlds, so they
created wrappers and abstractions around these dangerous dynamic
allocations, but more on that later.
Sometimes you will need to use new and delete directly, and in
those situations, you need to be sure that everything you allocate
needs to be deallocated at some point.
Memory leaks are by no means a new problem, so there are a lot of tools
which can help you find them before they actually occur.
One of these tools is a software called Valgrind.
In simplest terms,
Valgrind will basically run your program while intercepting
allocations on the heap. So if the number of allocations does
not match the number of deallocations,
that is the first sign that you did something wrong.
Of course, this tool does so much more than that,
but that is out of the scope of this course.
In the next lesson, we will learn how to overload new and delete operators,
which can be used as a simple way to check if our memory is leaking.