使用 ENUM 作为位图,如何在 C 中验证

Using ENUMs as bitmaps, how to validate in C

我正在为具有内存限制的嵌入式应用程序开发固件。我有一组命令需要在收到时进行处理。每个命令都属于不同的 'buckets',每个 'bucket' 都有一个有效的命令编号范围。我创建了两个 ENUM,如下所示。

enum
{
  BUCKET_1 = 0x100, // Range of 0x100 to 0x1FF
  BUCKET_2 = 0x200, // Range of 0x200 to 0x2FF
  BUCKET_3 = 0x300, // Range of 0x300 to 0x3FF
  ...
  ...
  BUCKET_N = 0xN00 // Range of 0xN00 to 0xNFF
} cmd_buckets;

enum 
{
  //BUCKET_1 commands
  CMD_BUCKET_1_START = BUCKET_1,
  BUCKET_1_CMD_1,
  BUCKET_1_CMD_2,
  BUCKET_1_CMD_3,
  BUCKET_1_CMD_4,
  //Add new commands above this line
  BUCKET_1_CMD_MAX,

  //BUCKET_2 commands
  CMD_BUCKET_2_START = BUCKET_2,
  BUCKET_2_CMD_1,
  BUCKET_2_CMD_2,
  BUCKET_2_CMD_3,
  //Add new commands above this line
  BUCKET_2_CMD_MAX,

  //BUCKET_3 commands
  ...
  ...
  ...

  //BUCKET_N commands
  CMD_BUCKET_N_START = BUCKET_N
  BUCKET_N_CMD_1,
  BUCKET_N_CMD_2,
  BUCKET_N_CMD_3,
  BUCKET_N_CMD_4,
  //Add new commands above this line
  BUCKET_N_CMD_MAX,
}cmd_codes

当我的命令处理函数接收到命令代码时,它需要在处理命令之前检查该命令是否已启用。我打算为此使用位图。在 运行 时间内可以启用或禁用命令处理。我可以为每个组使用一个 int(每组给我 32 个命令,我意识到 0xN00 到 0xN20 是有效的命令代码,并且该范围内的其他代码被浪费了)。即使命令代码被浪费,设计选择的好处是在控制台上看到原始数据时可以轻松告诉组命令代码。

由于许多开发人员可以向 'cmd_codes' 枚举添加命令(甚至可以根据需要向 'cmd_buckets' 枚举添加新的桶),我想确保命令代码的数量每个桶不超过32(位图是int)。我想在编译时而不是 运行 时捕获它。除了如下检查每个 BUCKET_N_CMD_MAX 值并抛出编译时错误之外,还有更好的解决方案吗?

#if (BUCKET_1_CMD_MAX > 0x20)
#error ("Number of commands in BUCKET_1 exceeded 32")
#endif

#if (BUCKET_2_CMD_MAX > 0x20)
#error ("Number of commands in BUCKET_2 exceeded 32")
#endif

#if (BUCKET_3_CMD_MAX > 0x20)
#error ("Number of commands in BUCKET_3 exceeded 32")
#endif
...
...
...
#if (BUCKET_N_CMD_MAX > 0x20)
#error ("Number of commands in BUCKET_N exceeded 32")
#endif

如果有更优雅的设计方式,也请提出建议。

谢谢,我感谢你的时间和耐心。

首先,预处理器命令不是那样工作的。 C 预处理器能够 "see" 仅由 #define 语句指示的名称或作为编译器标志传递。它无法看到定义为 enum 的一部分或使用 const 关键字定义的常量。您应该使用 _Static_assert 来验证命令而不是预处理器。

至于命令,我建议将所有命令编号在 0..0x20:

范围内
enum {
  BUCKET_1_CMD_1,
  BUCKET_1_CMD_2,
  ...
  BUCKET_1_CMD_MAX,
};
enum {
  BUCKET_2_CMD_1,
  BUCKET_2_CMD_2,
  ...
  BUCKET_2_CMD_MAX,
};

那么你只需要一个保护值来检查所有命令是否在有效范围内:

#define MAX_COMMAND 0x20
_Static_assert(BUCKET_1_CMD_MAX <= MAX_COMMAND, "too many bucket 1 commands");
_Static_assert(BUCKET_2_CMD_MAX <= MAX_COMMAND, "too many bucket 2 commands");

要使用命令,bitwise-or 将它们与存储桶一起 "offset":

enum {
BUCKET_1 = 0x100,
BUCKET_2 = 0x200,
};
...
int cmd = BUCKET_2 | BUCKET_2_CMD_1;

首先修复代码中的bug。如评论中所述,您有一个常量 BUCKET_1 = 0x100,然后将其分配 CMD_BUCKET_1_START = BUCKET_1。因此,尾随枚举将获得值 0x101、0x102...,而 BUCKET_1_CMD_MAX 将为 0x106。由于 0x106 始终大于 0x20,因此您的静态断言将始终触发。

修复该问题,使其真正检查枚举中的项目总数,如下所示:

#define BUCKET_1_CMD_N (BUCKET_1_CMD_MAX - CMD_BUCKET_1_START)
#define BUCKET_2_CMD_N (BUCKET_2_CMD_MAX - CMD_BUCKET_2_START)
...

假设以上是固定的,那么你可以用一个宏来代替无数的检查。不是很大的改进,但至少它减少了代码重复:

#define BUCKET_MAX 32 // use a defined constant instead of a magic number

// some helper macros:    
#define CHECK(n) BUCKET_ ## n ## _CMD_N
#define STRINGIFY(n) #n

// the actual macro:
#define BUCKET_CHECK(n) \
  _Static_assert(CHECK(n) <= BUCKET_MAX, \
                 "Number of commands in BUCKET_" STRINGIFY(n) "_CMD_N exceeds BUCKET_MAX.");


// usage:
int main (void)
{
  BUCKET_CHECK(1);
  BUCKET_CHECK(2);
}

如果一个常量太大,gcc 的输出:

error: static assertion failed: "Number of commands in BUCKET_1_CMD_N exceeds BUCKET_MAX."
note: in expansion of macro 'BUCKET_CHECK'

编辑

如果将错误修复与检查宏结合使用,您将得到:

#define BUCKET_MAX 32

#define CHECK(n) (BUCKET_##n##_CMD_MAX - CMD_BUCKET_##n##_START)
#define STRINGIFY(n) #n
#define BUCKET_CHECK(n) \
  _Static_assert(CHECK(n) <= BUCKET_MAX, \
                 "Number of commands in BUCKET " STRINGIFY(n) " exceeds BUCKET_MAX.");

int main (void)
{
  BUCKET_CHECK(1);
  BUCKET_CHECK(2);
}