If you want to reduce or prevent errors with wrong formats, your best bet is to use fprintf
, in my opinion. It is a variadic function and therefore unsafe, but it is the way to print stuff in C, so programmers are familiar with it and its shortcomings. If your logging function is just a macro, for example:
enum {Fatal, Error, Warning, Info};
int maxlevel = Error;
#define logmsg(L, ...) if (L > maxlevel); else { \
if (L == Fatal) fprintf(stderr, "Fatal: "); \
if (L == Error) fprintf(stderr, "Error: "); \
\
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
\
if (L == Fatal) exit(1); \
} \
you get the benefit of static analysis, which will warn you about format/type mismatches. in clang/gcc, -Wformat
, which is part of -Wall
, will do that. In Visual Studio, you can use /analyze
. (And you won't generate the va list when logging is suppressed.)
If you roll your own variable-argument function and call vfprintf
, you can detect bad format strings with the clang/gcc format
attribute or the _Printf_format_string_
anotation in Visual Studio.
That said, if all of your variable arguments are of the same type, you can use an array. If your compiler supports C99's compound literals, you can use a macro to convert the variable portion of your argument list into an array.
Say that all printed arguments should be const char *
. (I know that's not what you want, but bear with me.) Then you can achieve a printing function that takes any positive amount of C strings like this:
void put(const char **s)
{
while (*s) {
fputs(*s++, stdout);
}
fputs("\n", stdout);
}
#define put(...) put((const char *[]){__VA_ARGS__, NULL})
And use it like this:
put("Couldn't open \"", fn, "\".");
put("My name is ", (rand() % 2) ? "Igor" : "Tamara", ".");
put("");
You can extend this technique to const void *
. I've hidden the code behind an Ideone link, because using void
will make you lose the type information of the arguments. Your code probably won't crash hard, because the pointers should point somewhere, but you will still invoke undefined behaviour and get garbage output when you use the wrong format.
(In comments to the first answer, Lundin points out that converting an integer type to a pointer is legal. That's not a defect of the shown answer, that's a defect of the language. You could also say puts(54)
and only get a warning.)