将逗号分隔的结构初始值设定项传递给 C 预处理器宏

Passing comma-separated struct initializers to C preprocessor macros

长序言

在许多情况下,将枚举与数组元素相关联很有用,这样枚举名称始终与数组元素保持同步。对于这种事情,“定义宏的宏”可以很好地完成工作:

#define EXPAND_COLORS                 \
  DEFINE_COLOR(COLOR_RED, 0xff0000)   \
  DEFINE_COLOR(COLOR_GREEN, 0x00ff00) \
  DEFINE_COLOR(COLOR_BLUE, 0x0000ff)

// The following expands into an enum with a defined symbol for each color:
//     typedef enum { COLOR_RED, COLOR_GREEN, COLOR_BLUE, } color_t;
//
#undef DEFINE_COLOR
#define DEFINE_COLOR(_name, _value) _name,
typedef enum { EXPAND_COLORS } color_t;

// The following expands in to an array of color values:
//     int color_map[] = { 0xff0000, 0x00ff00, 0x0000ff, };
//
#undef DEFINE_COLOR
#define DEFINE_COLOR(_name, _value) _value,
int color_map[] = { EXPAND_COLORS };

// The following expands into an array of strings, one string for each color:
//     const char *color_names[] = { "COLOR_RED", "COLOR_GREEN", "COLOR_BLUE", };
//
#undef DEFINE_COLOR
#define DEFINE_COLOR(_name, _value) #_name,
const char *color_names[] = { EXPAND_COLORS };

您可以看到这如何保证使用符号 COLOR_GREEN 作为索引将始终引用 color_map[] 中的值 0x00ff00 和字符串“COLOR_GREEN”在 color_names[] 中,您可以轻松添加新颜色:

  ...
  DEFINE_COLOR(COLOR_AUBERGENE, 0x693b58)  \

...而且你只需要在一个地方添加即可。

现在开始提问

如果我想使用一个结构而不是将颜色表示为单个十六进制值会怎么样:

typedef struct {
  uint8_t r;
  uint8_t g;
  uint8_t b;
} color_t;

我想做这样的事情:

#define EXPAND_COLORS                                              \
  DEFINE_COLOR(COLOR_RED, {.r = 0xff, .g = 0x00, .b = 0x00})       \
  DEFINE_COLOR(COLOR_GREEN, {.r = 0x00, .g = 0xff, .b = 0x00})     \
  DEFINE_COLOR(COLOR_BLUE, {.r = 0x00, .g = 0x00, .b = 0xff})      \
  DEFINE_COLOR(COLOR_AUBERGENE, {.r = 0x69, .g = 0x3b, .b = 0x58})

// I would this to expand to:
//     color_t color_map[] = { {.r = 0xff, .g = 0x00, .b = 0x00}, 
//                             {.r = 0x00, .g = 0xff, .b = 0x00}, 
//                             ...
//                           }; 
#undef DEFINE_COLOR
#define DEFINE_COLOR(_name, _value) _value,
color_t color_map[] = { EXPAND_COLORS };

上面写的不会起作用,因为 C 预处理器将逗号视为字段分隔符:

error: macro "DEFINE_COLOR" passed 4 arguments, but takes just 2

是否有一些语法技巧可以用来将结构初始化程序传递给 C 预处理器,以便它将初始化程序解释为单个参数?

(我想出的最好办法是将结构值作为单独的参数传递给宏。这可行,但我的实际用例具有复杂的嵌套结构和许多预设字段,所以它会是最好使嵌套结构可见。)

基本答案

这对你有用吗?

typedef struct {
  uint8_t r;
  uint8_t g;
  uint8_t b;
} color_t;

#define INIT_RGB(rv, gv, bv) { .r = (rv), .g = (gv), .b = (bv) }

#define EXPAND_COLORS                                         \
  DEFINE_COLOR(COLOR_RED,       INIT_RGB(0xFF, 0x00, 0x00))   \
  DEFINE_COLOR(COLOR_GREEN,     INIT_RGB(0x00, 0xFF, 0x00))   \
  DEFINE_COLOR(COLOR_BLUE,      INIT_RGB(0x00, 0x00, 0xFF))   \
  DEFINE_COLOR(COLOR_AUBERGINE, INIT_RGB(0x69, 0x3B, 0x58))

#undef DEFINE_COLOR
#define DEFINE_COLOR(_name, _value) _value,

color_t color_map[] = { EXPAND_COLORS };

运行 cpp xmacro71.c,我得到了输出:

# 0 "xmacro71.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "xmacro71.c"

typedef struct {
  uint8_t r;
  uint8_t g;
  uint8_t b;
} color_t;
# 19 "xmacro71.c"
color_t color_map[] = { { .r = (0xFF), .g = (0x00), .b = (0x00) }, { .r = (0x00), .g = (0xFF), .b = (0x00) }, { .r = (0x00), .g = (0x00), .b = (0xFF) }, { .r = (0x69), .g = (0x3B), .b = (0x58) }, };

重新格式化,结果:

# 0 "xmacro71.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "xmacro71.c"

typedef struct {
  uint8_t r;
  uint8_t g;
  uint8_t b;
} color_t;
# 19 "xmacro71.c"
color_t color_map[] =
{
    { .r = (0xFF), .g = (0x00), .b = (0x00) },
    { .r = (0x00), .g = (0xFF), .b = (0x00) },
    { .r = (0x00), .g = (0x00), .b = (0xFF) },
    { .r = (0x69), .g = (0x3B), .b = (0x58) },
};

我 运行 在第一版答案中遇到的一个问题 — 不要调用宏参数 rgb;如果这样做,您最终会得到如下输出:

{ . 0xFF = (0xFF), . 0x00 = (0x00), . 0x00 = (0x00) },

这不是你想要的!

如果您希望保持初始化程序的显式可见,您还可以使用:

/* SO 7121-4579 */
typedef struct {
  uint8_t r;
  uint8_t g;
  uint8_t b;
} color_t;

#define INIT_RGB(rv, gv, bv) { rv, gv, bv }

#define EXPAND_COLORS                                                      \
  DEFINE_COLOR(COLOR_RED,       INIT_RGB(.r = 0xFF, .g = 0x00, .b = 0x00)) \
  DEFINE_COLOR(COLOR_GREEN,     INIT_RGB(.r = 0x00, .g = 0xFF, .b = 0x00)) \
  DEFINE_COLOR(COLOR_BLUE,      INIT_RGB(.r = 0x00, .g = 0x00, .b = 0xFF)) \
  DEFINE_COLOR(COLOR_AUBERGINE, INIT_RGB(.r = 0x69, .g = 0x3B, .b = 0x58))

#undef DEFINE_COLOR
#define DEFINE_COLOR(_name, _value) _value,

color_t color_map[] = { EXPAND_COLORS };

这产生基本相同的输出 — 区别主要在于此示例中的值周围没有括号(缺失是因为它们不存在于 .r = (0xFF) 等参数中)。

扩展答案

处理嵌套结构是可行的。这对我也有效:

/* SO 7121-4579 */
typedef struct
{
    uint8_t r;
    uint8_t g;
    uint8_t b;
} color_t;

#define INIT_RGB(rv, gv, bv) { rv, gv, bv }

#define EXPAND_COLORS                                                      \
    DEFINE_COLOR(COLOR_RED,       INIT_RGB(.r = 0xFF, .g = 0x00, .b = 0x00)) \
    DEFINE_COLOR(COLOR_GREEN,     INIT_RGB(.r = 0x00, .g = 0xFF, .b = 0x00)) \
    DEFINE_COLOR(COLOR_BLUE,      INIT_RGB(.r = 0x00, .g = 0x00, .b = 0xFF)) \
    DEFINE_COLOR(COLOR_AUBERGINE, INIT_RGB(.r = 0x69, .g = 0x3B, .b = 0x58))

#undef DEFINE_COLOR
#define DEFINE_COLOR(_name, _value) _value,

color_t color_map[] = { EXPAND_COLORS };

#undef DEFINE_COLOR
#define DEFINE_COLOR(name, value)   name,
enum
{
    EXPAND_COLORS
    MAX_COLOR_NAME
};
#undef DEFINE_COLOR

typedef struct
{
    color_t tl_corner;
    color_t tr_corner;
    color_t bl_corner;
    color_t br_corner;
} gradient_t;

#define RGB_RED       INIT_RGB(.r = 0xFF, .g = 0x00, .b = 0x00)
#define RGB_GREEN     INIT_RGB(.r = 0x00, .g = 0xFF, .b = 0x00)
#define RGB_BLUE      INIT_RGB(.r = 0x00, .g = 0x00, .b = 0xFF)
#define RGB_AUBERGINE INIT_RGB(.r = 0x69, .g = 0x3B, .b = 0x58)

#define EXPAND_GRADIENT \
    DEFINE_GRADIENT(VARIANT1, RGB_RED, RGB_GREEN, RGB_BLUE, RGB_AUBERGINE) \
    DEFINE_GRADIENT(VARIANT2, RGB_GREEN, RGB_BLUE, RGB_AUBERGINE, RGB_RED) \
    DEFINE_GRADIENT(VARIANT3, RGB_BLUE, RGB_AUBERGINE, RGB_RED, RGB_GREEN) \
    DEFINE_GRADIENT(VARIANT4, RGB_AUBERGINE, RGB_RED, RGB_GREEN, RGB_BLUE)

#undef DEFUNE_GRADIENT
#define DEFINE_GRADIENT(name, tlc, trc, blc, brc) \
    { .tl_corner = tlc, .tr_corner = trc, .bl_corner = blc, .br_corner = brc },

static gradient_t boxes[] = { EXPAND_GRADIENT };

重新格式化后的输出(添加换行符和空格,并删除一些宏定义对应的换行符):

# 0 "xmacro97.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "xmacro97.c"

typedef struct
{
    uint8_t r;
    uint8_t g;
    uint8_t b;
} color_t;
# 20 "xmacro97.c"
color_t color_map[] =
{
    { .r = 0xFF, .g = 0x00, .b = 0x00 },
    { .r = 0x00, .g = 0xFF, .b = 0x00 },
    { .r = 0x00, .g = 0x00, .b = 0xFF },
    { .r = 0x69, .g = 0x3B, .b = 0x58 },
};

enum
{
    COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_AUBERGINE,
    MAX_COLOR_NAME
};

typedef struct
{
    color_t tl_corner;
    color_t tr_corner;
    color_t bl_corner;
    color_t br_corner;
} gradient_t;
# 54 "xmacro97.c"
static gradient_t boxes[] =
{
    {
        .tl_corner = { .r = 0xFF, .g = 0x00, .b = 0x00 },
        .tr_corner = { .r = 0x00, .g = 0xFF, .b = 0x00 },
        .bl_corner = { .r = 0x00, .g = 0x00, .b = 0xFF },
        .br_corner = { .r = 0x69, .g = 0x3B, .b = 0x58 }
    },
    {
        .tl_corner = { .r = 0x00, .g = 0xFF, .b = 0x00 },
        .tr_corner = { .r = 0x00, .g = 0x00, .b = 0xFF },
        .bl_corner = { .r = 0x69, .g = 0x3B, .b = 0x58 },
        .br_corner = { .r = 0xFF, .g = 0x00, .b = 0x00 }
    },
    {
        .tl_corner = { .r = 0x00, .g = 0x00, .b = 0xFF },
        .tr_corner = { .r = 0x69, .g = 0x3B, .b = 0x58 },
        .bl_corner = { .r = 0xFF, .g = 0x00, .b = 0x00 },
        .br_corner = { .r = 0x00, .g = 0xFF, .b = 0x00 }
    },
    {
        .tl_corner = { .r = 0x69, .g = 0x3B, .b = 0x58 },
        .tr_corner = { .r = 0xFF, .g = 0x00, .b = 0x00 },
        .bl_corner = { .r = 0x00, .g = 0xFF, .b = 0x00 },
        .br_corner = { .r = 0x00, .g = 0x00, .b = 0xFF }
    },
};

您可以使用 ... 来接受其中包含逗号的“参数”:

#define DEFINE_COLOR(_name, ...) __VA_ARGS__,

就是说,让宏成为一个参数(IMO)要好得多,这样你就可以给它一个合理的名字,而不必 #undef 并重新定义同一个宏,所以更清楚是什么进行中:

#define COLOR_LIST(M)                                   \
  M(COLOR_RED, {.r = 0xff, .g = 0x00, .b = 0x00})       \
  M(COLOR_GREEN, {.r = 0x00, .g = 0xff, .b = 0x00})     \
  M(COLOR_BLUE, {.r = 0x00, .g = 0x00, .b = 0xff})      \
  M(COLOR_AUBERGENE, {.r = 0x69, .g = 0x3b, .b = 0x58})


#define COLOR_ENUM_VAL(_name, ...) _name,
#define COLOR_INIT_VAL(_name, ...) __VA_ARGS__,

enum colors { COLOR_LIST(COLOR_ENUM_VAL) };
color_t color_map[] = { COLOR_LIST(COLOR_INIT_VAL) };