【问题标题】:MACRO that converts enum to type in c将枚举转换为 c 类型的宏
【发布时间】:2017-10-14 04:03:39
【问题描述】:

我尝试了一个多星期没有成功。

我正在两个处理器之间创建一个记录器接口,我需要帮助来定义自动 MACROS。

我是什么意思? 假设我有一个定义为 LOGGER_MSG_ID_2 的记录器消息,它接受 uint8 和 uint16 类型的两个参数。

我有一个枚举定义为:

typedef enum{
    PARAM_NONE,
    PARAM_SIZE_UINT8,
    PARAM_SIZE_UINT16,
    PARAM_SIZE_UINT32
}paramSize_e;

所以LOGGER_MSG_ID_2 将有一个位图定义为:

#define LOGGER_MSG_ID_2_BITMAP   (PARAM_SIZE_UINT16 << 2 | PARAM_SIZE_UINT8)

这个位图是 1 Byte 大小,所以参数的最大数量是 4。 稍后我有一个根据消息 ID 定义所有参数类型的列表:

#define ID_2_P0_TYPE                                uint8  // first parameter
#define ID_2_P1_TYPE                                uint16 // 2nd parameter
#define ID_2_P2_TYPE                                0      // 3rd parameter
#define ID_2_P3_TYPE                                0      // 4th parameter

正如我所说,我有 4 个参数的限制,所以我想定义它们并让 MACRO 决定天气是否使用它们。我将它们定义为 0,但它可以是任何有效的值。

我还有其他使用位图获取各种属性的 MACROS,例如参数数量和消息大小。

现在是棘手的部分。我想构建一个从类型创建位图的宏。原因是我不希望位图和参数定义之间存在冗余。 我的问题是我尝试的所有内容都无法编译。

最终我想要一个宏,例如:

#define GET_ENUM_FROM_TYPE(_type)

根据类型给我 PARAM_SIZE_UINT8、PARAM_SIZE_UINT16 或 PARAM_SIZE_UINT32。

限制:我在 windows (armcl.exe) 和 C99 上使用 arm 编译器。我无法使用 C11 Generic()

我尝试了以下方法:

#define GET_ENUM_FROM_TYPE(_type)       \
(_type == uint8) ? PARAM_SIZE_UINT8 : \
        ((_type == uint16) ? PARAM_SIZE_UINT16 : \
                ((_type == uint32) ? PARAM_SIZE_UINT32 : PARAM_NONE))

最终我想像这样使用它:

 #define LOGGER_MSG_ID_2_BITMAP              \
    (GET_ENUM_FROM_TYPE(ID_2_P3_TYPE) << 6 | \ 
     GET_ENUM_FROM_TYPE(ID_2_P2_TYPE) << 4 | \ 
     GET_ENUM_FROM_TYPE(ID_2_P1_TYPE) << 2 | \
     GET_ENUM_FROM_TYPE(ID_2_P0_TYPE))

但是当我使用它时,它不会编译。

我有一张位图表:

uint8 paramsSizeBitmap [] = {
    LOGGER_MSG_ID_1_BITMAP,                         /*  LOGGER_MSG_ID_1   */
    LOGGER_MSG_ID_2_BITMAP,                         /*  LOGGER_MSG_ID_2   */
    LOGGER_MSG_ID_3_BITMAP,                         /*  LOGGER_MSG_ID_3   */
    LOGGER_MSG_ID_4_BITMAP,                         /*  LOGGER_MSG_ID_4   */
    LOGGER_MSG_ID_5_BITMAP,                         /*  LOGGER_MSG_ID_5   */
    LOGGER_MSG_ID_6_BITMAP,                         /*  LOGGER_MSG_ID_6   */
    LOGGER_MSG_ID_7_BITMAP,                         /*  LOGGER_MSG_ID_7   */
    LOGGER_MSG_ID_8_BITMAP,                         /*  LOGGER_MSG_ID_8   */
    LOGGER_MSG_ID_9_BITMAP,                         /*  LOGGER_MSG_ID_9   */
    LOGGER_MSG_ID_10_BITMAP,                        /*  LOGGER_MSG_ID_10  */
};

我得到这个错误:

line 39: error #18: expected a ")"
line 39: error #29: expected an expression

(第 39 行是LOGGER_MSG_ID_2_BITMAP

我哪里出错了?

----- 编辑-----

现在我有一个我不太喜欢的解决方法。 我不使用 uint64,所以我使用了sizeof() MACRO,现在我的 MACRO 看起来像这样:

#define GET_ENUM_FROM_TYPE(_type)       \
(sizeof(_type) == sizeof(uint8)) ? PARAM_SIZE_UINT8 : \
        ((sizeof(_type) == sizeof(uint16)) ? PARAM_SIZE_UINT16 : \
                ((sizeof(_type) == sizeof(uint32)) ? PARAM_SIZE_UINT32 : PARAM_NONE))

我的参数列表是:

#define NO_PARAM                                    uint64

#define ID_2_P0_TYPE                                uint8
#define ID_2_P1_TYPE                                uint16
#define ID_2_P2_TYPE                                NO_PARAM
#define ID_2_P3_TYPE                                NO_PARAM

它工作正常,但是......你知道......

【问题讨论】:

  • 你检查过预处理后的表格是什么样子的吗?大多数编译器都提供输出预处理源文件的选项。
  • 另外,像GET_ENUM_FROM_TYPE 这样的非平凡宏应该用括号括起来。运算符优先级可能会在这里咬你。
  • "This bitmap is 1 Byte size" 那你为什么使用 uint16?没有多大意义。
  • @Lundin ID 是 1 个字节,位图是另一个 1 个字节。 4 个参数,每个 2 位。 4 x 2bit = 8bit = 1 字节

标签: c types macros arm c-preprocessor


【解决方案1】:

基本问题是== 不支持types,仅支持values。给定

uint8 foo;

你可以说foo==42,但不能说foo == uint8。这是因为类型不是 C 中的第一类。

一种技巧是使用 C 预处理器字符串化运算符# (gcc docs)。但是,这会将您的所有计算转移到运行时,并且可能不适合嵌入式环境。例如:

#define GET_ENUM_FROM_TYPE(_type)   (    \
(strcmp(#_type, "uint8")==0) ? PARAM_SIZE_UINT8 : \
        ((strcmp(#_type, "uint16")==0) ? PARAM_SIZE_UINT16 : \
                ((strcmp(#_type, "uint32")==0) ? PARAM_SIZE_UINT32 : PARAM_NONE)) \
)

有了这个定义,

GET_ENUM_FROM_TYPE(uint8)

扩展到

( (strcmp("uint8", "uint8")==0) ? PARAM_SIZE_UINT8 : ((strcmp("uint8", "uint16")==0) ? PARAM_SIZE_UINT16 : ((strcmp("uint8", "uint32")==0) ? PARAM_SIZE_UINT32 : PARAM_NONE)) )

它应该做你想做的事,尽管在运行时。

【讨论】:

  • 哇。不错的黑客!但不幸的是,你是对的。我必须对其进行预处理。
  • @DrorNohi 您是否愿意接受额外的预处理步骤,或者它必须只在 C 中?我维护了一个可能有用的Perl-based preprocessor
  • @DrorNohi 实际上,我认为 user694733 已经做到了:)。快乐的黑客攻击!
【解决方案2】:

我相信解决方案是使用连接运算符##,并使用助手定义。

// These must match your enum
#define HELPER_0      PARAM_NONE
#define HELPER_uint8  PARAM_SIZE_UINT8
#define HELPER_uint16 PARAM_SIZE_UINT16
#define HELPER_uint32 PARAM_SIZE_UINT32

// Secondary macro to avoid expansion to HELPER__type
#define CONCAT(a, b) a ## b

// Outer parenthesis not strictly necessary here
#define GET_ENUM_FROM_TYPE(_type) (CONCAT(HELPER_, _type))

这样GET_ENUM_FROM_TYPE(ID_2_P1_TYPE) 将在预处理后扩展为(PARAM_SIZE_UINT16)

注意HELPER_*** 定义中的后缀必须与ID_*_P*_TYPE 宏的内容完全匹配。例如HELPER_UINT8 将不起作用(大小写无效)。 (感谢@cxw)

【讨论】:

  • 非常好!我建议在答案中明确指出 HELPER_ 定义区分大小写 --- HELPER_UINT8 不起作用。我不经常看到混合大小写的预处理器符号。
  • 救命!不知道## 运算符!我有很多东西可以用它!
  • @DrorNohi ## 很有用,但请谨慎使用。它通常会导致难以维护的宏混乱。
  • @user694733 我的错,似乎确实如此。评论已删除。
【解决方案3】:

抱歉,这并不能直接回答问题。但是你应该重新考虑整个代码。

首先,_Generic 会优雅地解决这个问题。

解开这些宏组的肮脏替代方法是使用所谓的X macros,它非常适合诸如“我不希望位图和参数定义之间存在冗余”之类的情况。您可能会使用 X 宏重写您的代码,并摆脱许多多余的定义和宏。它最终的可读性是另一回事。

但是,每当您发现自己在某个宏元编程丛林中如此深入时,这几乎总是表明程序设计不佳。所有这些听起来都像是对一个可以用更好的方式解决的问题的人为解决方案——它是"XY problem"。 (不要与 X 宏混淆 :))。最好的解决方案很可能涉及以更简单的方式完全重写它。您的情况听起来并不独特,您似乎只想生成一堆位掩码。

优秀的程序员总是试图让他们的代码更简单,而不是让它变得更复杂。


此外,您可能在整个代码中或多或少地存在由 C 语言类型系统引起的严重错误。可以概括为:

  • 位移位或其他位算术永远不应该用于有符号类型。这样做可能会导致各种细微的错误和定义不明确的行为。
  • 枚举常量始终是带符号的int 类型。您应该避免将它们与按位算术混合。像这样的程序完全避免使用枚举。
  • 在表达式中使用时,uint8_tuint16_t 等小整数类型会隐式提升为 int。这意味着 C 语言将支持您获取正确类型并将所有内容替换为 int 的大部分尝试。
  • 生成的宏类型将是 int,这不是您想要的。

【讨论】:

  • 很遗憾,我的编译器不支持_Generic。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-02-21
  • 2010-12-21
  • 2019-06-09
  • 2020-08-21
  • 1970-01-01
  • 2012-09-30
  • 1970-01-01
相关资源
最近更新 更多