• No results found

Driving LEDs

7.8 Dynamic Memory Allocation

It often happens that a C++ program will use dynamic memory allocation to request and have memory allocated at run-time. Dynamic memory allocation is used very widely in C++ programs and can greatly reduce the size of the executable program. It is especially useful when the actual storage requirements of the program are not known at the time of programming. For example, a program written to process the marks of a class of students will operate on various sized classes, their size unknown at the time of programming.

Memory allocation can be static or dynamic. In the static case, the compiler allocates the required memory at compile time. Programs with statically allocated memory tend to be bigger than programs with dynamically allocated memory. Furthermore, the statically allocated memory is obtained from the data area - an area specially set aside to store data. The memory region used to store program

instructions is known as the code area and is a different region to the data area. In general, the program instructions or code do not change during the life of the program. However, data will change as the executing program operates on it. Temporary data is created and destroyed during program execution in another area named the stack. The stack is a last-in-first-out (LIFO) type queue using a specially allocated region of computer memory. Some examples of temporary data are; parameters passed to a function, local variables declared within a function, and values returned by functions.

In the case of dynamic memory allocation, the requested memory is granted from yet another area named the heap (also known as the free store). Note: memory that has been allocated is not automatically returned back to the heap by the system. It is the programmer’s responsibility to include instructions to return the dynamically allocated memory for other use.

The two operators that manage dynamic memory allocation are shown in Table 7-4. The operator new allocates the requested memory and returns a pointer that points to the beginning of the allocated area. The simplest use of the new operator is shown in the following example:

int *IntPtr; IntPtr = new int;

The same effect can be achieved in one statement as follows:

int *IntPtr = new int;

Table 7-4 Operators used with dynamic memory allocation. Operator Function

new To request memory

delete To free up memory

In this example, we have requested the dynamic allocation of space from the heap for one int type data. We do not have a name for the allocated space, however, the pointer IntPtr knows where it is. Since we know how to manipulate data using pointers, we can use the allocated space as we need. At the end of its use, the memory space must be returned using the delete operator as follows:

delete IntPtr;

This operation does not remove the pointer variable IntPtr. Instead it releases or returns the portion of memory pointed to by IntPtr, thereby making the dynamically allocated integer no longer available. Now the memory previously occupied by that integer is available for any future dynamic memory allocation operations. If the memory was not released using the delete operator, then that memory will not be able to be used during remaining program operation. In this

situation we have created what is known as a memory leak and the computer will have a reduced amount of memory it can use.

A slightly more complex example is now given that requests space for an array of ten integers:

int *IntPtr;

IntPtr = new int[10];

When the allocated memory space is no longer needed, it must be relinquished using the delete operator as follows:

delete IntPtr;

The following example requests space for a two-dimensional array of 100 int type data:

int(*RowPtr[10]);

RowPtr = new int[10][10];

RowPtr is a pointer to a row of 10 elements. The new operator asks for memory to store 10 sets of 10 element arrays of type int. The allocated storage can be freed by using:

delete RowPtr;

Space can be dynamically allocated to class objects as well. To dynamically create aDAC type object, we can use the following statements:

DAC *DACPtr; DACPtr = new DAC;

We do not have a name for the DAC object that has been dynamically created on the heap. However, the pointer DACPtr knows where the object is. The pointer can be used just as efficiently as an object name to manipulate the object.

The allocated space must be deleted by:

delete DACPtr;

In all these memory allocation operations the new operator calls the constructor of the object. Although we did not create a constructor for int type objects, the data typeint has its own constructor. For example, to create space for one int type data which is initialised to 0, the following statements can be used:

int *IntPtr;

IntPtr = new int(0);

or they can be combined into one line as follows:

Similarly, if we want to create a new DAC type object that communicates with the PC using the BASE address 0x3BC instead of 0x378, we use the following statement:

DAC* DACPtr = new DAC(0x3BC);

When the new operator calls the constructor of the DAC class, the BASE address that is specified (in this case 0x3BC) will be passed.

When the object belongs to a class hierarchy, the pointer does not necessarily need to be of the same type – it can be a pointer to one of its base classes. In the case of theDAC class, the following is still valid:

ParallelPort *Ptr; Ptr = new DAC;

The same pointer can even be made to point to a new, yet different object of the same hierarchy. Suppose we had an object type named DAC16Bit further down in the hierarchy. Then, the following statement is allowed:

Ptr = new DAC16Bit;

In Section 8.6 we will be using this concept together with virtual functions to improve our programming.

It is also possible for the dynamic memory allocation operation to fail. This can occur if there is insufficient memory available to allocate. If memory allocation fails, then the pointer returned by the new operator will be set to NULL, a predefined constant. A simple test such as that shown below can be carried out to check if memory allocation has been unsuccessful:

if(Ptr == NULL) {

cout << “Memory allocation failed “; exit(1);

}

The function exit() is a library routine that can be called to terminate the program (if you decide insufficient memory on the heap justifies termination). Another approach is to write the program to cause an exception as described in Section 7.9. Any pointer returned by the new operator can be tested this way before proceeding. The usual practice is to pass an actual argument of 1 to the exit() function. This indicates to the system that runs your application that the program has terminated prematurely.

Typecasting

Wherever permitted, typecasting can be used to convert an existing type to match another type. The application of typecasting to fundamental data types is demonstrated by the following example:

float b = 8.73;

a = (int) b; // a will be 8.

The float type variable b is typecast to match that of a (data type int) and hence its value rounds down to 8. Similarly, pointers can also be typecast as shown in the following example:

int *IntPtr;

IntPtr = (*int) new int[10][10];

The pointer returned by the new operator is type casted to match the pointer type ofIntPtr by using (*int), which can be interpreted as ‘pointer to int’. Note that the new operator returns a pointer to an array of 10 elements (because the row size of the array being created is 10). However, the pointer IntPtr is a pointer to just one int type data.