Dynamic memory allocation in C++ by breakaleg

So you have just finished your program and you would like to see it at work. Usually you press a combination of keys to compile and run it. But what actually happens when you press those keys ? The compilation process translates your code from C++ to machine language, which is the only language computers “understand”. Then your application is given a certain amount of memory to use, which is divided into three segmentsas follows :

  • Code Segment, in which all the application code is stored
  • Data Segment, that holds the global data
  • Stack Segment, used as a container for local variables and other temporary information.

In addition to these, the operating system also provides an extra amount of memory called Heap.

Now let us recall what a C++ variable consisted of :

  • name, an identifier to recognize the variable
  • type, the set of values it may take
  • scope, the area of code in which it is visible
  • address, the address at which the variable is allocated
  • size, the amount of memory used to store its contents.

Consider the following sequence of code :

     void cookie() 
	{
		int integer_X;
	}

Let us try to figure out what are the attributes of the variable declared inside this function.

  • name : integer_X
  • type : int
  • scope : local, only visible within the cookie function
  • address : a certain address in the stack segment, where the variable is allocated
  • size : 32 bits (depending on the compiler)

What is a pointer then ? We all heard about it, it is the variable declared with a star in front of it… but let us try and put some order in what we know by enumerating its exact attributes :

  • name : an arbitrary identifier
  • type : integer address
  • scope : local or global (depending on the situation)
  • address : the address in the data segment (if it is global) or stack segment (if it is local)
  • size : 32 bits (16 bits for segment + 16 bits for offset of the address it points to)

A dynamic variable is a variable that is stored in the heap, outside the data and stack segments, that may change size during runtime or may even disappear for good. In fact if we do not allocate a certain amount of memory to store its contents, it will never exist.

For example consider the next declaration :

double *dp;

This tells the compiler we have a pointer dp that may hold the starting address of a dynamic variable of type double. However, the dynamic variable does not exist yet – to create it we must use the new operator like this :

dp = new double;

Remember one thing – dynamic variables are never initialized by the compiler. Therefore it is good practice to first assign them a value, or your calculations may not come out right. There are two ways of doing this :

dp = new double; *dp = a; or dp = new double(a);

where a is the initial value. I personally recommend the second version as it is more suggestive and also more compact.

Consider this line of code :

double *dp = new double(3.1415);

The diagram below shows the possible connection between dp and the dynamic variable associated with it, after the previous line is executed.

Now consider you have decided to erase the dynamic variable for good from the heap. Use the delete operator to achieve this :

delete dp;

Notice that while we have freed 8 bytes of memory – which is sizeof(double) – the dppointer was not erased. It still exists in the data or stack segment of the application and we may use it to initialize another dynamic variable.

C++ also provides the possibility to allocate an entire array in the heap, keeping a pointer to its first element. Again we use the new operator but mention the size of the array in square brackets :

int *table; table = new int[100];

This will allocate 100 * sizeof(int) = 100 * 4 = 400bytes of memory and assign the starting address of the memory block to the table pointer.

Arrays allocated in the heap are similar to those allocated in the data or stack segments, so we may access an arbitrary element using the indexing operator []. For example the next loop initializes each element of the array to zero :

for (int i = 0; i < 100; ++i) table[i] = 0;

Bad news is C++ does not provide any method of initializing heap arrays as you would an ordinary dynamic variable, so we have to do it ourselves using a loop similar to the one above. The following line will generate errors at compilation :

table = new int[100](0);

To erase a heap-allocated array we will use the delete operator, but this time add a pair of square brackets so that the compiler can differentiate it from an ordinary dynamic variable.

delete [] table;

All we have learned so far are means of replacing the old malloc() and free() functions in C with their new and delete C++ analogues. The main reason is that they are better alternatives to dynamic memory allocation and also have a more compact syntax. However C pointer arithmetics and other pointer operations are also available in C++.

 

Memory corruption

Memory is said to be corrupted if we try one of the following :

  • Free a dynamic variable that has already been freed
     char *str = new char[100];
     delete [] str; 
     delete [] str;
  • Free a dynamic variable that has not been yet allocated
     char *str;
     delete str;
  • Assign a certain value to a dynamic variable that was never allocated
    char *str; 
    strcpy(str, "error");

The previous three examples will most probably cause the application to crash. However the next two “bugs” are harder to detect :

  • Assign a certain value to a dynamic variable, after it has been freed (this may also affect other data stored in the heap)
     char *str = new char[100]; 
     delete [] str; 
     char *test = new char[100]; 
     strcpy(str, "surprise !");
     cout << test; 
     delete [] test;
  • Access an element with an index out of range
     char *str = new char[30]; 
     str[40] = 'C';

Last but not least, remember it is good practice to test whether allocation was or was not successful before proceeding with the code. The reason for this is that the operating system may run out of heap at some point, or the memory may get too fragmented to allow another allocation.

 char *str = new char[512]; 
 if (str == NULL)
 { // unable to allocate block of memory ! }