如何使用此方法将C宏扩展为结构体和函数

rdrgkggo  于 2023-02-18  发布在  其他
关注(0)|答案(4)|浏览(334)

我想要一个宏

#define MY_STRUCT( /* ... /*) /* ... */

我想这样称呼它

MY_STRUCT(point, double x, int y);

扩展到这个

typedef struct {
    double x;
    int y;
} point;

void init_point(point *p) {
     p->x = load_double_from_somewhere();
     p->y = load_int_from_somewhere();
}

宏应该能够处理我给予它的任意数量的参数。我尝试通过宏自动生成模型对象的数据绑定,因为我一直在手工编写Map函数,这很烦人且重复(从XML到C结构)。我知道这是可能的,但我不知道如何实现。

htzpubme

htzpubme1#

***注意:***我只是在解决如何编写struct定义的问题。我没有找到一种方法来让预处理器自动生成序列化和反序列化代码,原因有两个。首先,不可能从宏参数中提取标记--成员名称--即使您知道要提取哪个标记。其次,您可以使用C11的_Generic特性来创建对预定义类型特定的序列化/反序列化函数的调用。但是_Generic调用需要一个完整的可能性列表,因此不可能逐步地构建这些可能性。我想您会发现,自动生成序列化/反序列化代码的现有系统依赖于外部代码生成器。这是一个灵活得多(可能也更容易)的策略。

那么,回到自动生成结构声明。正如前面提到的,只有生成 N 个宏,每个宏对应一个参数,这才是真正可能的。而且,你还需要一种计算 N 的方法,这可能需要更多自动生成的宏。总而言之,使用像Jens Gustedt的P99这样的框架要容易得多。
下面是一个高度简化的示例,说明了如何执行此操作,由于模式很明显,因此字段数量有限。
我们从一个宏开始,它将计算变量调用中的参数个数。注意,如果没有变量参数,它将正确地返回0。一个健壮的解决方案,如P99,将正确地处理这个问题。但在这个例子中,这并不重要,因为没有成员的struct是C不允许的。所以我们知道这里至少有一个参数(在C++中不是这样的)。

#define ARG9_(a1, a2, a3, a4, a5, a6, a7, a8, a9, ...) a9
#define NARGS(...) ARG9_(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1)

现在,我们需要8个宏来构造成员,它们所要做的就是在参数之间插入分号,所以这不是很有挑战性:

#define S8_(first, ...) first; S7_(__VA_ARGS__)
#define S7_(first, ...) first; S6_(__VA_ARGS__)
#define S6_(first, ...) first; S5_(__VA_ARGS__)
#define S5_(first, ...) first; S4_(__VA_ARGS__)
#define S4_(first, ...) first; S3_(__VA_ARGS__)
#define S3_(first, ...) first; S2_(__VA_ARGS__)
#define S2_(first, ...) first; S1_(__VA_ARGS__)
#define S1_(first) first;

最后,我们需要把所有这些放在一起。为此,我们需要一个可以连接宏名称的宏:

#define PASTE3(a, b, c) PASTE3_(a, b, c)
#define PASTE3_(a, b, c) a ## b ## c

最后,创建结构体的宏

#define MY_STRUCT(name, ...)                       \
   typedef struct name name;                       \
   struct name {                                   \
     PASTE3(S, NARGS(__VA_ARGS__), _)(__VA_ARGS__) \
   };

现在我们可以给予一下:

MY_STRUCT(s1, int a);
MY_STRUCT(s2, int a, double b);
MY_STRUCT(s3, const char* s, int t[17], double sum);
MY_STRUCT(s4, char a, char b, char c, char d);
MY_STRUCT(s5, char a, char b, char c, char d, char e);
MY_STRUCT(s6, char a, char b, char c, char d, char e, char f);
MY_STRUCT(s7, char a, char b, char c, char d, char e, char f, char g);
MY_STRUCT(s8, char a, char b, char c, char d, char e, char f, char g, short h);

下面是gcc -E在上述所有条件下生成的结果:(注意:我不能评论这是否适用于MSVC的不同版本,但它都是标准的C99。)

# 1 "nargs.h"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "nargs.h"
# 22 "nargs.h"
typedef struct s1 s1; struct s1 { int a; };
typedef struct s2 s2; struct s2 { int a; double b; };
typedef struct s3 s3; struct s3 { const char* s; int t[17]; double sum; };
typedef struct s4 s4; struct s4 { char a; char b; char c; char d; };
typedef struct s5 s5; struct s5 { char a; char b; char c; char d; char e; };
typedef struct s6 s6; struct s6 { char a; char b; char c; char d; char e; char f; };
typedef struct s7 s7; struct s7 { char a; char b; char c; char d; char e; char f; char g; };
typedef struct s8 s8; struct s8 { char a; char b; char c; char d; char e; char f; char g; short h; };
2wnc66cl

2wnc66cl2#

我不知道你要它做什么,但是...

#define x(a,b,c)    struct a {b;c;}

x(mystr, int m, double n);

其结果是:

struct mystr {int m;double n;};

#define x(a,b,c)    typedef struct {b;c;} a

x(mystr, int m, double n);

结果是

typedef struct {int m;double n;} mystr;
j9per5c4

j9per5c43#

我不确定是否有可能从double x中提取x(除非您知道变量可能具有的所有类型)。
此外,使用预处理器迭代逗号分隔列表需要编写样板宏(我认为有O(n)个)。boost.preprocessor可能会对此有所帮助,但如果使用不同的语法,则完全可以避免样板宏:

MY_STRUCT(point, (double,x)(double,y))

实施:

#define MY_STRUCT(name, seq) \
    typedef struct { \
        MY_STRUCT_impl_end(MY_STRUCT_impl_decl_loop_a seq) \
    } name; \
    void init_point(name *p) { \
        MY_STRUCT_impl_end(MY_STRUCT_impl_load_loop_a seq) \
    }

#define MY_STRUCT_impl_end(...) MY_STRUCT_impl_end_(__VA_ARGS__)
#define MY_STRUCT_impl_end_(...) __VA_ARGS__##_end

#define MY_STRUCT_impl_decl_loop(type, name) type name;
#define MY_STRUCT_impl_decl_loop_a(...) MY_STRUCT_impl_decl_loop(__VA_ARGS__) MY_STRUCT_impl_decl_loop_b
#define MY_STRUCT_impl_decl_loop_b(...) MY_STRUCT_impl_decl_loop(__VA_ARGS__) MY_STRUCT_impl_decl_loop_a
#define MY_STRUCT_impl_decl_loop_a_end
#define MY_STRUCT_impl_decl_loop_b_end

#define MY_STRUCT_impl_load_loop(type, name) p->name = load_##name##_from_somewhere();
#define MY_STRUCT_impl_load_loop_a(...) MY_STRUCT_impl_load_loop(__VA_ARGS__) MY_STRUCT_impl_load_loop_b
#define MY_STRUCT_impl_load_loop_b(...) MY_STRUCT_impl_load_loop(__VA_ARGS__) MY_STRUCT_impl_load_loop_a
#define MY_STRUCT_impl_load_loop_a_end
#define MY_STRUCT_impl_load_loop_b_end
gcmastyq

gcmastyq4#

如果你想自动生成源代码,预处理器是你能得到的最糟糕的工具。
而且你绝对不能创建任何宏来生成一个有任意个字段的结构体;C预处理器不支持循环或递归。
但是如果最多8个字段/参数就足够了,尽管NON-STANDARD,在gcc和clang中也可以:

#define MY_STRUCT(name, ...)  struct name { C2SC(__VA_ARGS__,,,,,,,,,) }
#define C2SC(a, b, c, d, e, f, g, h, ...)       a; b; c; d; e; f; g; h;

MY_STRUCT(foo, int bar, int baz, void *quux, void *xuq, char sq[12]);

这会故意创建一个struct name { ... },而不是OP中的typedef struct { ... } name:即使没有实际生成typedefMY_STRUCT宏,它也已经够迟钝的了。

相关问题