Fun Tricks with your C++ Compiler and Preprocessor

Top  Previous  Next

Some interesting tricks are used in the JrDebugLogger files to make the debugging syntax look right, but still make it possible to easily compile-out all of the code for minimal performance impact.

 

Hidden C++ class constructors in order to support __FILE__ information

The idea to use C++ class constructors to emulate calling a global function, in order to grab information about the current source code being parsed was suggested by Paul Mclachlan, http://www.codeproject.com/debug/location_trace.asp#xx386001xx.  Basically the code expands what looks like a global function call into an object constructor (which takes as arguments the preprocessor macros), followed by an operator() call, which actually does the work.  The macros passed as arguments are stored in the constructed class for subsequent use:

#define JrDebugInternalPrintf (JrDebug( __FILE__ , __LINE__ , __TIMESTAMP__ , __FUNCTION__ ))

inline void JrDebug::operator()(const char* format, ... ) {;};

 

Indirect operations through a conditionally defined if block

In order to ensure that the JrDebugLogging code can be removed from compilation without any overhead, while not requireing the user to comment out the debug logging statements, all debug printing statements are redirected through an if statement that conditionally compiled to if(0) when the debug code is not compiled in.  This will cause the c++ compiler to remove that code during any optimization, since it knows it will never be executed.  When compiled in, the if statement used is if (enabled), which allows you to compile-in the debug logging code with a trivial impact on overhead when it is disabled (simple cmp and jz).

 

Trick to avoid problems with streams and temporary objects

JrDebugLogger provides both printf style debug logging statements and stream io style statements.  In order to get the stream style io mechanism to work properly, and interesting kludge was required.  The mechanism described above for invoking a class constructor in order to save state and then using the operator() does not quite work in this case.  The problem is that this mechanism generates a temporary object and then uses that object for the operation, deleting it immediately after use.  However, as described by Doug Harrison, http://groups.google.com/groups?selm=qrvkjvk67b6539rpngrjc6cs20v162cup8%404ax.com, this can cause a very subtle problem.  Some important stream io operators (for example those that work with std::string), are global non-member operators, which will refuse to be invoked on temporary objects (since the global operators expect non-const objects).  So in order to provide full stream io support, we use a kludge to overcome this issue:

#define JrDebugInternalStream (JrDebugs( __FILE__ , __LINE__ ,  __TIMESTAMP__ , __FUNCTION__ )())

inline JrDebugs& JrDebugs::operator()() {return *this;};

The last () in the define, which invokes the operator() is the key because it causes the temporary object to return a non-const stream object, which then can be used with all standard non-member stream operators.

 

Using automatic scoping of local objects to report block timing

In order to make it easy to report the timing of blocks of code, a trick is used to create an (anonymous) local object on the stack, which announces its creation, and which will be automatically deleted when it goes out of scope, reporting the end of the time interval when it is destructed.  This falls under the general heading of RAII (Resource Allocation is Initialization).  We employ a few macro tricks in order to anonymously name the local object so user doesnt have to make up a dummy name.  We use the line number of the file to name the object:

 #define JrDebugStringify2args(x,y) x ## y

 #define JrDebugInternalDebugBlock2If(Nstr, F,L,T,U) JrDebugBlockObject JrDebugStringify2args(jrdscopedobj_, L);   if (JrDebugEnabled) JrDebugStringify2args(jrdscopedobj_, L).init(Nstr,F,L,T,U);

 #define JrDebugInternalDebugBlock(Nstr) JrDebugInternalDebugBlock2If(Nstr, __FILE__ , __LINE__ ,  __TIMESTAMP__ , __FUNCTION__)

We also use a very lightweight wrapper object for the object that keeps timing information, so that if debugging code is compiled in but disabled, only the lightweight wrapper is constructed on the stack, not the full heavier object, which is only created above via init() if debugging is enabled.

 

Self-registering objects and calling 'functions' from global scope

In order to let a user register test functions from within each file that contains them, without requiring that they modify their main procedure, some tricks are necesary.  When the user calls (in global score) this macro:

JrDebugRegisterTestFunction("Simple Unit Test",MyUnitTest,"simple");

A global object is created, whose constructor code actually creates a new test object (this insures that this function macro can be called from any scope and wont die on exit of scope), and registers it with the global JrDebug class.  There is a problem however, which is that c++ doesn't guaruntee the order of creation of global variables, so we aren't sure if the global JrDebug object has been created yet that we need to register with.  To handle this, before registering the test function, we create a helper class whose constructor checks if the global singleton JrDebug has been created and initialized yet; if not creates it before returning.

class JrDebugSingletonInsurer {

 bool globalkill;

public:

 JrDebugSingletonInsurer(bool doinit=true) {if (GlobalJrDebugp==NULL) {GlobalJrDebugp = new JrDebug(doinit);}; globalkill=doinit;};

 ~JrDebugSingletonInsurer() {if (GlobalJrDebugp!=NULL && globalkill==true) delete GlobalJrDebugp; GlobalJrDebugp=NULL; globalkill=false;};

};

In this way, we insure that the the global singleton JrDebug object is always ready to receive other globally instantiated object, regardless of c++ order of construction.  The helper object (we call it an insurer) has at least one global instantiation (invoked with doinit=true) but may be instantiated dynamically anywhere else in the code where we need to insure that the global JrDebug singleton has been created (with doinit=false).  The globalkill/doinit flag makes sure that on program exit, when the global insurance object is destroyed, it will take responsibility for destroying the global JrDebug object, even if it wasn't the creator of it.

 

Trouble with macros inside nested if statements

#define DEBUGSTREAMMACRO if (debugenabled) DebugStreamObject

If the user does:

if (something) DEBUGSTREAMACRO << "hello"; else foobar();

The expansion is:

if (something) if (debugenabled) DebugStreamObject << "hello"; else foobar();

The result is that the last else becomes attached to the macro's if clause, instead of the original (something) clause, ie:

if (something) { if (debugenabled) DebugStreamObject << "hello"; else foobar(); }

The solution is to modify the macro:

#define DEBUGSTREAMMACRO if (!debugenabled) ; else DebugStreamObject

Which expands to:

if (something) if (!debugenabled) ; else DebugStreamObject << "hello"; else foobar();

Which is equivelent to:

if (something) {if (!debugenabled) ; else DebugStreamObject << "hello";} else foobar();