I recently needed a macro with variable arguments that work on all mainstream compilers. I use gcc only on Linux, on the other platforms I want to use the „native“ compiler that belongs to the OS. Let’s start with Linux.
Linux
If you’re inside the gnu/gcc world, things are easy. This is what everybody tries first:
1 2 3 4 5 6 7 8 9 10 11 | #include "stdio.h" /* I use "stdio.h" because my blog software cant't handle angle brackets inside a preformated text blog ... :( */ #define PRINTERROR(x, ...) fprintf(stderr, "ERRORMESSAGE = " f "\n" , __VA_ARGS__); int main(void) { PRINTERROR("Hello %s!", "world"); return 0; } |
The problems start, if you want to use the macro without variable arguments.
9 | PRINTERROR("Hello world!"); |
gcc will not accept the code, because it resolves the macro to
9 | fprintf(stderr, "ERRORMESSAGE = " "Hello world!" "\n" ,); |
(note the comma)
test.c: In function ‘main’: test.c:9: error: expected expression before ‘)’ token |
The gcc workaround for this kind of a situation is:
5 | #define PRINTERROR(x, s...) fprintf(stderr, "ERRORMESSAGE = " f "\n" , ## s); |
If „s“ is null, this erases the comma to the left of the „##“ in the macro definition. The resulting expansion is:
9 | fprintf(stderr, "ERRORMESSAGE = " "Hello world!" "\n" ); |
The comma is gone, the code compiles. Great.
HP-UX
However, this approach fails on HP-UX
cc -o test test.c cc: "test.c", line 9: error 1000: Unexpected symbol: ")". |
I found the solution for this problem in the HP-docs:
you must insert a space to the left of the comma to make it clear to the preprocessor that the comma is the left operand of the „##“ operator.
Yes. A space. Left of the comma. Sigh….
The definition that works on Linux and HP’s cc -AC99
compiler looks like this:
5 | #define PRINTERROR(x, s...) fprintf(stderr, "ERRORMESSAGE = " x "\n" , ## s); |
Solaris
Solaris c89 compiles the above definition with a warning:
"test.c", line 9: warning: argument mismatch |
Solaris c99 compiler is very strict and allows
5 | #define FOO(...) printf(__VA_ARGS__) |
only. The documentation points out, that it is possible to use #__VA_ARGS__
which seems to be similar to GNU’s/HP’s ## Operator:
5 | #define PRINTERROR(x, ...) fprintf(stderr, "ERRORMESSAGE = " x "\n" , # __VA_ARGS__ ); |
Ok, the code compiles, but calling PRINTERROR results in a „argument mismatch“-warning. I found no other solution than disabling the warning. Dirty but it works:
5 6 7 | #define PRINTERROR(x, ...) printf(stderr, "ERRORMESSAGE = " x "\n" , # __VA_ARGS__ ); /* optional: disables "warning: argument mismatch" */ #pragma error_messages (off, E_ARGUEMENT_MISMATCH) |
If course it is bad to hide warnings, but if your code is multi-platform, you can use very strict compiler settings on another platform to make the warnings visible at least there.
AIX
The original AIX-compiler (cc) does not complain, but c89 fails with
"test.c", line 3.16: 1506-1128 (S) The GNU variable argument identifier "s" of macro "FOO" is not permitted in the current langlvl mode. "test.c", line 7.29: 1506-041 (E) The invocation of macro FOO contains fewer arguments than are required by the macro definition. |
There is a langlvl #pragma to switch the compiler language level. All levels that allow implementation-specific language extensions are sufficient. I used extc99
which allows to use the c99 compiler. Simply add this to the beginning of your sourcefile:
#pragma langlvl (extc99) |
The final result
This is the result of some hours browsing the compiler docs. The macro works on all c99-compilers mentioned above and allows zero arguments:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #ifdef _AIX #pragma langlvl (extc99) #endif #include "stdio.h" #ifdef _SOLARIS #define PRINTERROR(x, ...) fprintf(stderr, "ERRORMESSAGE = " x "\n" , # __VA_ARGS__ ); /* optional: disables "warning: argument mismatch" */ #pragma error_messages (off, E_ARGUEMENT_MISMATCH) #else #define PRINTERROR(x, s...) fprintf(stderr, "ERRORMESSAGE = " x "\n" , ## s); #endif int main(void) { PRINTERROR("NO PARAM"); PRINTERROR("Param: %s", "foo"); return 0; } |
Of course you have to use the compiler switch -D_AIX
, -D_SOLARIS
or -D_HPUX
on the apropriate platform. Let me know what do you think and how it could be done better.
Update: André suggests this solution (see his comment):
void PRINTERROR_verbose(const char * file, int line, int level, char *format, ...) { int size; char *cp; va_list args; if (level < = 2) { va_start(args, format); size = vsnprintf(NULL, 0, format, args) + 1; va_end(args); va_start(args, format); cp = (char *)calloc(size, sizeof(char)); if (cp != NULL && vsnprintf(cp, size, format, args) > 0) { /* Ausgabe von cp hier via syslog, stderr, stdout, etc. */ /* oder cp noch mal umformatieren fuer Sonderzeichen und/oder */ /* zusaetzliche Zeitangabe */ /* bei Debuglevel 1 (z.B. ERROR) wird dann noch etwas mehr angezeigt */ if (level < = 1) { fprintf(stderr, "\tFile '%s' in line %i\n", file, line); } } va_end(args); free(cp); } } #define PRINTERROR(...) PRINTERROR_verbose(__FILE__, __LINE__, __VA_ARGS__) |