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

问题描述 投票:0回答:2

长序言

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

#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 作为索引将始终引用

0x00ff00
中的值
color_map[]
color_names[]
中的字符串“COLOR_GREEN”,并且您可以轻松添加新颜色:

  ...
  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 预处理器,以便它将初始值设定项解释为单个参数吗?

(我想出的最好的方法是将结构值作为单独的参数传递给宏。这可行,但我的实际用例具有带有大量预设字段的复杂嵌套结构,因此最好将嵌套结构可见。)

c struct c-preprocessor
2个回答
4
投票

您可以使用

...
接受带逗号的“参数”:

#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) };

1
投票

基本答案

这对你有用吗?

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) },
};

我在答案的第一版中遇到的一个问题 - 不要调用宏参数

r
g
b
;如果你这样做,你最终会得到如下输出:

{ . 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 DEFINE_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 }
    },
};
© www.soinside.com 2019 - 2024. All rights reserved.