如何在没有x宏的情况下在C中获得类似反射的功能
与之相关,我发现了一篇文章,该文章使用x宏创建“开箱即用”结构序列化所需的结构元数据。我也见过类似的“智能枚举”技术,但它归结为相同的原理,获取枚举的字符串表示形式,或通过名称获取结构的字段值,或类似的内容 然而,在堆栈溢出方面经验丰富的C程序员表示,应避免使用x宏作为“最后手段”:如何在没有x宏的情况下在C中获得类似反射的功能,c,serialization,preprocessor,x-macros,C,Serialization,Preprocessor,X Macros,与之相关,我发现了一篇文章,该文章使用x宏创建“开箱即用”结构序列化所需的结构元数据。我也见过类似的“智能枚举”技术,但它归结为相同的原理,获取枚举的字符串表示形式,或通过名称获取结构的字段值,或类似的内容 然而,在堆栈溢出方面经验丰富的C程序员表示,应避免使用x宏作为“最后手段”: 我可能会找到更多相关的帖子,但不幸的是我没有给它们添加书签,所以这只是一些Google fu 也许正确的答案是这样的?但是,为什么用不同的语言(.proto定义)创建结构定义,然后运行构建步骤来生成C文件
.proto
定义)创建结构定义,然后运行构建步骤来生成C文件比使用内置预处理器来完成同样的事情更可取呢?问题是这些技术仍然不允许我按名称检索单个结构,我必须在两个项目之间共享相同的定义并保持它们同步
因此,问题是:如果x-宏是“最后手段”,那么解决我的问题的方法(当从不同的设备请求时轻松序列化各种内部数据)是“第一手段”,还是在诉诸宏地狱之前的任何方法?通常的“第一手段”是:
- 将所有数据分组到由数组/结构组成的表中,最好是只读的,如第一个链接示例所示。表索引用作将数据保存在一起的搜索键(“主键”用于使用RDBMS术语)。这是快速且可读的,但在维护期间必须小心
- 根据一些OO设计对数据进行分组。您可以使用不透明指针和函数指针来实现私有封装和多态性。正确使用时,可以提供最先进的程序设计。但与此同时,写起来可能有点麻烦。如果不能使用动态内存分配(嵌入式系统),则必须为每个类创建一个内存池。最适用于更复杂的“ADT”类容器和API设计
#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
#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中的一些预处理器魔法,我们可以使宏能够生成可反射枚举 我设法构建了一个简单的概念验证实现,如下所示
首先是用法。以下:
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>";
}
}
不要再考虑“一般”的问题,而要处理一个具体的案例。这就是我多年来一直处于这种困境后得出的结论。@EugeneSh:这是一个实际的项目,我们有一个微控制器操作的设备,可以与其他设备通信,但其中一个要求是我们必须能够查询其状态(即按需访问内存结构)。到目前为止,我们只是为每个查询创建了不同的消息,然后我们将使用不同的手工函数解析请求并序列化每个特定的消息类型。因此,这种方法看起来工作量更小,意味着出错的空间更小。我也有类似的项目需求。因此,我们定义了一个特殊的“遥测”协议,该协议定义了一些访问特定类型数据的方法。对于最“通用”的访问,我们有一个访问特定内存的特殊命令。就是这样。你可以看看Boost的预处理器部分。它可能在C中工作,也可能不工作,但您可以移植它的某些部分。有了它,就可以生成一个宏(类似于
ReflectEnum((a,1)(b,2))
),它将同时生成枚举和反射数据。@Lou您想在远程运行程序的内存中摆弄一下。已经有一个很棒的通用工具可以做到这一点,包括了解结构字段、变量名、枚举名、函数名等。它是一个调试器。gdb有一个远程调试协议,据我记忆所及,它几乎可以讨论任何事情。我已经有20年没用过它了,但那时候我在PC上用它调试运行在Sparc上的内核,所以它有点符合你所说的。很酷,这基本上是x宏,但使用起来非常简单。
#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>"; \
} \
}
#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