Game Boy Advance Dev: Logging to the Console
16 points
4 hours ago
| 1 comment
| mattgreer.dev
| HN
scraft
1 hour ago
[-]
Small defensive suggestion: I’d usually define both the enabled and disabled versions as statement-like macros:

  #ifdef MGBALOG
  #define mgbalog(level, format, ...) \
      do { _mgbalog(level, format, ##__VA_ARGS__); } while (0)
  #else
  #define mgbalog(level, format, ...) \
      do { } while (0)
  #endif
For the exact usage shown here, "mgbalog(...);" will mostly work with the empty replacement-list version, since it just leaves a bare ";". So this is not a guaranteed bug.

The reason I prefer the "do { } while (0)" form is that it keeps the macro structurally equivalent to a single statement in both debug and release builds. With the empty disabled macro, this:

  if (failed)
      mgbalog(ERROR, "failed");
becomes:

  if (failed)
      ;
That is valid C, but it can trigger empty-body / suspicious-semicolon warnings, for example GCC’s "-Wempty-body", which matters if the project builds with "-Wextra" or warnings-as-errors.

It also means the enabled case is already set up correctly if the macro later grows beyond a single function call. For example, if you later add another statement to the macro:

  #define LOG(x) printf("log\n"); printf("%s\n", x)

  if (failed)
      LOG("failed");
  else
      recover();
then the expansion breaks because the "else" no longer attaches to the intended "if".

So in this specific case the empty macro is probably fine for ordinary "mgbalog(...);" calls, but wrapping both versions in "do { } while (0)" is the more conventional and future-proof C macro shape.

reply