Overloading Macros
How do I overload macros you ask? Well, you can’t! But that would be a short post so I’ll show something else you can do.
Imagine working with a code base that uses a function call for a specific purpose, for instance AllocateMemory( Heap h, int size ) is used to do all your allocations. One day, you decide that you want to do some memory tracking and would like to print out where all the allocations happen from. Since you don’t want to change how this function is called in your entire code base, you go ahead and modify the actual function to do something like this:
void* AllocateMemory( Heap h, int size ) {
#if defined( MEMORY_TRACKING )
printf( "%s:%d: Alloc %d bytes on heap %d", __FILE__, __LINE__, size, h );
#endif
// do normal alloc here
}
This would work just fine and it would require only a quick modification to one function and a define to be set in order to get this primitive memory tracking working. Only it wouldn’t give you much useful information since __FILE__ and __LINE__ will always give you the same values. When these two tokens are replaced by the preprocessor, it will aways be in the AllocateMemory function, probably somewhere in a memory manager class. It won’t tell you where the allocation comes from. Ideally what you’d want is for the preprocessor to replace the call to AllocateMemory with a call that contains the __FILE__ and __LINE__ variables as arguments. You don’t want to change every instance of AllocateMemory in the entire code, so you decide to use macros.
#if defined( MEMORY_TRACKING )
void* _AllocateMemory( Heap h, int size, const char* file, int line );
#define AllocateMemory( h, size ) \
_AllocateMemory( h, size, __FILE__, __LINE__ );
#else
void* AllocateMemory( Heap h, int size );
#endif
There you go. When the tracking define is used, rename the core function and give it its new signature which includes the file name and the line number. Define a macro with the function’s original name which catches the calls and forwards them to the function, appending the preprocessor variables as arguments. Since the macro will be expanded in place, __FILE__ and __LINE__ will correctly contain the filename and line number of the AllocateMemory() call.
Now to the actual point. What if the original AllocateMemory function was overloaded? What if in addition to the version above there’s a second version with the signature AllocateMemory( int alignment, int size )? This would obviously work when calling the two functions but replacing them with a macro will cause compile errors since macros can’t be overloaded. Turns out there’s an easy way around this.
#if defined( MEMORY_TRACKING )
void* _AllocateMemory( Heap h, int size, const char* file, int line );
void* _AllocateMemory( int alignment, int size, const char* file, int line );
#define AllocateMemory( ... ) \
_AllocateMemory( __VA_ARGS__, __FILE__, __LINE__ );
#else
void* AllocateMemory( Heap h, int size );
void* AllocateMemory( int alignment, int size );
#endif
Above we replaced the tracking macro with a variadic one. This uses the ellipsis to indicate that the macro can receive zero or more arguments as input. The __VA_ARGS__ variable expands to the sequence of symbols that were passed in between the parentheses of the above macro. Since these arguments get inserted just like you’d have typed them, the _AllocateMemory() function can have any signature and it would compile correctly.
Not really overloaded macros, but it gets things done. The example in this post was off the top of my head. For a memory tracking system it’s worth it to spend the time and write some solid code. You won’t regret it when the leaks and corruptions start showing up.