본문 바로가기

Programming/C++ Basic

[Basic C++] 09 - Memory Allocation (Stack / Heap) in C++


In this post, we are going to talk about one of the most important topics in C++; Memory Allocation (Global/static, stack, and heap). Without firm knowledge about them, you are not able to generate good programs ever. They are rather simple concepts, however, it's pretty hard to utilize the knowledge. It will take time for you to go deep into this, but practice makes perfect.


Table of Contents

1. What is MEMORY?

2. A simple example of the three memory types


 

 

1. What is memory?

When you talk about memory in C++, it means RAM (Random access memory). RAM is memory that you can store and read data while your program is running. RAM is the volatile memory whose lifetime ends with the demise of the current running program. You might have heard the word "Volatile" a lot while taking chemistry classes at school. Volatile descrives something easily evaporates at normal temperature.

 

Volatile memory, of course, does not evaporate, but it only lasts while powered on (or the lifetime of a program). This is why any data not being saved in a hard drive disappears into thin air after power off. A hard drive is, on the other hand, non-volatile memory. Thus data remains safe without power. Figure 1 shows a typical RAM card for PC.

Figure 1 A typical RAM for PC

 

Figure 2 is a schematic explanation of RAM. In C++, there are three types of memory in RAM; Global/Static, Stack, and Heap. They exist on RAM together but separately. Each type of memory has its own purpose and it's very important to use how to use them correctly in C++ because C++ does not manage your memory automatically as Python does.

 

Figure 2 Memory structure in RAM

 

1.1) Global / Static Memory

 

As the name says, global and static memory stores values exist for the entire lifetime of the program. It is only cleared out when the program is terminated. The programming code you've written is also allocated here.

 

1.2) Stack memory

 

Unlike global/static memory, stack memory stores values that only exist during a certain period in the program. Variables defined in a function can be good examples. They are allocated in stack memory when declared and cleared out with the demise of the function. That's why they are called local variables.

 

Stack memory, as the name says, allocates values side by side like a stack and the allocated space is automatically reclaimed at the end of the lifetime of the variables. If you try to allocate more memory than your stack memory can store, "Stack Overflow" occurs. Your program crashes and is terminated.

 

1.3) Heap memory

 

Heap is also referred to as dynamic memory and the allocated space is not automatically reclaimed by the compiler, thus the memory management is on your hands. Unlike stack memory, variables are allocated on a random location by the compiler. One might say it sounds like the heap is somewhat unnecessary.

 

However, our computers are not supercomputers and even supercomputers do not have infinite memory. There are a lot of moments at which a programmer needs to control the lifetime of variables; when to create and when to delete for efficient memory management. This is why the heap exists. By understanding which variables are created and are erased, you are able to code more efficient programs.

 


2. A simple example of the three memory types

Code example 1 is a simple example showing how the three types of memory work in C++. There are four int variables declared in different memory sections.

 

ga = global int 1234

la = local int 1234 in a function

ma = local int 1234 in the main function

a = dynamic int 1234 in the main function

 

Code example 1

#include <iostream>

int ga = 1234;

void function()
{
	int la = 1234;
}

int main()
{
	int ma = 1234;
	int *a = new int(1234);

	function();
}

 

Debugging of code example 1

 

Now, let's debug the example code 1. I made 6 breakpoints on the code and ran it by MSVC 2019 debugger. Go to memory window from Tool -> Window -> Memory 1.

 

Global variable 'ga'

The program is running and the flow has come into the main function after the header files and global variables are read. At the moment, we can see where the global int is and its value by searching "&ga". & (Ampersand) means to get the memory address of a value. The address of the global int is 0x00A3A000.

 

Local variable 'ma' (in the main function)

Press F10 (Proceed) and see where 'ma' is allocated. The value is the same as the global int but the memory address is totally different. Plus there are the four-byte guard bytes back and forth because it's allocated in Stack memory.

 

Dynamic variable 'new a' (in the main function)

Press F10 to continue debugging and see line 14. The dynamic allocation keyword "new" assigned the variable 'a' in the heap. '*a' means a pointer to the variable. I am going to cover "pointer" and "new" keyword later, no worries.  In C++ a pointer is just a variable that stores the address of a variable. "New" is used to allocate memory in the heap and that's all. Now you can see the same data "d2 04 00 00".

 

So in this case, "a" is a pointer variable allocated in the Stack memory pointing to the variable "a" allocated in the Heap memory. Do not be mistaken here. You can not declare a memory in the heap directly. There must be a pointer variable connecting the dynamically allocated variable and you. You are only able to access the variable through the pointer.

 

Unlike the stack memory, the heap memory allocation is followed and preceded by the four-byte guard bytes "fd", not "cc".

 

Local variable la (in the function)

Press F10 again to continue debugging. Now you can see the variable 'la' is declared in the function. You can now check the memory address of 'la' too. The value is the same as 'ma' and it also has the guard bytes "cc".

 

The lifetime of a local variable

Press F10 again to continue debugging. The programming has reached its end this stage. Now let's find the local variable we declared in the function. You can see MSVC does not show any memory address for 'la'. This is because the local variable is only valid in the function. At the end of the function, everything inside was cleared out of the stack.

 

 

2.1 ) Dynamic memory allocation in a function

 

Then what if you don't what the local variable to disappear even after the demise of the function?. The necessity of the heap memory (dynamic memory allocation) appears here. In other words, you can declare a variable in a local area (like a function), but it does not reclaim even after the function is terminated.

 

Dynamic allocation within a function (MISTAKE1)

#include <iostream>

int ga = 1234;

void function()
{
	int *la = new int(1234);
}

int main()
{
	int ma = 1234;
	int *a = new int(1234);

	function();
}

This is one common mistake that people usually make when they have started learning the heap allocation. This code makes a compilation error as below.

 

You might wonder what it did not work because you thought the variable "la" was declared as a dynamic variable in the Heap. Well, it's 50% correct. As I have mentioned already up there, a pointer to the dynamic variable is allocated in the Stack, not Heap together. Thus, even though the actual variable "la" was allocated in the Heap, the pointer "*la" was in the Stack which was reclaimed as soon as the function was terminated. 

 

Therefore, the pointer "la" which stores the address of the variable "la" must be defined before and is passed to the function. Which means...

 

Dynamic allocation within a function (CORRECT)

#include <iostream>

int ga = 1234;

void function(int*& la)
{
	(*la)++;
}

int main()
{
	int ma = 1234;
	int* a = new int(1234);
	int* la = new int(1234);
	std::cout << *la << std::endl;
	function(la);
	std::cout << *la;
}

 

You might wonder what "*&la" is which is passed to the function. I am not gonna go too deep inside a pointer in this post so just keep in mind that it's important to understand how reference, pointer, and function call - parameter passing in C++. Here is a simple explanation about each reference related operator.

 

 

int *la (Pointer operator *)

: A pointer to the variable "la". The pointer is stored in the Stack. The pointer stores the address of "la" (in the Heap)



&la (Reference operator & (Ampersand))

: The copy of the pointer in the Stack. (IT MAKES A COPY ! NOT REAL !)



*la (Deference operator *)

: The value of "la" stored in the Heap



la

: The address of "la" stored in the Heap



*&la

: The actual address of the pointer (REAL ONE)