Computer Science 010

Lecture Notes

Dynamic Memory Management

Memory leaks involving structs

Consider the following declarations:

typedef struct {
  char *opponent;
  int pointsFor;
  int pointsAgainst;
} Score;
   
Score *seasonRecord;

seasonRecord is declared as a pointer to score values. Assume that I don;t know how many games there are initially, so I declare it as a pointer and will initialize it later when I know the number of scores:

seasonRecord = (Score *)malloc (numGames * sizeof (Score));

This has allocated enough memory to hold numGames values of type Score.

The Score struct itself contains a pointer. When I create a new Score value, I should copy the opponent name into this score structure. Before doing that copy, I will need to allocate memory to hold the opponent's name:

void initScore (Score *s, char *otherTeam, int weScored, int theyScored) {
  s->opponent = (char *)malloc (strlen (otherTeam) + 1);
  strcpy (s->opponent, otherTeam);
  s->pointsFor = weScored;
  s->pointsAgainst = theyScored;
}

When I allocated the array earlier, I actually allocated memory for the Score values. So, I do not need to allocate that memory. Instead, I need to pass in the address of the Score struct that I should initialize. The first field of the Score struct is a char *. Thus, there is only memory to hold a pointer here. Now, I must allocate memory for the other team's name and then copy that value in. The remaining 2 fields are not pointers, so no memory allocation is needed and I can just assign the values.

Now, suppose at some later time I want to free this seasonRecord array. If I say:

free (seasonRecord);

I will free the memory that was returned by the malloc call whose result was assigned to seasonRecord. Even while freeing memory, I have just created a memory leak! The problem is that every time I called initScore, I did a malloc to hold the team's name. Before freeing the seasonRecord, I should walk the array and free the name associated with each opponent:

void freeSeasonRecord (Score *seasonRecord, int numGames) {
  int i = 0;
  for (; i < numGames; i++) {
    free (seasonRecord[i].opponent);
  }
  free (seasonRecord);
}

Dangling Pointers

A dangling pointer is a pointer that has a legal address but the contents of the address is not the value you expect to be there. This is slightly different than the uninitialized pointer problem. In the case of dangling pointers, the pointer pointed to a reasonable value at one time, but the memory was deallocated and has now been reused for a different purpose. Now, if you use the pointer, you will get an unexpected value.

Problem 1: Pointing to memory that was automatically deallocated

A dangling pointer can occur because you have a pointer to memory that was automatically freed when you exited a function:

char *buffer;
   
char *fillBuffer () {
  char line[1000];
  gets (line);
  return line;
}
   
buffer = fillBuffer();

Here we declare a pointer called buffer outside of the function but we do not allocate memory for the string pointed to. Inside fillBuffer, we declare an array called line that is large enough to hold 1000 characters. The C runtime system automatically allocates enough memory for this array. We now use the gets function to fill this array with the next line input by the user. Finally, we assign the address of the array to our buffer variable in the return statement. buffer now points to this allocated array. On return from the function, the memory that was automatically allocated to hold the line string is now deallocated. For a while, the value in buffer might appear to be ok, but eventually, the memory will be reallocated for another purpose and modified. At that point the value in buffer will change mysteriously.

The solution in this case is to be sure that we never use the & address-of operator to try to pass an address from a local variable to a global variable and also avoid returning arrays. Fortunately, the compiler gives us a warning in this case:

warning: function returns address of local variable

Problem 2: Pointing to memory that was deallocated with free

A very similar problem arises if you have two pointers to a chunk of memory and then free the memory while the other pointer continues to use the memory:

  char *s1;
  char *s2;
  char *s3;

  /* Allocate memory for s1's string and copy in a value. */
  s1 = malloc (1000);
  strcpy (s1, "Williams");

  /* Allow s2 and s1 to refer to the same chunk of memory. */
  s2 = s1;
  printf ("Before freeing s1, s2 = %s at %d\n", s2, s2);

  /* Free the memory even though s2 is still using it. */
  free (s1);
  s1 = NULL;

  /* s2 isn't changed because the memory has not been re-used. */
  printf ("After freeing s1, s2 = %s at %d\n", s2, s2);

  /* Allocate another chunk of memory.  Chances are good it will
     reuse the memory just freed. */
  s3 = malloc (1000);

  /* Copy a different value into this chunk of memory. */
  strcpy (s3, "Amherst");

  /* If the memory was reused, s3 and s1 will have the same address
     and value. */
  printf ("s3 = %s is at %d\n", s3, s3);
  printf ("After setting s3, s2 = %s at %d\n", s2, s2);

Here we declare 3 strings, with no memory allocated to any of them. We allocate memory, assign it to s1, and copy in a string value. Now, we set s2 to point to this same chunk of memory. Now, free the chunk of memory. s2 is still using this address, though. If we are unlucky, the next malloc call will return the same address and the subsequent strcpy will overwrite Williams with Amherst, modifying s2!

This problem is very, very difficult to avoid in general. You just must be very careful when you assign one pointer variable to another and then use free. Also, unless the variable through which you are doing the free is about to be reclaimed automatically because a function is ending, or you are about to assign another value to the variable, you should set the variable to NULL. Incorrect use of the variable will then result in a segmentation fault, which is easier to find and fix then a dangling pointer.

Problem 3: Deleting the same memory more than once

This problem is really just a variation on the previous. If you have a dangling pointer and you call free with that pointer bizarre things might happen. If the memory has not been reallocated for another purpose, it's possible that nothing bad will happen. If it has been reallocated, however, you will now free memory that is being used for a different purpose. Instead of just inadvertently changing another variable's value, you have now turned that variable into a dangling pointer! Fortunately, the implementation of malloc on FreeBSD gives us a warning at runtime if we do this. It says:

in free(): warning: page is already free.

Another mistake I might make is to call free with an address that was not returned by malloc. For example, I might have used pointer arithmetic and now have a pointer pointing into the middle of an array that was dynamically allocated. Again, FreeBSD's implementation is helpful and reports:

in free(): warning: modified (chunk-) pointer.

What if I try to free memory that was not dynamically allocated? In this case, malloc knows the range of addresses that it allocates memory from and sees that it is out of range. In this case, the error is:

in free(): warning: junk pointer, too high to make sense.

These errors are reported by our implementation of malloc and are not required to be reported by C. If you run a C program on a different OS, it might not report errors and misbehave instead. It certainly seems that this implementation of malloc is much more paranoid than most!


Return to CS 010 Home Page