【问题标题】:portability and safety of C macro using lambda parameter使用 lambda 参数的 C 宏的可移植性和安全性
【发布时间】:2021-07-01 07:13:30
【问题描述】:

前言

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

动机

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

所以我添加了一个宏,当断言失败时,它允许类似printf() 的输出(通过E() (for 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__);

由于自测程序使用了类似数组的数据结构,我也需要检查这些,所以我在测试之前输出了这些,即使所有测试都成功了,也会产生很多输出。

所以我发明了另一个宏,VFA()verbose failed assertion),它使用这样的“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 会不会造成任何麻烦?

【问题讨论】:

  • C 允许相当长的标识符。与其命名像VA 这样完全神秘的东西,然后想出它代表什么的解释,只需将宏命名为verbose_assert。同样,不要命名为“E for error”,将其命名为 print_err 或其他名称。对于其他 C 程序员来说,没有什么比强迫他们学习一种秘密的宏语言来阅读你的代码更烦人的了。
  • 一个常见的众所周知的技巧是编写像assert(("Failed to do stuff",cond)); 这样的断言,其中, 是逗号运算符。然后大多数编译器会给出一个错误,例如:“断言在第 x 行失败,表达式:(“无法做事”,0)”。这很不言自明。
  • @Lundin 总的来说,我同意你关于简短和神秘名称的说法,但自检模块不适合最终用户查看。在输入数百个测试时,我通常会打错那些冗长的名称。
  • “我不喜欢/管理打字”不是设计程序时使用的有效理由。任何体面的 IDE 都具有代码完成功能。而且总是有复制/粘贴。除此之外,程序员的职业就是要打字很多。那些不喜欢它的人可能最好从事其他职业。

标签: c lambda macros


【解决方案1】:

宏参数的解析可以跨编译器移植吗?

没有。 ##__VA_ARGS__ 是一个不可移植的 gcc 扩展。 What does ##__VA_ARGS__ mean?

考虑到参数可能相当复杂(如上一个示例所示),使用 cmd 会产生任何麻烦吗?

该宏参数的() 中的项目将意味着它都被视为单个预处理器令牌并被扩展。如果您好奇,可以查看预处理器的输出。正式地,这是在 C17 6.10.3/10 中指定的:

每个后续实例 类似函数的宏名称后跟 ( 作为下一个预处理标记引入 由定义中的替换列表替换的预处理标记序列 (宏的调用)。预处理标记的替换序列是 由匹配的 ) 预处理标记终止,跳过中间匹配的左右括号预处理标记对。

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

【讨论】:

  • 您能否引用解释“balpar 功能”(将一对平衡括号内的项目作为一个参数处理)的参考资料。我昨天搜索的时候没找到。
  • @U.Windl 参考添加到答案中。
  • 我找不到公共文档,但我发现“列表中的各个参数由逗号预处理标记分​​隔,但匹配内括号之间的逗号预处理标记不分隔参数。" in "19.3 Macro replacement" on page 439 in Working Draft, Standard for Programming Language C++
  • @U.Windl 这是错误的语言,但是如果您阅读我引用的 C17 6.10.3/11 之后的下一段,您会在 C 标准中找到相同的文本。跨度>
  • 我知道引用的文档是 C++,而不是 C。但是,找到 C++ 文档总比什么都找不到要好。毕竟我认为 C 和 C++ 预处理器之间没有显着差异。
猜你喜欢
  • 2020-11-22
  • 2023-03-05
  • 2011-08-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-02
  • 1970-01-01
相关资源
最近更新 更多