8

I know that variadic macros have been added in C99 (and via GNU extensions). I've been wondering if there is a nice alternative in ANSI C.

I've come up with something like this, but it's still kind of awkward:

void log_info(const char *file, int line, const char *fmt, ...)
{
#ifdef DEBUG
    va_list ap;
    char newfmt[1024] = { 0 };
    va_start(ap, fmt);
    sprintf(newfmt, "[INFO] (%s:%d): %s\n", file, line, fmt);
    vfprintf(stderr, newfmt, ap);
    va_end(ap);
#endif
}

So that this can be called like this:

log_info(__FILE__, __LINE__, "info message: %s %d", "helloworld", 12);

There is nothing wrong with this approach, however I'm wondering if there is a nicer way of doing it? Eg. with no need to specify file/line everytime.

I'd appreciate any feedback. :)

Edit: By ANSI C here I mean C89.

Edit: The answer below is fine but I believe given it requires running the printing command twise it may impose some thread-safety issues as it is. Another alternative might be to use define to minimize the typing (also quite ugly):

#define __FL__ __FILE__, __LINE__

and then run the command like:

log_info(__FL__, "info message: %s %d", "helloworld", 12);
M.M
  • 138,810
  • 21
  • 208
  • 365
Alex
  • 948
  • 1
  • 13
  • 17
  • 1
    In what situation could you possibly be writing C with a compiler that doesn't support variadic macros? – Alexis King Dec 26 '14 at 23:40
  • 2
    @Alexis King: Thanks for the response. Say for example when C89 is a requirement by a company standards or a particular project? I know most of the compilers would still compile the code even with -ansi flag and my question is not about why I should care but rather what would/should I do if I am under such constraints but still need the functionality. Its more of a curiosity question, I must admit. – Alex Dec 26 '14 at 23:43
  • 2
    Please use `snprintf(newfmt, sizeof(newfmt), "…", …)` to prevent buffer overruns. Though (on second thoughts), I suppose that if you don't have C99 you may not have `snprintf()` either. That's not quite true — MSVC is mostly C89 but the libraries include `snprintf()`. At least, use `snprintf()` if you possibly can. – Jonathan Leffler Dec 27 '14 at 00:07
  • @Jonathan Leffler: Yes, I totally agree and I would have used snprintf but as you quite correctly pointed out - it's also C99. I doubt though the message would ever be longer then 1024 (not intentionally anyway ;) ). I believe 1024 is a message size limit in syslog as well. – Alex Dec 27 '14 at 00:32
  • 4
    "ANSI C" is a misnomer. It commonly refers to the language described by the 1989 ANSI C standard (which is the same language described by the 1990 ISO C standard), but in fact [ANSI](http://ansi.org/) has adopted each edition of the ISO C standard (1990, 1999, 2011) shortly after its publication. – Keith Thompson Dec 27 '14 at 00:37
  • @KeithThompson: Thanks for the comment. I agree and indeed by ANSI C I meant C89. – Alex Dec 27 '14 at 00:41
  • 1
    You should probably look at the discussion in [C `#define` macro for debug printing](http://stackoverflow.com/questions/1644868/c-define-macro-for-debug-printing/1644898#1644898) – Jonathan Leffler Dec 27 '14 at 02:04
  • @JonathanLeffler: thanks for the link. It's interesting. – Alex Dec 27 '14 at 08:49
  • 1
    The comment "why are you using C89 in ?" is in basically every SO post about C89. The one above was from 2014. Visual Studio did not support C89 until VS2015. I'm still (unfortunately) trying to write C89 code in 2023, but probably won't have to, in a few years (fingers crossed). – Rich Jahn Feb 24 '23 at 14:00
  • @RichJahn Agreed, C projects tend to stick to older versions for eternity. It was all C89 at the place I worked, some subprojects allowed C99, but I believe it was mostly discouraged (just in case - for portability). And we used C++98 + old version of boost almost exclusively for smart pointers back then (and probably still do - left the company by now). :D It's hard to port a rather big and sensitive project to even C++11. Not broken - don't touch it. – Alex Feb 24 '23 at 19:24

2 Answers2

19

It's a little ugly, but a sequence of comma-separated expressions in parentheses can be treated as a single argument.

An example:

#include <stdio.h>
    
#define LOG(args) (printf("LOG: %s:%d ", __FILE__, __LINE__), printf args)
    
int main(void) {
    int n = 42; 
    LOG(("Hello, world\n"));
    LOG(("n = %d\n", n));
    return 0;
}   

The output:

LOG: c.c:6 Hello, world
LOG: c.c:8 n = 42

Note that this requires an extra set of parentheses in the call.

The preprocessed code will look like:

#include <stdio.h>

int main(void)
{   
    int n = 42; 
    (printf("LOG: %s:%d ", "test_variadic.c", 7), printf ("Hello, world\n"));
    (printf("LOG: %s:%d ", "test_variadic.c", 9), printf ("n = %d\n", n));
    return 0;
}
Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • Thanks. It's interesting. – Alex Dec 27 '14 at 00:45
  • 1
    The only caveat with this approach is that you MUST run printf (or any other printing function) twice which I reckon may impose some issues in multi-threaded applications. – Alex Dec 27 '14 at 11:02
  • 1
    @Alex I didn't understand what you meant by 2 printf() calls, when the output shows only one message. But I see that the first printf() call basically prints half the message, then the second prints the rest. I edited the answer to put the preprocessed code. One thing to note, the variable declaration of `int n` after a statement is not C89, and the goal of this is for C89 (because it doesn't support variadic macros). – Rich Jahn Feb 24 '23 at 13:56
5

Here is an even uglier option, but uses single call to printf:

#define PROMPT "[INFO] (%s:%d): "
#define FL __FILE__, LINE__

#define DEBUG0(fmt) printf(PROMPT fmt, FL);
#define DEBUG1(fmt, a1) printf(PROMPT fmt, FL, a1);
#define DEBUG2(fmt, a1, a2) printf(PROMPT fmt, FL, a1, a2);
#define DEBUG3(fmt, a1, a2, a3) printf(PROMPT fmt, FL, a1, a2, a3);

and so on until the most number of arguments that you call the macro with.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • Thanks for this. I firstly thought it's rather awkward but the more I think about it the more I like the solution. The only bad side of it is the need to create a LOT of defines. – Alex Jan 02 '15 at 00:31
  • 2
    @Alex yeah , it's bad to set up but once it's all hidden in a header it's not so bad in your other source files – M.M Jan 02 '15 at 11:26