比较预处理器宏是否相等
Compare preprocessor macros for equality
我从一些 .dbc
文件中生成了一些粗略的 header。
由于一些消息表示数组中的元素,因此结构是相等的,因此生成的宏是相等的。因为我在代码中填充了一些结构数组,所以我想省力并为所有 objects 使用相同的宏,但为了确保定义没有改变,我想在编译时测试宏是否相等.
示例:
#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++ 中有一些解决方案也可能有帮助。但是宏是固定的。
这是一个可怕的 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)
returns x
的扩展作为字符串文字。我们需要使用 STRINGIFY_()
分两步进行字符串化,因为 #
会抑制宏扩展。 (一步我们会得到 "<x>"
而不是 "expanded version of <x>"
。)
GCC 有一个内置版本的 strcmp()
(__builtin_strcmp()
) 在这里使用。它恰好能够在编译时比较常量字符串。如果您传递 -fno-builtin
(除非您明确使用 __builtin_strcmp()
),代码会中断。
static_assert
是 C11 编译时断言。
有了上面的三个要素,我们可以将扩展的宏字符串化(传递一些可能对参数唯一的虚拟标记)并在编译时比较字符串。
是的,这是一个 hack...
在 C++11 中,有更安全的方法在编译时比较字符串——参见例如this answer.
附带说明一下,您也可以在 运行 时执行此操作,GCC 和 Clang 的开销为零。 (上面的版本不适用于 Clang,因为它更挑剔 strcmp(...) == 0
不是 static_assert
要求的 整数常量表达式 。)A 运行-时间检查喜欢
if (strcmp(STRINGIFY(m1(xxx)), STRINGIFY(m2(xxx))) != 0) {
*report error and exit*
}
当宏相等时得到完全优化。甚至字符串都没有保存在只读数据段中(刚刚检查过)。如果您可以忍受不得不 运行 程序来发现问题,那么这是一种更可靠的方法。
使用可变参数宏进行字符串化可以做得更好:
#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 这样的虚拟标记)。注意
比较最终的扩展,而不是完整的扩展历史。
所以给定这些#defines:
#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);
我从一些 .dbc
文件中生成了一些粗略的 header。
由于一些消息表示数组中的元素,因此结构是相等的,因此生成的宏是相等的。因为我在代码中填充了一些结构数组,所以我想省力并为所有 objects 使用相同的宏,但为了确保定义没有改变,我想在编译时测试宏是否相等.
示例:
#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++ 中有一些解决方案也可能有帮助。但是宏是固定的。
这是一个可怕的 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)
returnsx
的扩展作为字符串文字。我们需要使用STRINGIFY_()
分两步进行字符串化,因为#
会抑制宏扩展。 (一步我们会得到"<x>"
而不是"expanded version of <x>"
。)GCC 有一个内置版本的
strcmp()
(__builtin_strcmp()
) 在这里使用。它恰好能够在编译时比较常量字符串。如果您传递-fno-builtin
(除非您明确使用__builtin_strcmp()
),代码会中断。static_assert
是 C11 编译时断言。
有了上面的三个要素,我们可以将扩展的宏字符串化(传递一些可能对参数唯一的虚拟标记)并在编译时比较字符串。
是的,这是一个 hack...
在 C++11 中,有更安全的方法在编译时比较字符串——参见例如this answer.
附带说明一下,您也可以在 运行 时执行此操作,GCC 和 Clang 的开销为零。 (上面的版本不适用于 Clang,因为它更挑剔 strcmp(...) == 0
不是 static_assert
要求的 整数常量表达式 。)A 运行-时间检查喜欢
if (strcmp(STRINGIFY(m1(xxx)), STRINGIFY(m2(xxx))) != 0) {
*report error and exit*
}
当宏相等时得到完全优化。甚至字符串都没有保存在只读数据段中(刚刚检查过)。如果您可以忍受不得不 运行 程序来发现问题,那么这是一种更可靠的方法。
使用可变参数宏进行字符串化可以做得更好:
#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 这样的虚拟标记)。注意 比较最终的扩展,而不是完整的扩展历史。 所以给定这些#defines:
#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);