比较预处理宏是否相等

4
我有一些通过一些`.dbc`文件生成的原始头文件。由于其中一些消息代表数组中的元素,因此结构相等,因此生成的宏也相等。由于我在代码中填充了一些结构体数组,我希望节省精力并为所有对象使用相同的宏,但为了确保定义没有更改,我希望在编译时测试这些宏是否相等。
例如:
#define GET_PATTERN_01_PATTERNPOINT02Y(buf) (0 \
    | (uint16)(-(uint16)((buf[7] >> 6) & 0x01) << 15) \
    | (uint8)(+(uint8)((buf[6] >> 0) & 0xff) << 0) \
    | (uint16)(+(uint16)((buf[7] >> 0) & 0x7f) << 8) \
)

#define GET_PATTERN_02_PATTERNPOINT04Y(buf) (0 \
    | (uint16)(-(uint16)((buf[7] >> 6) & 0x01) << 15) \
    | (uint8)(+(uint8)((buf[6] >> 0) & 0xff) << 0) \
    | (uint16)(+(uint16)((buf[7] >> 0) & 0x7f) << 8) \
)

#if GET_PATTERN_01_PATTERNPOINT02Y != GET_PATTERN_02_PATTERNPOINT04Y
#  error blah
#endif

这个可行吗? 如果有一些C++的解决方案也可能会有所帮助,但是宏已经固定了。


不可以,因为预处理器运行于编译过程的独立阶段,意味着它没有关于变量或其他运行时事物的知识。 - Some programmer dude
是的,我知道这些阶段。我想也许有人有一些 stringify hack。正确解析表达式并不重要。如果只有空格不同,比较失败也没关系,因为生成器会产生完全相同的代码。 - vlad_tepesch
不行,因为你的宏是函数式的。你可以用另一种类型的宏来实现你的想法,但如果没有正确数量的参数,你无法展开函数式宏。 - John Bollinger
5
@JoachimPileborg,我认为他想要比较宏定义时替换参数。但是,不,你不能这样做。 - John Bollinger
如果在C++中有一些解决方案可能也会有所帮助。--“cpp”是指C ++还是C预处理器? - Keith Thompson
显示剩余3条评论
2个回答

7
这是一个可怕的hack,但至少对于您在GCC和C11中的示例似乎有效:
#include <assert.h>
#include <string.h>

...

#define STRINGIFY(x) STRINGIFY_(x)
#define STRINGIFY_(x) #x

#define ASSERT_SAME(m1, m2)                                          \
  static_assert(strcmp(STRINGIFY(m1(xxx)), STRINGIFY(m2(xxx))) == 0, \
                #m1"() and "#m2"() differ!")

ASSERT_SAME(GET_PATTERN_01_PATTERNPOINT02Y, GET_PATTERN_02_PATTERNPOINT04Y);

你可能需要传递-std=c11-std=gnu11,尽管在这里后者不应该需要。

解释:

  • STRINGIFY(x)返回x的展开形式作为字符串文字。我们需要使用STRINGIFY_()分两步进行字符串化,因为#抑制了宏扩展。(使用一步我们得到的是"<x>"而不是"expanded version of <x>")。
  • GCC有一个内置版本的strcmp() (__builtin_strcmp()) 在这里被使用。它碰巧能够在编译时比较常量字符串。如果你传递-fno-builtin (除非你明确使用__builtin_strcmp()),否则代码会中断。
  • static_assert是C11编译时断言。

有了上述三个部分,我们可以将扩展的宏转换为字符串(传递一些虚拟标记,这对于参数很可能是唯一的),并在编译时比较字符串。

是的,这是一个hack...

在C++11中,有更安全的方法在编译时比较字符串--请参见例如这个答案

顺便说一下,你也可以在运行时进行零开销的检查,对于GCC和Clang来说都是如此。 (上面的版本不会为Clang工作,因为它更挑剔于strcmp(...) == 0不是一个整数常量表达式,正如static_assert所要求的那样)。一个运行时检查可能是:

if (strcmp(STRINGIFY(m1(xxx)), STRINGIFY(m2(xxx))) != 0) {
    *report error and exit*
}

当宏相等时,该段代码得到完全优化。甚至字符串也不会保留在只读数据段中(已经检查过)。如果您可以接受必须运行程序才能发现问题的方式,那么这是一种更可靠的方法。

1
请注意,您可以在C11标准之前的C语言中使用静态断言。 - user3079266
@Mints97 是的,我知道有办法将编译时断言(compile-time assertions)破解到早期版本的 C 中。但我想保持事情相对简单。也许值得一提。 - Ulfalizer
2
在阅读一个无关的答案时,我意识到仅仅执行 STRINGIFY(m1(xxx)) == STRINGIFY(m2(xxx)) 在实践中也很可能有效。原因是编译器倾向于合并相同的字符串字面量,使它们最终位于相同的地址,这意味着您可以直接比较指针。不过别用这种方法。:P - Ulfalizer

0

通过使用可变参数宏来进行字符串化,可以更好地完成这个操作:

#define STRINGIFY_VARIADIC(...) #__VA_ARGS__
#define EXPAND_AND_STRINGIFY_VARIADIC(...) STRINGIFY_VARIADIC (__VA_ARGS__)

#define STATIC_ASSERT_IDENTICAL_EXPANSIONS(macro_a, macro_b) \
   _Static_assert (                                          \
       (                                                     \
         __builtin_strcmp (                                  \
             EXPAND_AND_STRINGIFY_VARIADIC (macro_a),        \
             EXPAND_AND_STRINGIFY_VARIADIC (macro_b) )       \
         == 0                                                \
       ),                                                    \
       "expansions of " #macro_a " and " #macro_b " differ" )

这有两个优点:它可以与扩展为元组的宏一起使用(例如 #define FOO thing1, thing2),也可以与带参数的宏一起使用(无需像其他解决方案中的 xxx 一样使用虚拟标记)。请注意,进行比较的是最终的扩展结果,而不是完整的扩展历史记录。因此,考虑到以下 #define:

#define FOO foo
#define BAR bar
#define ARG_DOUBLER(arg) arg, arg
#define ARG_ITSELF(arg) arg
#define OTHER_ARG_DOUBLER(arg) ARG_ITSELF (arg), ARG_ITSELF (arg)
#define SECOND_ARG_NUKER(arg1, arg2) arg1

所有这些都会触发编译时错误:

STATIC_ASSERT_IDENTICAL_EXPANSIONS (FOO, BAR);
STATIC_ASSERT_IDENTICAL_EXPANSIONS (ARG_DOUBLER (x), ARG_DOUBLER (y));
STATIC_ASSERT_IDENTICAL_EXPANSIONS (x, ARG_ITSELF (y));
STATIC_ASSERT_IDENTICAL_EXPANSIONS (SECOND_ARG_NUKER (x, y), y);

这些都将编译成功:

STATIC_ASSERT_IDENTICAL_EXPANSIONS (FOO, foo);
STATIC_ASSERT_IDENTICAL_EXPANSIONS (ARG_DOUBLER (x), ARG_DOUBLER (x));
STATIC_ASSERT_IDENTICAL_EXPANSIONS (x, ARG_ITSELF (x));
STATIC_ASSERT_IDENTICAL_EXPANSIONS (SECOND_ARG_NUKER (x, y), x);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接