3

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.

dbush
  • 205,898
  • 23
  • 218
  • 273
Parduz
  • 662
  • 5
  • 22

2 Answers2

5

why not doing it in 3 steps?

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

this does 3 prints, but at least it's simple and does what you want. the do while(0) trick makes sure this is one only block (when using if without braces) and requires semicolon.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
  • *why not doing it in 3 steps?* One reason could be ensuring that a multithreaded process doesn't have log entries from different threads jumbled together. But if the process is single-threaded, that's not applicable. – Andrew Henle Nov 28 '18 at 15:35
  • as much as I wanted to do that in one printf, that sounded difficult. And 3 calls to printf or one call to printf doesn't change a thing thread-wise. you can have mixed lines with 2 concurrent printfs already, unless you use mutexes. – Jean-François Fabre Nov 28 '18 at 15:41
  • Not quite so, @Jean-FrançoisFabre. Output to the same file from individual `fprintf` calls in different threads will not be mixed. See [paragraphs 7.21.2/7-8](http://port70.net/~nsz/c/c11/n1570.html#7.21.2p7) of the standard. But output from *multiple* calls in different threads may be interleaved on a per-call basis. – John Bollinger Nov 28 '18 at 16:27
  • @JohnBollinger The lock requirement you allude to is C11 (and presumably later) and is missing from [the C99 standard](https://port70.net/~nsz/c/c99/n1256.html#7.19.2). There are likely still many running systems out there with C runtime libraries that don't implement that lock. – Andrew Henle Nov 28 '18 at 16:35
2

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;
}
John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • With respect to the title question, note in particular that prepending or appending arguments to `__VA_ARGS__` is simple, provided that you rely upon and abide by the requirement of standard C that it always correspond to at least one actual argument. You can just do it, as my first alternative shows. – John Bollinger Nov 28 '18 at 16:01
  • Thanks! Your first example worked perfectly, and the other parts of your answer are well explained and useful to learn! – Parduz Nov 29 '18 at 10:56
  • About the `##` in `##__VA_ARGS__`, is the GCC way to handle the trailing "," if i use just one argument. I can't find the question from when i picked the original macro, but it is explained also here: [link](https://stackoverflow.com/questions/37206118/va-args-not-swallowing-comma-when-zero-args-under-c99) – Parduz Nov 29 '18 at 11:00
  • I know what the `##` is for, @Parduz. I'm just pointing out that the behavior you describe is an *extension*, not part of standard C, as is calling a variadic macro without at least one variable argument. Only you can decide whether it's important to you that your code will not be portable in this regard. – John Bollinger Nov 29 '18 at 13:57