C Preprocessor Wizardry: Debugging and Logging
Every time I see a developer manually adding and removing printf("here\n"); statements, I cringe. We have a compiler; let's make it work for us. By using some clever macro tricks, we can create a logging system that is invisible in production builds but incredibly detailed during development.
The Basic DEBUG Macro
The most important trick is the variable-argument macro (though in '98, we're often limited by older compilers, so we use a specific style).
#ifdef DEBUG
#define LOG(msg) fprintf(stderr, "DEBUG: %s at %s:%d\n", msg, __FILE__, __LINE__)
#else
#define LOG(msg)
#endif
The __FILE__ and __LINE__ macros are built-in and tell you exactly where the error occurred. No more guessing which "here" was printed.
Handling Arguments
If you need to print formatted strings, you can use the "double-parentheses" trick to handle multiple arguments in older C89 compilers:
#ifdef DEBUG
#define DBG_PRINT(args) printf args
#else
#define DBG_PRINT(args)
#endif
// Usage:
DBG_PRINT(("Value of x: %d\n", x));
Notice the extra parentheses! The preprocessor sees it as a single argument to the macro, which then expands into a valid printf call.
Compile-Time Assertions
Why wait for a crash? Use macros to catch errors at compile time.
#define CASSERT(predicate, name) \
typedef char assertion_failed_##name[(predicate) ? 1 : -1]
// Usage: ensure integers are 32-bit
CASSERT(sizeof(int) == 4, int_is_not_32_bit);
If the predicate is false, the compiler will try to declare an array with size -1, which is a hard error. This costs exactly zero bytes in your final binary but saves hours of debugging on weird architectures.
Real programmers don't use debuggers; they write code that debugs itself.
Aunimeda provides DevOps engineering and infrastructure services - CI/CD pipelines, containerization, cloud deployments, and monitoring setups.
Contact us to discuss your infrastructure needs. See also: DevOps Services, Custom Software Development