如何在没有函数调用和宏的情况下填充指针数组?

How to fill a pointer array without a function call and from a macro?

我有一个小的递归 AST 如下:

enum ABC
{
    A,
    B,
    C
};

typedef struct abc_t
{
    enum ABC kind;
    union {
        struct a_
        {
            int x;
        } a_;
        struct b_
        {
            int y;
        } b_;
        struct c_
        {
            struct abc_t **array;
        } c_;
    } u;
} abc_t;

为了简化构造,我定义了填充宏:

#define ABC_SET_A(n)    (abc_t) { .kind = A, .u.a_ = { n } }
#define ABC_SET_B(n)    (abc_t) { .kind = B, .u.b_ = { n } }

const abc_t bar = ABC_SET_A(5);
const abc_t foo = ABC_SET_B(2);

但是,我不知道如何用宏填充我的 AST 的 "c_" 分支。我想得出这样的结果:

const abc_t test = ABC_SET_C({ABC_SET_B(4), ABC_SET_A(3), ABC_SET_B(2)});
// or
const abc_t test = ABC_SET_C(ABC_SET_B(4), ABC_SET_A(3), ABC_SET_B(2)); // With variadics arguments

目标是拥有一个 abc_t 的不可变数组,代表一组预构建的结构,例如:

const abc_t pre_builds[] =
{
    ABC_SET_A(10),                            // pre_builds[0]
    ABC_SET_C({ABC_SET_A(8), ABC_SET_B(4)}),  // pre_builds[1]
    ABC_SET_B(23)                             // pre_builds[2]
    ...
};

我不知道如何处理 such 宏的递归结构,尤其是在这种情况下的数组。 我该怎么做?

首先修正你的 ABC_SET_AABC_SET_B。你不用像 int var = { 1 }; 这样的大括号初始化 int ,你像 int var = 1;:

这样初始化 int
#define ABC_SET_A(n)    (abc_t){ .kind = A, .u.a_ = n }
#define ABC_SET_B(n)    (abc_t){ .kind = B, .u.b_ = n }

ABC_SET_C 很容易。当你的结构中有指针数组时,你可以这样做:

#define ABC_SET_C(...)  (abc_t){ .kind = C, .u.c_ = (abc_t*[]) __VA_ARGS__ }
const abc_t pre_builds[] =
{
    ABC_SET_A(10),                            // pre_builds[0]
    // note - as this is array of pointers, I added & to get the pointer
    ABC_SET_C({&ABC_SET_A(8), &ABC_SET_B(4)}),  // pre_builds[1]
    ABC_SET_B(23)                             // pre_builds[2]
};

无论如何,{ } 大括号对预处理器来说并不特殊,也不会被它解析。我更喜欢:

#define ABC_SET_C(...)  (abc_t){ .kind = C, .u.c_ = (abc_t*[]) { __VA_ARGS__ } }
const abc_t pre_builds[] =
{
    ABC_SET_A(10),                            // pre_builds[0]
    ABC_SET_C(&ABC_SET_A(8), &ABC_SET_B(4)),  // pre_builds[1]
    ABC_SET_B(23)                             // pre_builds[2]
};

如果你想摆脱在每个 ABC_SET_* 前面写 &,那么你可以在参数数量上重载宏,并为每个参数插入它们。我发现它不值得付出努力,但它最多可以为 3 个参数实现:

#define ABC_SET_A(n)    ((abc_t){ .kind = A, .u.a_ = n })
#define ABC_SET_B(n)    ((abc_t){ .kind = B, .u.b_ = n })
#define ABC_SET_C_1(_1)        &_1
#define ABC_SET_C_2(_1,_2)     &_1, &_2
#define ABC_SET_C_3(_1,_2,_3)  &_1, &_2, &_3
#define ABC_SET_C_N(_1,_2,_3,N,...)  ABC_SET_C##N
#define ABC_SET_C(...)  (abc_t){ .kind = C, .u.c_ = (abc_t*[]) { \
        ABC_SET_C_N(__VA_ARGS__,_3,_2,_1)(__VA_ARGS__) \
    } }

const abc_t pre_builds[] = {
    ABC_SET_A(10),                            // pre_builds[0]
    ABC_SET_C(ABC_SET_A(8), ABC_SET_B(4)),  // pre_builds[1]
    ABC_SET_B(23)                             // pre_builds[2]
};

我个人不喜欢在宏变量初始化中写(abc_t)复合文字。他们让 abc_t *arr = (abc_t[]){ ABC_SET_A(8) } 之类的事情变得不可能。如果用户需要,我更喜欢写 &(abc_t) ,这样其他人就会知道什么是指针,什么不是指针,这样他们就知道何时以及如何分配内存,内存的存储持续时间是多少内存等。它还使宏易于在 C++ 和 C99 中工作。像这样:

#define ABC_SET_A(n)    { .kind = A, .u.a_ = n }
#define ABC_SET_B(n)    { .kind = B, .u.b_ = n }
#define ABC_SET_C(...)  { .kind = C, .u.c_ = __VA_ARGS__ }
const abc_t pre_builds[] = {
    ABC_SET_A(10),                            // pre_builds[0]
    ABC_SET_C((abc_t*[]){ &(abc_t)ABC_SET_A(8), &(abc_t)ABC_SET_B(4) }),  // pre_builds[1]
    ABC_SET_B(23)                             // pre_builds[2]
};

// it also allows for something like the following naturally to work:
abc_t *make_me_visible_in_debugger[] = {
    &(abc_t)ABC_SET_A(8),
    &(abc_t)ABC_SET_B(4)
};
const abc_t pre_builds2[] = {
    ABC_SET_A(10),                            // pre_builds[0]
    ABC_SET_C(make_me_visible_in_debugger),  // pre_builds[1]
    ABC_SET_B(23)                             // pre_builds[2]
};