No More Memory Leaks - fs(C++)

The Implementation

fs is implemented in two pieces: an instrumenting preprocessor, and a collection of library routines. When a user program is processed by the instrumenting preprocessor, calls to the appropriate library routines are inserted into the code. These calls are responsible for keeping fs's data structure consistent with the actual state of the freestore, and also for notifying fs when freestore events of interest are about to take place.

fs works correctly regardless of whether all the source files making up a program are compiled with -f. This allows programmers to continue using object libraries which were not compiled with -f. (However, the fewer source files compiled with -f, the less symbolic information fs will be able to provide during debugging.) fs also works correctly on programs that define their own new and delete operators, both global and class-specific.

The instrumenting preprocessor performs the following source-level transformations on the user code:

  1. Every use of any new operator is augmented with code that updates the data structure to reflect the creation of the new object.

  2. Every definition of any new operator is also augmented with code that updates the data structure to reflect the creation of the new object. Inserting code at both the use and the definition of new operators is necessary since it is possible that either the use or the definition of the operator will not have been compiled with -f. The code at the use and the definition coordinate correctly in case only one or both were instrumented. The uses and definitions of delete operators are similarly instrumented.

The fs object library contains the following publicly accessible functions:

  1. The implementations of the data structure routines called by the code inserted by the instrumenting preprocessor.

  2. The implementations of the functions fs_showall(), fs_mark(), fs_watch(), etc. (i.e., those functions which comprise the external functionality of fs).

  3. Instrumented versions of the default global new and delete operators found in the standard C++ library.

The fs data structure consists of a collection of object descriptors, one descriptor per object in the freestore, together with a mapping from descriptors to main memory addresses and another mapping in the reverse direction. The descriptor for an object contains all the symbolic information about that object: its type, birthplace, whether there is a watchpoint currently set on it, and so forth. The mapping from descriptors to main memory addresses is implemented by a field in the descriptors; the mapping in the reverse direction is implemented by a C++ associative array class. Descriptors and all other objects newed by the fs manager itself are kept in a ``separate'' area of the freestore which is not itself descriptored. This avoids an otherwise infinite regression of descriptors, and it also shields users from seeing objects they are not interested in.

fs is implemented in a way that makes it ``ready-to-go'' immediately upon program load. That is, fs does not rely upon static initializers having been executed in order to work properly. This means that fs correctly manages objects newed during static initialization, and that the fs functions themselves (fs_showall(), etc.) can be called during either initialization or finalization.

Next topic: Conclusion
Previous topic: Program #2: Setting a watchpoint

© 2005 The SCO Group, Inc. All rights reserved.
SCO OpenServer Release 6.0.0 -- 02 June 2005