Previous Table of Contents Next


Figure 6.6 uses string_t to declare parameters for the function new_string. (A similar function, strdup, is available in Microsoft C but not in ANSI C.) With this function, it’s easy to write a correct version of month_name, as shown in Figure 6.7. Like Figure 6.5’s incorrect version, the corrected version returns a character pointer. However, the pointer returned by the corrected version points to memory that remains allocated and that contains the desired month name.

Figure 6.6 new_string Function

string_t function new_string( const string_t val ) {

   /*
   | Allocate and load storage for string val
   |
   | Return pointer or NULL, if error
   */

   string_t p;
   if ( val == NULL ) {
      printf( "Invalid NULL value pointer\n" );
      return NULL;
   }
   p = (string_t) malloc( strlen( val ) + 1 );

   if ( p == NULL ) {
      printf( "No memory for %s\n", val );
      return NULL;
   }
   else {
     strcpy( p, val );
     return p;
   }
 }

Figure 6.7 Corrected month_name Function

char * month_name( const int month ) {

   /*
   | Return month name
   */
 
   char names[10][12] = {
     "January", ... "December" };
   return new_string( names[ month - 1 ] );
}

Amnesia

In large or long-running programs that explicitly allocate memory by calls to C’s malloc memory allocation function, you may try to allocate more memory than is available. If malloc can’t allocate the amount of memory you request, it returns NULL. Always check for a NULL return value after calling malloc. By doing this, you can avoid the problems that occur when you use a NULL pointer. Most C programmers follow this rule — most of the time. But that’s not good enough. Even if you allocate only one byte as the very first statement in a trivial main block (an operation you think can “never” fail), check what malloc returns. The test takes 30 seconds to code and practically no time to be executed, and you’ll never be unpleasantly surprised when the “it couldn’t happen” does.

If a program does much explicit memory allocation, you also need to guard against C’s form of amnesia — unexpected memory loss or memory “leakage.” This quaint term refers to the situation where memory you’ve allocated isn’t available for reuse when you’re done with it.

Figure 6.8 shows how leakage can occur. The first malloc operation allocates a memory block and stores a pointer to it in ptr. After using this memory to hold a character string, the code reuses ptr to point to memory containing a different string. This code will be executed fine, but the memory originally allocated to hold the first string will remain marked “in use,” even though it can’t be referenced or deallocated after the second malloc operation (assuming the pointer value isn’t copied to another pointer variable).

Figure 6.8 How Memory Leakage Occurs

string_t val1 = "abc";
string_t val2 = "xyz";
string_t ptr;

ptr = (string_t) malloc( strlen( val1 ) + 1 );

strcpy( ptr, val1 );
          .
          .
          .
ptr = (string_t) malloc( strlen( val2 ) + 1 );

strcpy( ptr, val2 );
          .
          .
          .

A simple solution is the allocate macro in Figure 6.9, which uses malloc when the pointer is NULL and realloc when it’s not. (In a subsequent chapter, I’ll explain why allocate uses so many parentheses. Simply put, the parentheses prevent unintended changes in the generated code’s evaluation order.) Figure 6.10 shows how to reuse allocated memory with the allocate macro. Note that allocate requires the pointer to be NULL or a value returned by one of the memory allocation functions. Another rule, always initialize pointers in their definitions, partially satisfies this requirement. Although C initializes static pointer variables to NULL, it doesn’t initialize automatic pointer variables. Coding an explicit NULL initializer covers all cases and emphasizes pointer declarations.

Figure 6.9 allocate Macro

/*
| ptr MUST be NULL or address of block allocated
| by calloc, malloc, or realloc functions
|
| The value of allocate is either a valid pointer
| of the specified ptr_type, or NULL if no memory
| can be allocated.
*/

#define allocate( ptr, ptr_type, alloc_size )    \
                                \
( ( ptr ) = ( ptr_type ) ( ( ( ptr ) == NULL ) ? \
          malloc(           ( alloc_size ) ) : \
          realloc( ( ptr ), ( alloc_size ) ) ) )

Figure 6.10 Avoiding Memory Leakage

string_t val1 = "abc";
string_t val2 = "xyz";
string_t ptr  = NULL;

allocate( ptr, string_t, ( strlen( val1 ) + 1 ) );

strcpy( ptr, val1 );
         .
         .
         .
allocate( ptr, string_t, ( strlen( val2 ) + 1 ) );

strcpy( ptr, val2 );
         .
         .
         .


Previous Table of Contents Next