Finding memory leaks in testHash

The testHash program provided with Purify shows you how to find and correct memory leaks.

If you have not yet built and run the testHash example program, do so now. For instructions, click

The memory address information in the example is compiler and system dependent. You will see different output and debugging information depending on whether you are running the example on Solaris, HP-UX, or IRIX.

  1. Run the Purify'd version of testHash.

% testHash.pure


 

  1. Expand the memory leaked summary, then expand the MLK error messages.

    Purify reports two MLK errors: a 12-byte leak and a 2-byte leak, each occurring multiple times. This results in a total of 182 bytes of leaked memory.

     

  2. Expand the putHash function. Notice that each leaked block is a hashEntry or its associated key.

Finding the source of memory leaks

To track down a memory leak, you need to know how the memory blocks are used and where they are stored, and you need to understand where they are lost.

Run the program again and look for a section of code that loses the last pointer to a block of memory. The last pointer to a block of memory can be lost if:

To understand this memory leak message, you need to review how to store and use hashEntry type memory blocks. It is possible that a pointer to a hashEntry is being lost when a new one is inserted in putHash, or when the old one in remHash is removed. It is also possible that they are being lost when they are removed in delHashTable.

Notice that only 13 blocks are lost, even though many more are added or removed as the test program runs. Therefore, do not expect to see a leak on every hashEntry that is added, removed, or deleted.

Using your debugger to set breakpoints

  1. Start your debugger and set breakpoints in the testHash program after calls to PutHash, RemHash, and DelHashTable.

  % dbx testHash.pure
 
 (dbx) file testHash.c
  
(dbx) stop at 99
  
(1) stop at "testHash.c":99
  
(dbx) stop at 183
  
(2) stop at "testHash.c":183
  
(dbx) stop at 194
  
(3) stop at "testHash.c":194
  
(dbx) run

Note
: For an explanation of how to use purify_xdb, click

  % purify_xdb testHash.pure
  
>v testHash.c
  >b 99
  Overall breakpoints state: ACTIVE
  Added:
   1: count: 1 Active testPutHash: 99: }
  >b 183
  
Overall breakpoints state: ACTIVE
  Added:
   2: count: 1 Active testRemHash: 183: }
  >b 194
  
Overall breakpoints state: ACTIVE
  Added:
   3: count: 1 Active testDelHashTable: 194: }
  >r
  
Starting process 796: "testHash.pure"
  
.
  
.
  
.

Notes:

  1. Rerun the testhash program.

Running purify_new_leaks

  1. Each time the program stops (at the end of testPutHash, testRemHash, and testDelHashTable), call purify_new_leaks.

    By calling purify_new_leaks, you can get a message showing the new leaks that occurred since the last call to purify_new_leaks. If no leaks are reported, continue running the program to the next breakpoint.

  stopped in testPutHash at line 99 in file "testHash.c"
     
99 }
  (dbx) print purify_new_leaks()
  
.
  .
  .
  purify_new_leaks() = 0
  (dbx) cont
  
.
  .
  .
  stopped in testPutHash at line 194 in file "testHash.c"
 
    194 }
 
 (dbx) print purify_new_leaks()
  
.
  
.
  
.
  
purify_new_leaks() = 182
    
    

Note:

  1. Use your debugger to take a closer look at the last hashEntry that was leaked.

   (dbx) print *((hashEntry *)0x4f400)
  
*(hashEntry *) 0x4f400 = {
    
    key   = 0x83328 "49"
    
    value = 0xf7fff524
    
    next  = (nil)
  }
  (dbx)

The debugger confirms that this is the last
hashEntry in the list because its next field is NULL. Now look at the source code for delHashTable, looking for errors relating to the edge case of handling the last member of the list.

  void delHashTable(ht)
    
   hashtable* ht;
  {
        int index;
        hashEntry* last_entry;
  
      hashEntry* entry;
  
      for (index = 0; index < HASHTABLE_SIZE; index++) {
   
        for (last_entry = NULL, entry = ht[index];
       
           entry;
    
              last_entry = entry, entry = entry->next) {
    
           if (last_entry) {
    
              free(last_entry->key);
    
              free(last_entry);
    
           }
    
       }
    
    
    
    
        }
        free(ht);
  }
    
    

Notice that the inner loop is off by one. The loop deallocates the entry prior to the present position, thereby failing to deallocate the last entry in the list. This means that the pointer to the last hashEntry in each list is being dropped. This happens because the loop is terminated prematurely while last_entry still points to an entry, and the memory is never freed.

Correcting the error

  1. To correct this error, add a free for the last hashEntry and its key at the end of the loop.

  void delHashTable(ht)
 
       hashtable* ht;
  {
  int index;
  hashEntry* last_entry;
  hashEntry* entry;
  for (index = 0; index < HASHTABLE_SIZE; index++) {
     for (last_entry = NULL, entry = ht[index];
          entry;

          last_entry = entry, entry = entry->next) {
        if (last_entry) {
          free(last_entry->key);
          free(last_entry);
        }
      }
      if (last_entry) {        /* last_entry left dangling */
        free(last_entry->key); /* classic off-by-one error */
        free(last_entry);      /* free the last one */
   
   }
   
}
   free(ht);
  }

  1. Recompile the program and run it again.

This time Purify should indicate that there are no leaks. You have successfully fixed the problem.