使用 lambda 参数的 C 宏的可移植性和安全性

portability and safety of C macro using lambda parameter

前言

我知道有几个可用的自动测试库。 这个问题请忽略。

动机

实现一些库我厌倦了手动测试,所以我开始编写一个“自测试”程序,从使用许多 assert() 的代码开始。 不幸的是,当 assert() 失败时,屏幕上只显示有限的信息,我通常必须使用调试器检查核心转储以获得更多 失败细节 .

所以我添加了一个宏,当断言失败时允许类似 printf() 的输出(通过 E()(对于 error)宏实现) ;我将该宏命名为 VA()(用于 详细断言 ):

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

使用它看起来像这样:

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

由于自测程序使用了类似数组的数据结构,我也需要对它们进行inspact,所以我在做测试之前输出了那些,结果即使所有测试都成功了也会输出很多。

所以我发明了另一个宏,VFA()冗长的失败断言),它使用这样的“lambda 参数” :

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

在写这篇文章时,我想知道预处理器如何为这样的用例解析逗号:

    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);

我的意思是条件可能是第一个参数,dump_fastwords_range(fw可能是第二个,4可能是第三个,依此类推...

然而,至少 gcc 并非如此。

另一件事是宏中的cmd;: 我的第一个版本没有包含分号,所以我不得不写(看起来真的很难看):

    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);

好的,这是我的宏的另一个使用示例:

    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);

问题

我的问题是:

  1. 宏参数的解析是否可以跨编译器移植?
  2. 考虑到参数可能相当复杂(如最后一个示例所示),cmd 的使用是否会造成任何麻烦?

Is parsing of the macro parameters portable across compilers?

没有。 ##__VA_ARGS__ 是不可移植的 gcc 扩展。

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

该宏参数 () 内的项目将意味着它全部被视为单个预处理器标记并按此扩展。如果您好奇,可以查看预处理器输出。在 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.

所以它不应该造成任何麻烦,除非你在里面做真正邪恶的事情,比如使用 gotosetjmp 等。