Computer Science 010

Lecture Notes

Dynamic Memory Management

A Safe Substitue for gets

Most of you are justifiably concerned about using gets for input since you get the warning that it is not safe to call. Indeed, as you saw in one of the exercises last week, if you use gets for input, it makes it extremely simple for a user to crash your program! All the user needs to do is type in a longer string than your array can handle and your program will almost certainly crash or at least behave unexpectedly at some point in the future.

Here's a safe substitute (assuming you use it properly!):

  char input[10];
  printf ("enter some input. First 9 characters will be saved.\n");
  fgets (input, 10, stdin);
  printf ("%s\n", input);

Instead of calling gets, call fgets. fgets takes three parameters: the array to read the input into, the maximum number of characters to read, and the input stream to read from. If the value of the second parameter is the size of the array passed as the first parameter, the user will no longer be able to crash your program by entering a very long string. If you want to get input from the user, you will always pass stdin as the third parameter. stdin means "standard input" which normally menas that it will accept input from the keyboard.

Later, when we talk about file manipulation, you'll see other possible values for the 3rd parameter.

fgets returns when the earliest of the following is true:

If fgets returns because newline was entered, the newline will be the last character in the returned string. This is different from gets, which did not include the newline.

If fgets returns because the maximum number of characters was reached and we call fgets again, it will read more characters from the same line. If this isn't what you want, you'll need to read and throw away those extra characters yourself. Here is a function, readline, that abstracts from fgest a bit. It only reads from standard input, it does not include the newline in the returned string, and it throws away extra characters at the end of the line.

char *readline (char *a, int size) {
  /* The number of characters actually read. */
  int charsread;

  char *retval;

  /* Get characters from standard input, stopping when size -1 characters are entered
     or newline is enetered, whichever comes first. */
  retval = fgets (a, size, stdin);

  /* If an error occured on input, return. */
  if (retval == NULL) {
    return NULL;
  }

  /* Find out how many characters were input. */
  charsread = strlen (a);

  /* If the last character input was newline.  Throw it out by replacing it with
     null */
  if (a[charsread-1] == '\n') {
    a [charsread-1] = '\0';
  }

  /* If the last character read was not newline, read and throw away the remaining
     characters on the line. */
  else {
    while (getchar() != '\n') {
    }
  }

  retun retval;
}

Memory Deallocation

As mentioned earlier C frees some memory automatically. Specifically, variables that are local to a function have memory allocated when the function begins execution and have memory freed when the function completes execution as follows:

int increment (int i) {
  int i2;
  i2 = i1 + 1;
  return i2;
}
   
int j = increment (100);

When increment is called, memory is allocated for the parameter i large enough to hold an integer. The value 100 is copied into this memory to pass the parameter value in. Memory is also allocated for the local variable i2 large enough to hold an integer. The value 101 is copied into i2. On return the value in i2 is copied into j, the variable being assigned to by increment. The memory allocated for i and i2 is deallocated. Since none of the types involved are pointer types, the memory allocation and deallocation is done automatically by the C runtime system.

In contrast, consider the following function to duplicate an array:

char *duplicate (char *s) {
  char *s2 = malloc (strlen (s) + 1);
  strcpy (s2, s);
  return s2;
}
   
char *school = duplicate ("Williams");
...
free (school);

When duplicate is called, memory is allocated for s that is large enough to hold a pointer. The address of "Williams" is copied into this memory to pass the parameter value in. Memory is also allocated for the local variable s2 that is large enough to hold a pointer. The initializer of s2 calls malloc to allocate memory that is large enough to hold the string pointed to by the parameter. The strcpy function is then called to copy the string into this new memory. strcpy assumes that memory allocation is done before it is called. The return statement copies the pointer into the school pointer value being assigned to by duplicate. The memory used by s1 and s2 to hold the pointers is automatically deallocated, but the memory used to hold the copied string is not deallocated. The string continues to exist and is now pointed to by the school variable. Eventually, we decide that we no longer need the string. At that point we call the free function to deallocate the memory. The memory management system remembers how much memory was allocated when a pointer is returned by malloc and it deallocates that much memory when we call free. A later call to malloc may now return this pointer again allowing the memory to be recycled.

  char *s1;

  s1 = malloc (1000);
  strcpy (s1, "Williams");
  /* Print the string in s1 as well as the address where the string is. */
  printf ("%s at address %d\n", s1, s1);

  /* Free the memory so it can be used again. */
  free (s1);

  s1 = malloc (1000);
  strcpy (s1, "Amherst");

  /* Since we freed the original memory, malloc might very well, 
     give us the same address again here. */
  printf ("%s at address %d\n", s1, s1);

There are several dangers associated with pointers (in addition to using uninitialized pointers as discussed in an earlier class) and memory management that are described in the rest of these notes.

Memory Leaks

One problem that might occur is that you might forget to free memory that you are no longer using. The good news is that memory leaks will rarely cause your program to crash or exhibit bugs of any kind. The bad news is that the size of your program will grow continuously over time. For small programs this is not normally a problem, but it is for large programs. Large programs may become very slow and might eventually crash if they run out of memory that can be allocated.

  char *s1;

  s1 = malloc (1000);
  strcpy (s1, "Williams");
  /* Print the string in s1 as well as the address where the string is. */
  printf ("%s at address %d\n", s1, s1);

  s1 = malloc (1000);
  strcpy (s1, "Amherst");

  /* This time we did not call free.  malloc must return a different
     address in the second call, but notice that there is no way that
     our program can access the first chunk of memory anymore.  We
     can't even tell C to free that memory anymore! */
  printf ("%s at address %d\n", s1, s1);

It is good practice to free memory religiously even in small programs so that you get used to doing it and avoid memory leaks when you start to write large programs where it really matters.

The trick here is to make sure that every chunk of memory allocated with malloc is eventually freed with free. If you have a single variable referencing that memory, it is pretty simple to do it correctly. If you have multiple variables pointed to the same chunk of memory, you need to be sure that you only free the memory when you no longer need the value through any of the variables. Failure to do this correctly leads to the next problem....dangling pointers.

More Unix Utilities

We have already seen a number of Unix utilities: You have also use Unix pipes to link the output of one program to the input of another Here are some additinal utilities (See the man page for fill details):

Return to CS 010 Home Page