Introduction to C / C++ Programming
Advanced Pointers

Dynamic Memory Allocation

Allocation Functions

There are a number of valuable functions for dynamically allocating memory ( from the heap ) as programs run. These functions all require #include <stdlib.h>

Deallocation

When the memory is no longer needed, it should be returned to the memory heap.

Common Mistakes with Dynamic Memory Allocation

There are a number of errors that occur commonly when using dynamic memory:

  1. Dangling Pointers:
    • If dynamic memory is freed up using free, but the pointer variable is not reset back to NULL, then the pointer still contains the address of where the dynamic memory used to be. Using ( following ) this pointer later can have a wide variety of consequences, depending on what if anything is done with that block of memory in the meantime.
    • The problem can be especially difficult to find if multiple pointers point to the same place. Even though one of the pointers may have been reset to NULL, the other pointers could be left dangling.
  2. Memory Leaks:
    • If dynamic memory is continuously allocated without ever being freed back up again, the system will eventually run out of memory, which will result in either a stack overflow error or a failure of the dynamic memory allocation calls.
    • This type of error occurs most commonly when dynamic memory is allocated in a loop.
    • Always make sure to free up dynamic memory when it is no longer being used.
    • Always check the return value of malloc, calloc, or realloc to verify that it did not return NULL.
    • Memory leaks can be extremely hard to find, because they often don't result in program failures until the program runs for a sufficiently long time, so unless the program tests run long enough, the problems won't be detected.
  3. Double deallocation
    • If an attempt is made to deallocate memory pointed to by a dangling pointer, it can cause all sorts of problems, particularly if that memory has since been reallocated for other purposes.
  4. Use of uninitialized pointers
    • As covered earlier, any variable that is not initialized starts out with a random unknown value, which will generally be different from one run of the program to another. If the variable in question is a pointer, then the problem can be especially difficult when the uninitialized pointer is used later on in the program.
      • In a best case, the pointer will point to inaccessable memory, and a "segmentation fault" will occur, stopping the program.
      • If the bad pointer points to a valid location within your data or worse your code, then any number of problems can occur, possibly at some later time in the program.

Dynamically Allocating Strings

One common use of dynamic memory allocation is for the storage of strings, in an array that is just big enough to hold the necessary data:

    	char line[ 200 ];
        char *name = NULL;

        printf( "Please enter your name: " );
        fgets( buffer, 200, stdin );
        
        name = ( char * ) malloc( strlen( buffer ) + 1 );  // +1 to hold the null byte

        if( !name ) {
        	fprintf( stderr, "Error - malloc failed!\n";
            exit( -1 );
        }

        strcpy( name, buffer );

Dynamically Allocating Arrays

Another very common use of dynamic memory allocation is for allocating arrays. ( Particularly in the days before C99 allowed you to allocate arrays after the program was already executing. )

    	int nData = -1;
        double *data = NULL;

		do { 
            printf( "How many data items would you like to store? " );
            scanf( "%d", &nData );
        } while( nData < 1 );
        
        data = ( double * ) malloc( nData * sizeof( double ) );  // Uninitialized, random values
        //data = ( double * ) calloc( nData, sizeof( double ) );  // Cleared to all zeros

        if( !data ) {
        	fprintf( stderr, "Error - malloc failed!\n";
            exit( -1 );
        }

Linked Lists

Abstract Data Types

An Abstract Data Type, ADT, is defined in terms of the operations that can be performed on or with the data type, independent of any specific implementation of the data type. A linked list is one of the simplest and most common ADTs used in computer science, consisting of a series of nodes ( links ) each of which points to the next node in the list, like links in a chain.

Operations Performed on a ( Linked ) List Data Type:

Define a list node

A list node must contain at a minimum a pointer to the next node in the list. In order to be effective it must also contain some data, and depending on the specific kind of linked list, it may or may not contain additional pointers.

In C a list node is typically defined as a struct, e.g.:

      	struct StudentLink {
        	int nClasses;
            double gpa;
            char *name; // will point to adynamically allocated string
            struct StudentLink * next;
        };
At first glance it appears that the above struct contains a circular reference, with one StudentLink contained within another. That is not the case, however, because next is a pointer, not a full struct. ( Note that all pointers are the same size, regardless of what they point to, so there is no question here about how much space to allocate for the pointer or for the struct. )

Create an empty list

A null pointer of the appropriate type is effectively an empty list:

      	struct StudentLink *head = NULL;

Create a list node.

List nodes can be declared as ordinary auto variables, but more commonly they are allocated dynamically:

      	// Possible but not common
      	// struct StudentLink newGuy = { 3, 4.0, "George P. Burdell", NULL }; 
        
        // Much more common
        struct StudentLink * newStudent = NULL;/
        newStudent = ( struct newStudent * ) malloc( sizeof( struct newStudent ) );
        if( newStudent ) {
        	 newStudent->nClasses = 0;
            newStudent->gpa = 0.0;
            newStudent->name = ( char * ) malloc( strlen( buffer ) + 1 );
            if( !newStudent->name ) {
        	     fprintf( stderr, "Error - malloc failed!\n";
                exit( -1 );
            }
            strcpy( newStudent->name, buffer );
        } else {
        	 fprintf( stderr, "Error - malloc failed!\n";
            exit( -1 );
        }

Insert a new data item ( node )

Adding a link into an existing list requires setting the next pointer in the new link to point into the list, and then setting a pointer from the list to point to the new link. There are two basic ways to do this, depending on circumstances:

  1. Beginning of the list - Inserting a new link at the beginning of a list is easiest, because you just have to work with the new link and the list head:
  2.       	newStudent->next = head; // Remember newStudent is a pointer, not a struct
           head = newStudent; // Pointer assigned to pointer
           // head now points to newStudent, which then points to the rest of the list.
  3. Maintaining an ordered list - Inserting a new link into the middle of an existing list is harder, because you generally need it to go to a specific spot, , which means you first need to find the node in the list just before the spot where the new node needs to go. Generally we write a function to do this:
  4.       struct StudentLink *previous = findPrevious( head, newStudent->name );
          if( !previous ) { // There is a link that belongs before the new student
              newStudent->next = previous->next;
              previous->next = newStudent;
          } else { // This student belongs at the beginning of the list
          	   newStudent->next = head; // Same code as above
              head = newStudent; 
          }

Remove a data item ( node )

Find a data item ( node )

Determine the length of a list

Destroy a list

 

More Complex Data Structures, e.g. Advanced Linked Lists, Trees, etc.

Pointers to Pointers ( to Pointers to . . . )

	  void movePointerToNextNode( struct node ** pointer ) {
    
    	 node *p = *pointer;
        p = p->next;
        *pointer = p;
        return;
     }
	   node *current = NULL;
      
      // code omitted that makes current point into a list
      
      movePointerToNextNode( &current ); // Pointer variable passed by address

Function Pointers ( Pointers to Functions )