0

Preface

I know that there are several libraries for auto-testing available. Let's ignore that for this question, please.

Motivation

Implementing some library I got tired of manual testing, so I started to write a "self-test" program, starting with code using many assert()s. Unfortunately when an assert() fails, only limited information is shown on the screen, and I would typically have to use the debugger to examine the core dump to get more details of the failure.

So I added a macro that allows a printf()-like output (implemented via the E() (for error) macro) when an assertion fails; I named that macro VA() (for verbose assertion):

#define VA(assert_cond, msg, ...)   do { \
    if ( !(assert_cond) ) E(msg, ##__VA_ARGS__); \
    assert(assert_cond); \
} while (0)

Using that would look like this:

    VA(FASTWORD(FASTWORD_BITS - 1) == 0, "%s: FASTWORD() failed", __func__);

As the self-test program used array-like data structures, I needed to inspact those as well, so I output those before doing the tests, resulting in a lot of output even when all tests succeed.

So I invented another macro, VFA() (verbose failed assertion) that uses a "lambda parameter" like this:

#define VFA(assert_cond, cmd, msg, ...) do {    \
    if ( !(assert_cond) ) { \
        E(msg, ##__VA_ARGS__); \
        cmd; \
    } \
    assert(assert_cond); \
} while (0)

While writing that I wondered how the preprocessor would parse commata for a use case like this:

    VFA(fw[0] == out_fw0 && fw[1] == out_fw1,
        dump_fastwords_range(fw, 4, pos, (pos + count) % FASTWORD_BITS),
        "%s: __clear_fw_bits_up(%d, %d) failed", context, pos, count);

I mean it could be possible that the condition could be the first parameter, dump_fastwords_range(fw could be the second, 4 could be the third, and so on...

However that is not the case with gcc at least.

The other thing is cmd; in the macro: My first version did not include the semicolon, so I would have to write (which looks really ugly):

    VFA(fw[0] == out_fw0 && fw[1] == out_fw1,
        dump_fastwords_range(fw, 4, pos, (pos + count) % FASTWORD_BITS);,
        "%s: __clear_fw_bits_up(%d, %d) failed", context, pos, count);

OK, here's another use example of my macro:

    VFA(fw[0] == out_fw0 && fw[1] == out_fw1,
    {
        const unsigned first = pos >= count ?
            pos - count : FASTWORD_BITS + pos - count + 1;

        dump_fastwords_range(fw, 4, first, pos);
    },
        "%s: __clear_fw_bits_dn(%d, %d) failed", context, pos, count);

Questions

The questions I have are:

  1. Is parsing of the macro parameters portable across compilers?
  2. Will the cmd use create any trouble, considering the parameter could be rather complex (as the last example suggests)?
U. Windl
  • 3,480
  • 26
  • 54
  • 2
    C allows quite long identifiers. Instead of naming stuff utterly cryptical like `VA` and then come up with an explanation what it stands for, just name the macro `verbose_assert`. Similarly, don't name something "`E` for error", name it `print_err` or something. There are few things that are more annoying to other C programmers than forcing them to learn a secret macro language in order to read your code. – Lundin Jul 01 '21 at 07:43
  • 2
    A common, well-known trick is to write asserts like `assert(("Failed to do stuff",cond));` where the `,` is the comma operator. Most compilers will then give an error like: "Assertion failed at line x, Expression: ("Failed to do stuff",0)". That's pretty self-explanatory. – Lundin Jul 01 '21 at 07:48
  • @Lundin In general I agree with your statement about short and cryptic names, but the self-test module isn't intended for end-users to see. And when entering hundreds of tests, I typically tend to mistype those lengthy names. – U. Windl Jul 01 '21 at 08:09
  • "I don't like/manage typing" is not a valid rationale to use when designing programs. Any decent IDE out there has a code completion feature. And there's always copy/paste. Apart from that, the programmer profession is all about typing a whole lot. Those who don't like it are probably better off pursuing some other career. – Lundin Jul 01 '21 at 08:37

1 Answers1

1

Is parsing of the macro parameters portable across compilers?

No. ##__VA_ARGS__ is a non-portable gcc extension. What does ##__VA_ARGS__ mean?

Will the cmd use create any trouble, considering the parameter could be rather complex (as the last example suggests)?

Items within () of that macro parameter will mean that it all gets treated like a single pre-processor token and expanded as such. You can peek at the pre-processor output if you are curious. Formally this is specified in C17 6.10.3/10:

Each subsequent instance of the function-like macro name followed by a ( as the next preprocessing token introduces the sequence of preprocessing tokens that is replaced by the replacement list in the definition (an invocation of the macro). The replaced sequence of preprocessing tokens is terminated by the matching ) preprocessing token, skipping intervening matched pairs of left and right parenthesis preprocessing tokens.

So it shouldn't create any trouble unless you do truly evil stuff like using goto or setjmp etc from inside it.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Can you cite a reference where the "balpar feature" (handling items within a balanced pair of parentheses as one parameter) is explained. I failed to find it when searching yesterday. – U. Windl Jul 01 '21 at 08:11
  • @U.Windl Reference added to the answer. – Lundin Jul 01 '21 at 08:45
  • I could not find a public document, but I found "*The individual arguments within the list are separated by comma preprocessing tokens, but comma preprocessing tokens between matching inner parentheses do not separate arguments.*" in "*19.3 Macro replacement*" on page 439 in [Working Draft, Standard for Programming Language C++](http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/n4659.pdf) – U. Windl Jul 01 '21 at 09:04
  • @U.Windl That's the wrong language, but you'll find the same text in the C standard if you read the next paragraph after the one I quoted, C17 6.10.3/11. – Lundin Jul 01 '21 at 09:15
  • I'm aware that the doc cited from was C++, not C. However finding a C++ document is better than finding nothing at all. After all I don't think there are significant differences between the C and C++ preprocessor. – U. Windl Jul 01 '21 at 12:33
  • @U.Windl C++ didn't have variadic macros until C++11. C had it since C99. And I think C++ added some feature similar to the `##__VA_ARGS__` extension, to solve the problem that all arguments must be used. C doesn't have that. – Lundin Jul 01 '21 at 13:39