I have a use for a macro that displays a message on stderr showing the current filename (from __FILE__
) and line number (from __LINE__
), but also allows the use of an optional format and variadic argument list, for custom messages. If a custom message is specified, the compiler must do a fairly decent job of warning about incorrect printf arguments.
It can be called in the following ways, with this arbitrary error code:
LogError(ErrorCode_TypeMismatch); // prints "[filename:lineno] Type Mismatch:"
LogError(ErrorCode_TypeMismatch, "type was %d", type); // prints "[filename:lineno] Type Mismatch: type was 0"
The filename printed is always the basename (i.e. path is stripped).
In addition, it returns the error code value passed in, so that it can be used in the following manner:
result = LogError(ErrorCode_TypeMismatch); // result will be ErrorCode_TypeMismatch
result = LogError(ErrorCode_InvalidName, "name is %s", name); // result will be ErrorCode_InvalidName
At the moment, I have this solution:
#include <stdio.h>
#include <stdarg.h>
// used to make strings from numeric macros
#define XSTRINGY(a) STRINGY(a)
#define STRINGY(a) #a
typedef enum {
ErrorCode_TypeMismatch,
ErrorCode_InvalidName,
// ...
} ErrorCode;
#define LogError(code, ...) Log2(code, __VA_ARGS__)
// a function to simply return the value provided, used below
static inline ErrorCode ReturnErrorCode(ErrorCode code) { return code; }
#define Log2(code, format, ...) \
ReturnErrorCode(code); /* return the code */ \
{ \
const char * fileName = __FILE__; \
const char * lineNum = XSTRINGY(__LINE__); \
const char * shortFileName = strrchr(fileName, '/'); \
const char * errorString = ErrorCode_ToString(code); /* returns string representation of error code, implementation not shown here */ \
DoLog("[%s:%s] %s: " format "\n", shortFileName ? shortFileName + 1 : fileName, lineNum, errorString, ##__VA_ARGS__); \
}
void DoLog(const char * format, ...) __attribute__ ((format (printf, 1, 2)));
void DoLog(const char * format, ...)
{
va_list argp;
va_start(argp, format);
vfprintf(stderr, format, argp);
va_end(argp);
}
This works well, and meets the requirements mentioned previously.
However I'd like to introduce a new requirement - that the LogError macro can be used with a return
statement, for example:
return LogError(ErrorCode_TypeMismatch);
// or
return LogError(ErrorCode_TypeMismatch, "The values are %d, %d", value0, value1);
Unfortunately, due to the way the Log2 macro is written (to return the passed-in value), the processor will execute the return, and the remaining code to format the filename/line number and print it will not be executed. Therefore no output is generated (although the correct value is returned).
I have investigated numerous ideas concerning the comma operator and GNU statement expressions (which I'd like to avoid for portability reasons, although ##__VA_ARGS__
is acceptable) and whereas I have found some useful resources relating to returning values from a macro ("function" macros), I'm not able to find a solution that works for a variadic macro of this kind.
Alternatively, is there a way to detect that the macro has been used with a return
statement and automatically stop the build? The compiler warning of unreachable code doesn't seem to trigger with this macro, and I'm not sure why that is.
__LINE__
) as a string instead of as a decimal number using"%d"
format? – Some programmer dudeLog2
macro, what if it's part of anif
statement body without braces, e.g.if (some_condition) LogError(...);
Only theReturnErrorCode
call (which is really a no-op here) will be in the condition. – Some programmer dudereturn LogError(...)
is also problematic. There was a reason why__LINE__
was converted to a string, but it's lost in time. I can see how printing it with%d
would suffice. – davidA