【问题标题】:How to get reflection-like functionality in C, without x-macros如何在没有 x 宏的情况下在 C 中获得类似反射的功能
【发布时间】:2017-08-15 13:00:39
【问题描述】:

this question on Software Engineering about easily serializing various struct contents on demand 相关,我发现一篇文章使用 x-macros 创建“开箱即用”结构序列化所需的结构元数据。我还看到过类似的“智能枚举”技术,但归结为相同的原理,即获取枚举的字符串表示形式,或通过名称获取结构的字段值,或类似的东西。

然而,经验丰富的 C 程序员在 Stack Overflow 上表示应该避免使用 x 宏作为“最后的手段”:

我可能会找到更多相关主题,但不幸的是我没有将它们添加为书签,所以这只是一些 Google-fu。

也许正确答案类似于Protocol Buffers?但是为什么用不同的语言创建结构定义(.proto 定义)然后运行构建步骤来生成 C 文件比使用内置预处理器来做同样的事情更可取呢?问题是这些技术仍然不允许我按名称检索单个结构,我必须在两个项目之间共享相同的定义并保持它们同步。

那么问题是:如果 x-macros 是“最后的手段”,那么解决我的问题的方法(当从不同的设备请求时轻松序列化各种内部数据)将是“第一手段”,或者在诉诸宏之前的任何方法地狱?

【问题讨论】:

  • 停止思考“通用”并处理特定案例。这是我在经历了多年的困境之后为自己得出的结论。
  • @EugeneSh.: 这是一个实际的项目,我们有一个可以与其他设备通信的微控制器操作的设备,但其中一个要求是我们必须能够查询它的状态(即访问内存中的结构按需)。到目前为止,我们只是为每个查询创建了不同的消息,然后我们将使用不同的手工函数解析请求并序列化每个特定的消息类型。所以这种方法看起来工作量更少,意味着出错的空间更小。
  • 我也有类似的项目需求。所以我们定义了一个特殊的“遥测”协议,它有一些定义的方法来访问特定类型的数据。对于最“通用”的访问,我们有一个用于访问特定内存的特殊命令。就是这样。
  • 您可以查看 Boost 的预处理器部分。它可能在 C 中工作,也可能不工作,但你可以移植它的某些部分。有了它,就可以创建一个宏(类似于ReflectEnum( (a , 1) (b , 2) ))来生成枚举和反射数据。
  • @Lou 你想在远程运行程序的内存中摆弄。已经有一个很棒的通用工具可以做到这一点,包括了解结构字段、变量名、枚举名、函数名等。它是一个调试器。 gdb 有一个远程调试协议,据我所知,它几乎可以讨论任何事情。我已经有将近 20 年没有使用它了,但当时我在 PC 上使用它来调试在 Sparc 上运行的内核,所以它有点符合您的意思。

标签: c serialization preprocessor x-macros


【解决方案1】:

借助 Boost 中的一些预处理器魔法,我们可以使宏能够生成可反射的枚举。

我设法构建了一个简单的概念验证实现,如下所示。


首先,用法。以下:

ReflEnum(MyEnum,
    (first)
    (second , 42)
    (third)
)

扩展为:

enum MyEnum
{
    first,
    second = 42,
    third,
};

const char *EnumToString_MyEnum(enum MyEnum param)
{
    switch (param)
    {
      case first:
        return "first";
      case second:
        return "second";
      case third:
        return "third";
      default:
        return "<invalid>";
    }
}

因此一个完整的程序可能如下所示:

#include <stdio.h>

/*
 * Following is generated by the below ReflEnum():
 *   enum MyEnum {first, second = 42, third};
 *   const char *EnumToString_MyEnum(enum MyEnum value) {}
*/
ReflEnum(MyEnum,
    (first)
    (second , 42)
    (third)
)

int main()
{
    enum MyEnum foo = second;
    puts(EnumToString_MyEnum(foo));  // -> "second"
    puts(EnumToString_MyEnum(43));   // -> "third"
    puts(EnumToString_MyEnum(9001)); // -> "<invalid>"
}

这是实现本身。

它由两部分组成。代码本身和预处理器魔术头无耻地从 Boost 中窃取。

代码:

#define ReflEnum_impl_Item(...) PPUTILS_VA_CALL(ReflEnum_impl_Item_, __VA_ARGS__)(__VA_ARGS__)
#define ReflEnum_impl_Item_1(name)        name,
#define ReflEnum_impl_Item_2(name, value) name = value,

#define ReflEnum_impl_Case(...) case PPUTILS_VA_FIRST(__VA_ARGS__): return PPUTILS_STR(PPUTILS_VA_FIRST(__VA_ARGS__));

#define ReflEnum(name, seq) \
    enum name {PPUTILS_SEQ_APPLY(seq, ReflEnum_impl_Item)}; \
    const char *EnumToString_##name(enum name param) \
    { \
        switch (param) \
        { \
            PPUTILS_SEQ_APPLY(seq, ReflEnum_impl_Case) \
            default: return "<invalid>"; \
        } \
    }

扩展代码以支持字符串->枚举转换应该不会困难;如果不确定,请在 cmets 中询问。

魔法:

请注意,预处理器魔法必须由脚本生成,并且您必须在生成它时选择最大枚举大小。这一代很简单,留给读者作为练习。

Boost 默认大小为64,下面的代码是为大小4生成的。

#define PPUTILS_E(...) __VA_ARGS__

#define PPUTILS_VA_FIRST(...) PPUTILS_VA_FIRST_IMPL_(__VA_ARGS__,)
#define PPUTILS_VA_FIRST_IMPL_(x, ...) x

#define PPUTILS_PARENS(...) (__VA_ARGS__)
#define PPUTILS_DEL_PARENS(...) PPUTILS_E __VA_ARGS__

#define PPUTILS_CC(a, b) PPUTILS_CC_IMPL_(a,b)
#define PPUTILS_CC_IMPL_(a, b) a##b

#define PPUTILS_CALL(macro, ...) macro(__VA_ARGS__)

#define PPUTILS_VA_SIZE(...) PPUTILS_VA_SIZE_IMPL_(__VA_ARGS__,4,3,2,1,0)
#define PPUTILS_VA_SIZE_IMPL_(i1,i2,i3,i4,size,...) size

#define PPUTILS_STR(...) PPUTILS_STR_IMPL_(__VA_ARGS__)
#define PPUTILS_STR_IMPL_(...) #__VA_ARGS__

#define PPUTILS_VA_CALL(name, ...) PPUTILS_CC(name, PPUTILS_VA_SIZE(__VA_ARGS__))

#define PPUTILS_SEQ_CALL(name, seq) PPUTILS_CC(name, PPUTILS_SEQ_SIZE(seq))

#define PPUTILS_SEQ_DEL_FIRST(seq) PPUTILS_SEQ_DEL_FIRST_IMPL_ seq
#define PPUTILS_SEQ_DEL_FIRST_IMPL_(...)

#define PPUTILS_SEQ_FIRST(seq) PPUTILS_DEL_PARENS(PPUTILS_VA_FIRST(PPUTILS_SEQ_FIRST_IMPL_ seq,))
#define PPUTILS_SEQ_FIRST_IMPL_(...) (__VA_ARGS__),

#define PPUTILS_SEQ_SIZE(seq) PPUTILS_CC(PPUTILS_SEQ_SIZE_0 seq, _VAL)
#define PPUTILS_SEQ_SIZE_0(...) PPUTILS_SEQ_SIZE_1
#define PPUTILS_SEQ_SIZE_1(...) PPUTILS_SEQ_SIZE_2
#define PPUTILS_SEQ_SIZE_2(...) PPUTILS_SEQ_SIZE_3
#define PPUTILS_SEQ_SIZE_3(...) PPUTILS_SEQ_SIZE_4
#define PPUTILS_SEQ_SIZE_4(...) PPUTILS_SEQ_SIZE_5
// Generate PPUTILS_SEQ_SIZE_i
#define PPUTILS_SEQ_SIZE_0_VAL 0
#define PPUTILS_SEQ_SIZE_1_VAL 1
#define PPUTILS_SEQ_SIZE_2_VAL 2
#define PPUTILS_SEQ_SIZE_3_VAL 3
#define PPUTILS_SEQ_SIZE_4_VAL 4
// Generate PPUTILS_SEQ_SIZE_i_VAL

#define PPUTILS_SEQ_APPLY(seq, macro) PPUTILS_SEQ_CALL(PPUTILS_SEQ_APPLY_, seq)(macro, seq)
#define PPUTILS_SEQ_APPLY_0(macro, seq)
#define PPUTILS_SEQ_APPLY_1(macro, seq) PPUTILS_CALL(macro, PPUTILS_SEQ_FIRST(seq))
#define PPUTILS_SEQ_APPLY_2(macro, seq) PPUTILS_CALL(macro, PPUTILS_SEQ_FIRST(seq)) PPUTILS_SEQ_CALL(PPUTILS_SEQ_APPLY_, PPUTILS_SEQ_DEL_FIRST(seq))(macro, PPUTILS_SEQ_DEL_FIRST(seq))
#define PPUTILS_SEQ_APPLY_3(macro, seq) PPUTILS_CALL(macro, PPUTILS_SEQ_FIRST(seq)) PPUTILS_SEQ_CALL(PPUTILS_SEQ_APPLY_, PPUTILS_SEQ_DEL_FIRST(seq))(macro, PPUTILS_SEQ_DEL_FIRST(seq))
#define PPUTILS_SEQ_APPLY_4(macro, seq) PPUTILS_CALL(macro, PPUTILS_SEQ_FIRST(seq)) PPUTILS_SEQ_CALL(PPUTILS_SEQ_APPLY_, PPUTILS_SEQ_DEL_FIRST(seq))(macro, PPUTILS_SEQ_DEL_FIRST(seq))
// Generate PPUTILS_SEQ_APPLY_i

【讨论】:

  • 酷,这本质上是 x 宏,但使用起来非常简单。
【解决方案2】:

“第一选择”通常是:

  • 将所有数据分组到由数组/结构组成的表中,最好是只读的,如第一个链接示例中所示。表索引用作将数据保持在一起的搜索键(“主键”使用 RDBMS 术语)。这是快速且可读的,但在维护期间必须小心。

  • 根据一些 OO 设计对数据进行分组。您可以使用不透明指针和函数指针来实现私有封装和多态。如果使用得当,这可以提供最先进的程序设计。但与此同时,编写起来可能会有些负担。如果您不能使用动态内存分配(嵌入式系统),那么您必须为每个类发明一个内存池。最适合更复杂的“ADT”类容器和 API 设计。

话虽如此,只要您不认为每个读者都熟悉 X 宏,它们在某种程度上是可以接受的。因此,我会留下一些关于宏列表如何工作、使用时如何扩展以及如何维护它们的问题。

从链接的代码示例中,#define X(dir) {dir, #dir} 行可能应该更恰当地注释如下:

/*
  Create a temporary X-macro that expands the DIRECTION_LIST, to form 
  an array initialization list. The format will be:

  {north, "north"},  
  {south, "south"},
  ...
*/
#define X(dir) {dir, #dir}
  DIRECTION_LIST
#undef X

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-09-16
    • 1970-01-01
    • 2023-03-12
    • 1970-01-01
    • 2017-09-19
    • 1970-01-01
    • 1970-01-01
    • 2015-11-27
    相关资源
    最近更新 更多