Overview
Teaching: 15 min
Exercises: 15 minQuestions
How do I allocate memory?
How do I free memory?
Objectives
Learn to work with dynamic memory
C and C++ are fairly low level languages and as such you can manage your program’s memory yourself. This means that you can have great control over how much memory is used and when. With careful planning managing memory yourself can lead to lower memory usage and more performant code than in other languages which manage this for you. However, care must be take to avoid issues such as:
In C you use malloc
to allocate memory, but in C++ you should use new
. It is possible to use malloc
but some additional steps need to be taken to ensure it works properly with C++ classes.
There are two flavors of new
, the new
keyword, or the array version new []
. The first will declare a new variable/object of the given type like:
#include <iostream>
int main(){
int* a=new int;
*a=10;
std::cout<<"a="<<*a<<"\n";
}
Here we declare a pointer to an int
, a
and allocate enough memory using new
to hold an integer.
However, if we wanted enough memory to hold a number of integers we would create a dynamic array with the new[]
. Lets do that now.
$ cp hello.cpp memory.cpp
$ nano memory.cpp
and then modify it to look like the following:
#include <iostream>
int main(){
int size=10;
int* a=new int[size];
a[9]=15;
std::cout<<"a[9]="<<a[9]<<"\n";
}
$ g++ memory.cpp -o memory
$ ./memory
a[9]=15
In this example we allocate enough memory to hold 10 integers and the pointer a
points to the beginning of this memory. The []
operator is used to index into a given part of this memory and allow one to access and modify that memory.
Memory allocated using new
or new[]
is not freed if pointers that point to it go out of scope. This is different than memory that is allocated in statements like int a[10];
. This can allow great flexibility in how the memory is used. By passing a pointer to different parts of your code it can access and modify that memory without having to copy the data. This can have huge performance improvements and avoids wasted memory. However it does mean that the programmer is responsible for keeping track of the memory and freeing it.
Scope
Remember that in C and C++ the
{
and}
define scope. The below code example:int a=2; { int a=10; } std::cout<<"a="<<a<<"\n";
will print out
a=2
, since inside the open{
and close}
brackets a newa
is created and initialized to 10, but only has scope within that code block. This goes for any code block, for loops, functions, if blocks etc.
Generally it is a good idea to match every new
and new[]
allocation with a matching delete
or delete[]
. In the above program we haven’t done this, and in this simple situation it actually doesn’t cause any problems. When our program ends that memory is freed up, however if that new were inside a large loop and we didn’t take special care to track memory, would could end up with a leak of unusable memory that won’t be freed until the program ends, which in severe cases could eventually causes us to run out of memory before our program finishes executing.
To deallocate or free memory in C
the free
function is used, however, in C++ you should use the delete
operator instead. It also comes in two flavors delete
and delete[]
to match the two flavors of new
and new[]
. Lets free the memory we allocated above.
$ nano memory.cpp
#include <iostream>
int main(){
int size=10;
int* a=new int[size];
a[9]=15;
std::cout<<"a[9]="<<a[9]<<"\n";
delete[] a;
}
$ g++ memory.cpp -o memory
$ ./memory
a[9]=15
Because we used the new[]
version we must use the delete[]
version to match it. Notice that we don’t have to tell it the size of the array, this information is tracked for us.
Debug tools
It is possible to use a tool to help find bugs in your code. These tools usually let you inspect the values of variables in your code, step through execution line by line, set beak points in your code to stop execution and let you examine variables. They will also help in detecting some memory issues like accessing memory your program doesn’t have access to (e.g. Segmentation faults) and stop on the line where it occurs.
gdb
One such tool is
gdb
the GNU debugger. It can be used from the command line. To use it you first need to compile your code including the-g
option.$ g++ -g memory.cpp -o memory
Then run the newly created executable using
gdb
.$ gdb memory
You can the type
help
to show information about how to usegdb
. For more details see: gdb documentation.Valgrind
Is a collection of useful tools which allows checking for memory errors, performance profiling, and more. See valgrind.org for more information.
How much memory is allocated?
int* createIntArray(int size){ int* a=new int[size]; return a; } int main(){ int* array; for(int i=0;i<10;++i){ array=createIntArray(10); } }
what is the maximum amount of memory allocated at any point during the running of this application if
sizeof(int)==4
(in other words anint
is 4 bytes).
400 bytes
10 bytes
40 bytes
100 bytes
Solution
YES: 10x from the loop, 10x from the array allocation, 4 bytes from the size of an
int
. This is because we do not free ordelete
any memory during this program.NO: we are allocating enough memory to hold 10
int
s each time the function is called. An integer is 4 bytes so a single call to the function will allocate 40 bytes. However, we are also calling the function 10 times without deleting any memory.NO: 40 bytes are allocated every time the function is called, but it is called 10 times and we do not delete any memory.
NO: note that when we use
new
it allocates an amount of memory based the number of a particular data type, in this case anint
. This is different than the Cmalloc
function which takes a size inbytes
to allocate. In this casesize
is the size of the array, or the number ofint
s in the array.
How much memory is deallocated?
int* createIntArray(int size){ int* a=new int[size]; return a; } int main(){ int* array; for(int i=0;i<10;++i){ array=createIntArray(10); } delete[] array; }
how much memory was deallocated if
sizeof(int)==4
(in other words anint
is 4 bytes).
400 bytes
4 bytes
40 bytes
Solution
NO: 400 bytes are allocated, but that isn’t how many bytes are deallocated. The
delete
keyword is used outside the loop and will delete the last array that is allocated, but will miss the other 9 arrays that are allocated previously.NO: the
delete
keyword when used in the formdelete[]
will delete an entire array allocated with keywordnew[]
and not just a single element of that array.yes: 40 bytes are deleted, which is only the last array allocated. That means that 9x40=360 bytes have not been deallocated.
Where does the memory go?
void createIntArray(int size){ int* a=new int[size]; return; } int main(){ createIntArray(10); }
What happens to the memory that is allocated inside the
createIntArray
function after the function is called and execution returns to the main function?
At the end of the function
a
goes out of scope and the memory is deleted.The memory remains allocated however there is no way to access or
delete
it as the reference to it,a
, has gone out of scope.Since the reference count to the memory is now zero, at some later time the garbage collector will free the allocated memory.
Solution
NO: the compiler does not assume that you would want to delete allocated memory when a reference to it goes out of scope. What would happen if there was another reference to it and the memory was deleted when the first reference to it goes out of scope?
YES:
NO: C and C++ do not have garbage collectors and don’t do reference counting for you.
Key Points
Every use of
new
/new []
should be match by adelete
/delete []
.The amount of dynamic memory allocated can be determined at runtime.
Need to be careful about incorrect access and especially modification of memory.