Managing memory more effectively using dynamic allocation
Programmers generally deal with five areas of memory: global namespace, registers, code space, stack, and the free store. When an array is initialized, the number of elements has to be defined. This leads to lots of memory problems. Most of the time, not all elements that we allocated are used, and sometimes we need more elements. To help overcome this problem, C++ facilitates memory allocation while an .exe
file is running by using the free store.
The free store is a large area of memory that can be used to store data, and is sometimes referred to as the heap. We can request some space on the free store, and it will give us an address that we can use to store data. We need to keep that address in a pointer. The free store is not cleaned up until your program ends. It is the programmer's responsibility to free any free store memory used by their program.
The advantage of the free store is that there is no need to preallocate all variables. We can decide at runtime when more memory is needed. The memory is reserved and remains available until it is explicitly freed. If memory is reserved while in a function, it is still available when control returns from that function. This is a much better way of coding than global variables. Only functions that have access to the pointer can access the data stored in memory, and it provides a tightly controlled interface to that data.
For this recipe, you will need a Windows machine with a working copy of Visual Studio.
In this recipe, we will see how easy it is to use dynamic allocation. In games, most of the memory is allocated dynamically at runtime as we are never sure how much memory we should assign. Assigning an arbitrary amount of memory may result in less memory or memory wastage:
- Open Visual Studio.
- Create a new C++ project.
- Add a source file called
main.cpp
or anything that you want to name the source file. - Add the following lines of code:
You can allocate memory to the free store using the new
keyword; new
is followed by the type of the variable you want to allocate. This allows the compiler to know how much memory will need to be allocated. In our example, we have used string. The new
keyword returns a memory address. This memory address is assigned to a pointer, sNameOfGuns
. We must assign the address to a pointer, otherwise the address will be lost. The format for using the new
operator is datatype * pointer = new datatype
. So in our example, we have used sNameOfGuns = new string[iNumberofGuns]
. If the new allocation fails, it will return a null pointer. We should always check whether the pointer allocation has been successful; otherwise we will try to access a part of the memory that has not been allocated and we may get an error from the compiler, as shown in the following screenshot, and your application will crash:
When you are finished with the memory, you must call delete on the pointer. Delete returns the memory to the free store. Remember that the pointer is a local variable. Where the function that the pointer is declared in goes out of scope, the memory on the free store is not automatically deallocated. The main difference between static and dynamic memory is that the creation/deletion of static memory is handled automatically, whereas dynamic memory must be created and destroyed by the programmer.
The delete[]
operator signals to the compiler that it needs to free an array. If you leave the brackets off, only the first element in the array will be deleted. This will create a memory leak. Memory leaks are really bad as it means there are memory spaces that have not been deallocated. Remember, memory is a finite space, so eventually you are going to run into trouble.
When we use delete[]
, how does the compiler know that it has to free n number of strings from the memory? The runtime system stores the number of items somewhere it can be retrieved only if you know the pointer sNameOfGuns
. There are two popular techniques that do this. Both of these are used by commercial compilers, both have tradeoffs, and neither are perfect:
- Technique 1:
Over-allocate the array and put the number of items just to the left of the first element. This is the faster of the two techniques, but is more sensitive to the problem of the programmer saying delete sNameOfGuns
, instead of delete[] sNameOfGuns
.
- Technique 2:
Use an associative array with the pointer as a key and the number of items as the value. This is the slower of the two techniques, but is less sensitive to the problem of the programmer saying delete sNameOfGuns
, instead of delete[] sNameOfGuns
.
We can also use a tool called VLD to check for memory leaks.
After the setup has downloaded, install VLD on your system. This may or may not set up the VC++ directories correctly. If it doesn't, do it manually by right-clicking on the project page and adding the directory of VLD to the field called Include Directories, as shown in the following figure:
After setting up the directories, add the header file <vld.h>
in your source file. After you execute your application and exit it, your output window will now show whether there are any memory leaks in your application.
Understanding the error messages
When using the debug build, you may notice the following values in memory during debugging:
0xCCCCCCCC
: This refers to values being allocated on the stack, but not yet initialized.0xCDCDCDCD
: This means memory has been allocated in the heap, but it is not yet initialized (clean memory).0xDDDDDDDD
: This means memory has been released from the heap (dead memory).0xFEEEFEEE
: This refers to values being deallocated from the free store.0xFDFDFDFD
: "No man's land" fences, which are placed at the boundary of heap memory in debug mode. These should never be overwritten, and if they are, it probably means the program is trying to access memory at an index outside of an array's max size.