Search code examples
cmacrosprintfvariadic-macros

Variadic Macro calling fprintf: how to add arguments to __VA_ARGS__?


I have two macros:

 #define LogFunction(str)       fprintf(stdout, "%s: %s\n",__FUNCTION__,(str))
 #define LogPrintf(f_, ...)     fprintf(stdout, (f_), ##__VA_ARGS__)

So i can use them this way:

void MyFunction()
{
    int N=4;
    LogFunction("START");      // Outputs "MyFunction: START"
    LogPrintf("N=%d\n", N);    // Outputs "N=4"
}

What I would like to change is to

  1. add FUNCTION at the start of the LogPrintf as it is in LogFunction
  2. add "\n" at the end of the LogPrintf without having to remember to put it in myself

so in the end i could have just one macro for my outputs.

I've tried to understand if Appending to __VA_ARGS__ could've been useful, but i admit that i've not understood if it is related to my case :(

Thanks.


Solution

  • If you're willing to rely on the first argument to LogPrintf being a string literal, then you should be able to use string concatenation to achieve your objective:

    // Assumes f_ always corresponds to a string literal:
    #define LogPrintf(f_, ...)    fprintf(stdout, "%s: " f_ "\n", __FUNCTION__, ##__VA_ARGS__)
    

    Note, however, that in standard C, the LogPrintf macro requires at least two arguments, and the ## has no place. I keep it here only because you use it in your original code.

    If you must accept format string expressions other than string literals, however, then your simplest alternative is to perform multiple I/O calls, as another answer also suggests:

    #define LogPrintf(f_, ...)    do {         \
        fprintf(stdout, "%s: ", __FUNCTION__); \
        fprintf(stdout, (f_), ##__VA_ARGS__);  \
        fputc('\n', stdout);                   \
    } while (0)
    

    Note that in this case, the macro expands to a statement (sans trailing semicolon), whereas in the other, the macro expands to an expression. If you want the return value(s) of any of the I/O functions, then you'll have to make special provisions for that in this case.

    If that doesn't work for you either, then the ultimate alternative is to write and use a helper function, as was suggested in comments:

    #define LogPrintf(f_, ...)    log_printf_impl(stdout, __FUNCTION__, (f_), ##__VA_ARGS__)
    
    int log_printf_impl(FILE *f, const char *func, const char *fmt, ...) {
        static const char prefix[] = "%s: ";
        size_t flen = strlen(fmt);
        va_list args;
        int result = -1;
        char *aug_fmt = malloc(sizeof(prefix) + strlen(fmt) + 1);
    
        if (aug_fmt) {
            va_start(args, fmt);
            sprintf(aug_fmt, "%s%s\n", prefix, fmt);
            result = vfprintf(f, aug_fmt, func, args);
            va_end(args);
            free(aug_fmt);
        }
    
        return result;
    }