8

I have found no way to merge the first printf into the second:

unsigned get_time_now(void) {return 1;}
#define DEBUG_PRINT 1
#define debug_tcprintf(fmt, ...) do { \
            if (DEBUG_PRINT) { \
                unsigned p_time_now = get_time_now(); \
                printf ("%u ms ", p_time_now); \
                printf(fmt, __VA_ARGS__); \
            } \
        } while (0)

I need to get this done to get an atomic debug_tcprintf. The macro above has been taken from this Stack Overflow question.

I am doing code in XC that runs on an XMOS multi-logical-core processor. It compiles XC, C and C++, but the code example is from a C code part. It is similar for XC except that it has a timer defined in the language.

If it's not possible to merge the two in one printf, an option may perhaps be to create a string and use sprintf instead? I'd rather not, since such an array might easily overflow.

Woodrow Barlow
  • 8,477
  • 3
  • 48
  • 86
Øyvind Teig
  • 139
  • 1
  • 9

1 Answers1

4

You need to use string concatenation and token pasting. Notice in the snippet below there is no comma after the first string literal -- this is intentional.

#define debug_tcprintf(fmt, ...) do { \
    if (DEBUG_PRINT_HTTPD) { \
        unsigned p_time_now = get_time_now (); \
        printf ("%u ms " fmt, p_time_now, ##__VA_ARGS__); \
    } \
} while (0)

The string concatenation allows you to prepend the "%u ms " portion onto the supplied format string. Token pasting (the ## operator) accounts for the possibility of your macro being invoked with or without additional variadic arguments (beyond just the format string).

This only works if you call the macro with a string literal as the format string.


Addendum: The way token-pasting is used in this example is actually a gcc extension to the standard C pre-processor. If you are not using the gcc compiler, you may need to omit the ## operator. The downside to this is that you then cannot call your macro with only one argument; for example, debug_tcprintf ("hello world") will not work. A simple workaround is to use debug_tcprintf ("%s", "hello world") in such a scenario.

Woodrow Barlow
  • 8,477
  • 3
  • 48
  • 86
  • I'd also remove the `p_time_now` temp var - it doesn't do anything and could cause warnings if printf() were ever redefined on a release build. – Michael Dorgan Aug 29 '17 at 17:16
  • String concatenation and token pasting won't work if the passed-in format isn't a string literal. – Andrew Henle Aug 29 '17 at 17:23
  • @AndrewHenle: That's true, but format strings should probably never ever be anything other than literals. – Kerrek SB Aug 29 '17 at 17:24
  • 1
    @AndrewHenle that's why my answer says "This only works if you call the macro with a string literal as the format string." – Woodrow Barlow Aug 29 '17 at 17:24
  • Yes, I konw that. But it's ok to use debug_tcprintf ("%s", "Hello World\n"); once I got used to it. – Øyvind Teig Aug 29 '17 at 17:48
  • @ØyvindTeig that isn't necessary, that's what the token pasting (the `##` operator) is for. you can simply call `debug_tcprintf ("Hello World\n");`. – Woodrow Barlow Aug 29 '17 at 17:50
  • @ØyvindTeig the token pasting accounts for the possibility of the macro being called with or without the additional variadic arguments. – Woodrow Barlow Aug 29 '17 at 17:51
  • The XC compiler causes "warning: Less than necessary 2 argument(s) in macro call "debug_tprintf2("Hello world\n")"" if I do the single "Hello World" – Øyvind Teig Aug 29 '17 at 17:55
  • @ØyvindTeig ah, in that case, it is possible that your compiler doesn't fully implement token pasting. – Woodrow Barlow Aug 29 '17 at 17:56
  • Interesting. I'll take this to theXCore Exchange group and ask there. Back later – Øyvind Teig Aug 29 '17 at 17:57
  • The XC compiler (in an .xc file) does not accept the ##__VA_ARGS__ and issues error: Not a valid preprocessing token ","init]\n"" That's why I didn't include it initially. However, when it compiles C (in a .c file) it is being accepted. So to make it general I'd have to take the lower route. – Øyvind Teig Aug 29 '17 at 19:05
  • There's a problem with `__VA_ARGS__`: You always get the comma before, even if you have *no* variable arguments. There are currently no portable ways to have an optional comma. – Kerrek SB Aug 29 '17 at 21:32
  • @WoodrowBarlow FYI, in a standard C Preprocessor, the macro `debug_tcprintf(fmt, ...)` requires _two arguments_; `...` requires _at least one_ argument. Furthermore, in a standard C Preprocessor, `, ## __VA_ARGS__` usually isn't valid as the paste usually doesn't result in a valid token. What you're actually using is a gnu cpp _extension_ that allows one less argument to be taken, and a special meaning to be given to `, ## __VA_ARGS__` that in such calls results in the comma being removed. – H Walters Aug 30 '17 at 02:58
  • Thanks to all of you! – Øyvind Teig Aug 30 '17 at 11:27
  • @KerrekSB "There are currently no portable ways to have an optional comma." That's not quite true. You can: (a) define the macro as `debug_tcprintf(...)`, detect whether there's one or more than one arguments (up to some n), in the former case use the one without a comma, and in the latter case use the rest with a comma; that approach requires multiple macros. Or (b) you can just use `debug_tcprintf(fmt, extra)`, _use_ it like `debug_tcprintf("%d%s",(,a,b))` or `debug_tcprintf("---",())`, and use supporting macros to expand `extra`. – H Walters Aug 30 '17 at 15:47
  • ...here it's probably not worth it; simply using an idiom `debug_tcprintf("---",0)` would pass an extra argument in that's ignored, but that's safe. – H Walters Aug 30 '17 at 15:54