Appendix Two: Tracing Objects in C

In Chapter 10 we discussed some possible strategies for tracing memory leaks in C++ programs. In the following code we illustrate both localization tracing and object tracing. The program itself is the file count.cpp .

 extern "C" {      #include <stdio.h>      #include <string.h>      #include <stdlib.h>      #include "trace.hpp"    }    #ifdef _OBTRACE_ON      #include "obtrace.hpp"    #endif    #ifdef _OBTRACE_ON      class XClass : private OBTRACE<XClass>    #else      class XClass    #endif    {    public:      XClass() { value=0; }      XClass(int i) { value=i;}      #ifdef _OBTRACE_ON        // method ReportAll -------------------------------------        static void ReportAll() {          OBTRACE<XClass>::ReportAll(stdout,"XClass");        }end ReportAll        // method ReportCount -----------------------------------        static void ReportCount() {          OBTRACE<XClass>::ReportCount(stdout,"XClass");        }end ReportCount      #endif // _OBTRACE_ON    protected:      int value;    };//end class XClass    #ifdef _OBTRACE_ON      class YClass : private OBTRACE<YClass>    #else      class YClass    #endif    {    public:      YClass() { string[0]=' 
 extern "C" { #include <stdio.h> #include <string.h> #include <stdlib.h> #include "trace.hpp" } #ifdef _OBTRACE_ON #include "obtrace.hpp" #endif #ifdef _OBTRACE_ON class XClass : private OBTRACE<XClass> #else class XClass #endif { public: XClass() { value=0; } XClass(int i) { value=i;} #ifdef _OBTRACE_ON // method ReportAll ------------------------------------- static void ReportAll() { OBTRACE<XClass>::ReportAll(stdout,"XClass"); }end ReportAll // method ReportCount ----------------------------------- static void ReportCount() { OBTRACE<XClass>::ReportCount(stdout,"XClass"); }end ReportCount #endif // _OBTRACE_ON protected: int value; };//end class XClass #ifdef _OBTRACE_ON class YClass : private OBTRACE<YClass> #else class YClass #endif { public: YClass() { string[0]='\0'; } YClass(char* p) { strcpy (string,p); } #ifdef _OBTRACE_ON // method ReportAll ------------------------------------- static void ReportAll() { OBTRACE<YClass>::ReportAll(stdout,"YClass"); }//end ReportAll // method ReportCount ----------------------------------- static void ReportCount() { OBTRACE<YClass>::ReportCount( stdout ,"YClass"); }//end ReportCount #endif // _OBTRACE_ON protected: char string[10]; }//end class YClass #ifdef _OBTRACE_ON char* OBTRACE<XClass>::init=NULL; char* OBTRACE<YClass>::init=NULL; #endif XClass ox1(2); YClass oy1("hello"); // function doit1 -------------------------------------- int doit1() { TRACE(doit1) XClass* ox2 = new XClass(12); //delete ox2; RETURN1(1) }//end doit1 // function doit2 --------------------------------------- void doit2() { TRACE(doit2) XClass ox3(3); XClass* ox4 = new XClass(0); doit1(); //delete ox4; YClass* oy2 = new YClass("by"); #ifdef _OBTRACE_ON YClass::ReportCount(); #endif //delete oy2; RETURN }//end doit2 #ifdef _OBTRACE_ON void Report() { XClass::ReportAll(); YClass::ReportAll(); } #endif // function main -------------------------------------------- int main() { TRACE(main) #ifdef _OBTRACE_ON atexit(Report); #endif doit2(); RETURN1(0) }//end main 
'; } YClass(char* p) { strcpy(string,p); } #ifdef _OBTRACE_ON // method ReportAll ------------------------------------- static void ReportAll() { OBTRACE<YClass>::ReportAll(stdout,"YClass"); }//end ReportAll // method ReportCount ----------------------------------- static void ReportCount() { OBTRACE<YClass>::ReportCount(stdout,"YClass"); }//end ReportCount #endif // _OBTRACE_ON protected: char string[10]; }//end class YClass #ifdef _OBTRACE_ON char* OBTRACE<XClass>::init=NULL; char* OBTRACE<YClass>::init=NULL; #endif XClass ox1(2); YClass oy1("hello"); // function doit1 -------------------------------------- int doit1() { TRACE(doit1) XClass* ox2 = new XClass(12); //delete ox2; RETURN1(1) }//end doit1 // function doit2 --------------------------------------- void doit2() { TRACE(doit2) XClass ox3(3); XClass* ox4 = new XClass(0); doit1(); //delete ox4; YClass* oy2 = new YClass("by"); #ifdef _OBTRACE_ON YClass::ReportCount(); #endif //delete oy2; RETURN }//end doit2 #ifdef _OBTRACE_ON void Report() { XClass::ReportAll(); YClass::ReportAll(); } #endif // function main -------------------------------------------- int main() { TRACE(main) #ifdef _OBTRACE_ON atexit(Report); #endif doit2(); RETURN1(0) }//end main

Let us examine what is in the program. Two classes, XClass and YClass , are defined - just simple classes, for we are mainly interested in tracing them. Note that each entry to a function is marked with TRACE( name ) and that each return from a function is RETURN whereas each return with a value of an expression is RETURN1(expression) . Observe also that no ; is used after these "commands". We #included our own header file for tracing, trace.hpp :

 #ifndef _TRACE_HPP    #define _TRACE_HPP    extern "C" {     #include <stdio.h>     #include <string.h>     #include <stdlib.h>    }    #ifdef _TRACE_ON      extern "C" {       extern void POP_TRACE();       extern void PUSH_TRACE(char*);       extern char* REPORT_TRACE(int);      }      #define TRACE(a) PUSH_TRACE(#a);      #define RETURN { POP_TRACE(); return; }      #define RETURN1(a) { POP_TRACE(); return(a); }      #define REPORT() REPORT_TRACE(3)    #else      char* TRACELOC="global";      #define TRACE(a) TRACELOC=#a;      #define RETURN return;      #define RETURN1(a) return(a);      #define REPORT() TRACELOC    #endif    #endif    // _TRACE_HPP 

This means that if our program count.cpp is compiled without defining _TRACE_ON , our tracing will consist simply of setting the global variable TRACELOC to the function's name (thus a call from function A to function B will reset the name to B and on the return to A it will still be B ). RETURN and RETURN1 are each defined as an ordinary return . On the other hand, if our program count.cpp were compiled with a definition of _TRACE_ON , then TRACE would translate to pushing the name onto the stack of function names , RETURN would translate to popping the stack of function names and then plain return , and RETURN1(exp) would translate to popping the stack of function names and then plain return(exp) . These functions are defined in the source file trace.c , which must be compiled and linked with our program.

 #include <stdio.h> #include <string.h> // the function trace stack is a simple linked list of // pointers to function identifiers // the head of the list is the top of the stack struct TRACE_NODE_STRUCT {   char* floc;                      // ptr to function identifier   struct TRACE_NODE_STRUCT* next;  // ptr to next frame }; typedef struct TRACE_NODE_STRUCT TRACE_NODE; static TRACE_NODE* TRACETop = NULL; //ptr to top of the stack /* function PUSH_TRACE ------------------------------------- */ void PUSH_TRACE(char* p)            // push p on the stack {   TRACE_NODE* tnode;   static char glob[]="global";   if (TRACETop==NULL) {     // initialize the stack with "global" identifier     TRACETop=(TRACE_NODE*) malloc(sizeof(TRACE_NODE));     // no recovery needed if allocation failed, this is only     // used in debugging, not in production     if (TRACETop==NULL) {       printf("PUSH_TRACE: memory allocation error\n");       exit(1);     }     TRACETop->floc = glob;     TRACETop->next=NULL;   }   // now create the node for p   tnode = (TRACE_NODE*) malloc(sizeof(TRACE_NODE));   // no recovery needed if allocation failed, this is only   // used in debugging, not in production   if (tnode==NULL) {     printf("PUSH_TRACE: memory allocation error\n");     exit(1);   }   tnode->floc=p;   tnode->next = TRACETop;  // insert fnode as the 1st in the list   TRACETop=tnode;          // point TRACETop to the first node }/*end PUSH_TRACE*/ /* function POP_TRACE -------------------------------------- */ void POP_TRACE()    // remove the op of the stack {   TRACE_NODE* tnode;   tnode = TRACETop;   TRACETop = tnode->next;   free(tnode); }/*end POP_TRACE*/ /* report 'depth' top entries from the stack in the form    fun1:fun2:fun3:....:funk meaning fun1 called from fun2    that was called from fun3 ... that was called from funk    where k = depth    The calling path may be up to 100 characters, if longer    it is truncated */ /* function REPORT_TRACE ------------------------------------ */ char* REPORT_TRACE(int depth) {   int i, length, j;   TRACE_NODE* tnode;   static char buf[100];   if (TRACETop==NULL) {   // stack not initialized yet, so we     strcpy(buf,"global"); // are still in the 'global' area     return buf;   }   /* peek at the depth top entries on the stack, but do not      go over 100 chars and do not go over the bottom of the      stack */   sprintf(buf,"%s",TRACETop->floc);   length = strlen(buf);  // length of the string so far    for(i=1, tnode=TRACETop->next;                          tnode!=NULL && i<depth;                                    i++,tnode=tnode->next) {      j = strlen(tnode->floc);  // length of what we want to add      if (length+j+1 < 100) {   // total length is ok        sprintf(buf+length,":%s",tnode->floc);        length += j+1;      }else                     // it would be too long        break;    }    return buf; }/* end RPORT_TRACE */ 

The stack in trace.c is as simple as possible - a linked list, with the top of the stack as the head of the list. The location is reported in the form fun1:fun2:...:funK , meaning function fun1 called from fun2 that itself was called from fun3 .... The depth K of this reporting is controlled in trace.hpp by the preprocessing directive

 #define REPORT() REPORT_TRACE(3) 

when _TRACE_ON is defined; in our example, K =3.

The classes for object counting, tracing, and reporting are defined in the header file obtrace.hpp , which is #included for compilation if _OBTRACE_ON is defined and otherwise not. Note all the places in our program that are related to object tracing - they are all conditionally included based on the flag _OBTRACE_ON . Besides the #inclusion of obtrace.hpp we need a different class header for each class involved (in our example, for XClass and YClass ), and each class must include the reporting functions ReportAll() and ReportCount() and, most importantly, the definition and initialization of the static part of each tracing class,

 char* OBTRACE<XClass>::init=NULL; 

and

 char* OBTRACE<YClass>::init=NULL; 

The method ReportCount() will report the total number of objects at the moment of its invocation; ReportAll() will report all undeallocated objects and where they were created.

We will be playing with several variations, so we include a simple Makefile (in this example we are using a public domain GNU C compiler gcc and a public domain GNU C++ compiler g++ under Solaris operating system). The Makefile:

 CFLAGS=    count: count.o trace.o            g++ -o count count.o trace.o $(CFLAGS)    count.o: count.cpp trace.hpp obtrace.hpp            g++ -c -D_TRACE_ON -D_OBTRACE_ON count.cpp $(CFLAGS)    count1: count1.o            g++ -o count1 count1.o $(CFLAGS)    count1.o: count.cpp trace.hpp obtrace.hpp            g++ -c -D_OBTRACE_ON count.cpp $(CFLAGS)            mv count.o count1.o    count2: count2.o trace.o            g++ -o count2 count2.o trace.o $(CFLAGS)    count2.o: count.cpp trace.hpp obtrace.hpp            g++ -c -D_TRACE_ON count.cpp $(CFLAGS)            mv count.o count2.o    count3: count3.o            g++ -o count3 count3.o $(CFLAGS)    count3.o: count.cpp trace.hpp obtrace.hpp            g++ -c count.cpp $(CFLAGS)            mv count.o count3.o    trace.o: trace.c            gcc -c trace.c $(CFLAGS) 

Let us first make count - it is the form with both localization tracing and object tracing on. Executing the program yields the following output:

 [doit2:main:global] number of objects of class YClass = 2 undeallocated object of class XClass created in doit1:doit2:main undeallocated object of class XClass created in doit2:main:global undeallocated object of class XClass created in global undeallocated object of class YClass created in doit2:main:global undeallocated object of class YClass created in global 

The first line is produced by call to YClass::ReportCount() doit2() called from main() and the next three lines by XClass::ReportAll() invoked by the atexit registered function Report() when the program exits. The last two lines are produced by YClass::ReportAll() invoked by Report() . We tried to keep the reporting simple and so it is writing only to standard output (not every operating system keeps standard files open and accessible past exit() , so it may not be executable in this form on your particular machine). Let us check the output to see whether it is correct. When YClass::ReportCount() is invoked there are two YClass objects, oy1 and *oy2 . The "undeallocated object of class XClass created in doit1:doit2:main" is *ox2 (note the commenting out of the delete). The "undeallocated object of class XClass created in doit2:main:global" is *ox4 (again its delete is blocked) - ox3 was deleted automatically when it went out of scope. The "undeallocated object of class XClass created in global" is the global object ox1 . The "undeallocated object of class YClass created in doit2:main:global" is *oy2 (its delete is also commented out), and finally the "undeallocated object of class YClass created in global" is the global object oy1 . Let us uncomment all the commented deletes, recompile, and execute again; correctly we obtain

 [doit2:main:global] number of objects of class YClass = 2    undeallocated object of class XClass created in global    undeallocated object of class YClass created in global 

Commenting the deletes again and recompiling our program as count1 , which keeps the object tracing on but turns off the localization tracing, produces the output

 [doit2] number of objects of class YClass = 2    undeallocated object of class XClass created in doit1    undeallocated object of class XClass created in doit2    undeallocated object of class XClass created in global    undeallocated object of class YClass created in doit1    undeallocated object of class YClass created in global 

which we know is correct from our previous analysis - notice the "flat" localization reporting. Also notice in the Makefile that for count1 we did not link our program with trace.o . If we uncomment the deletes, we get the same correct response with "flat" localization. Now we compile our program as count2 , which keeps the localization tracing on but turns the object tracing off (again we must link our program with trace.o as we did for count ); this time we get no output because there is no reporting. The reader is encouraged to play with the localization tracing and reporting. Compiling our program as count3 turns off both the localization tracing and the object tracing; this time our program is again not linked with trace.o and there is no output. There is practically no overhead, and the program behaves almost as if we never programmed it to do localization or object tracing.



Memory as a Programming Concept in C and C++
Memory as a Programming Concept in C and C++
ISBN: 0521520436
EAN: 2147483647
Year: 2003
Pages: 64

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net