1

I have defined a clunky macro to give myself a reduced probability of making mistakes in a project with a large amount of unique logging situations...

#define BANG(...) {fprintf(stderr, "in %s(), ", __func__);\
                   fprintf(stderr, ##__VA_ARGS__);}

And I can call this quite successfully like this...

BANG("Something went wrong\n");        

But the following (obviously) breaks in frightening ways...

if(emulatorExitCode == 1)
    BANG("emulatorExitCode: %d (an error)\n", emulatorExitCode);        
else
    everythingIsWonderful = 1;

I know the reason of course, it's the semicolon I accidentally left on the end of the call to BANG();

That rogue semicolon causes the if/else construct to separate into two fragments, disassociated from eachother. Here's the compiler error...

main.c:74:3: error: ‘else’ without a previous ‘if’

So far so good, the workaround is to have -Wall -Werror -Wextra -Wpedantic in my compiler arguments so I cannot accidentally cause a horrible mess of my logic.

And as long as I always leave out the semicolon on the end of the BANG() macro calls I'll be OK because the macro definition uses braces around the two fprintf(); calls. But I'm worried that other people maintaining my code later will not realise that this is a problem (it's really not obvious) and get in a mess.

Question

Can this macro #define be rewritten to either reduce the danger posed by the problematic semicolon OR can it be rewritten to roll these two calls to fprintf into a single call? The latter is complicated by the fact that it's variadic AND has the famously head-scratchy printf-family semantics to deal with.

  • See [Why use apparently meaningless `do … while` and `if … else` statements in macros?](https://stackoverflow.com/questions/154136/why-use-apparently-meaningless-do-while-and-if-else-statements-in-macros) I'm tempted to close this as a duplicate of that. – Jonathan Leffler Jun 06 '17 at 23:04
  • @JonathanLeffler, fair point, be my guest :) –  Jun 07 '17 at 06:43

1 Answers1

2

A multi-statement should be wrapped in a do..while block to avoid exactly this type of issue.

#define BANG(...) do{ \
                     fprintf(stderr, "in %s(), ", __func__);\
                     fprintf(stderr, ##__VA_ARGS__);\
                  } while (0)

So your if block then expands to:

if(emulatorExitCode == 1)
    do{
      fprintf(stderr, "in %s(), ", __func__);   // ignoring the __func__ expansion
      fprintf(stderr, "emulatorExitCode: %d (an error)\n", emulatorExitCode);
    } while (0);
else
    everythingIsWonderful = 1;

The fact that you're using a variadic macro doesn't matter in this case.

dbush
  • 205,898
  • 23
  • 218
  • 273