通过宏组合两个指定的初始化程序

Combine two designated initializers via a macro

在一个嵌入式项目中,我使用了一个库,它提供了一个用于初始化结构的宏。这提供了合理的默认值,但默认值取决于其他参数。我想覆盖此指定初始化程序的一个或多个值,因为之后初始化这些值会产生开销。

理想情况下,我不想复制粘贴所有宏,因为这样我就必须管理第三方代码。如果库更改它的默认值,我也不想这样做。

有没有一种方法可以合并或覆盖 designated initializers,这样就没有开销了?代码必须符合 C99 标准且可移植。

演示该问题的示例代码:

#if SITUATION
#define LIBRARY_DEFAULTS \
{ \
  .field_a = 1, \
  .field_b = 2, \
  .field_c = 3 \
}
#else
#define LIBRARY_DEFAULTS \
{ \
  .field_a = 100, \
  .field_b = 200, \
  .field_c = 300, \
  .field_d = 400, \
  .field_e = 500 \
}
#endif

/* The following is what I want (or similar), but (of course) doesn't 
   work. */
// #define MY_DEFAULTS = LIBRARY_DEFAULTS + { .field_a = 100 }

int main(void) {
    /* The exact definition of something also depends on situation. */
    struct something library_thing = LIBRARY_DEFAULTS;

    /* This generates overhead, and I want to avoid this. It is certain
       that the field exists. */
    library_thing.field_a = 100;
}

您可以将 library_thing 包装在外部结构中,并从外部结构的初始值设定项进行覆盖:

#include <stdio.h>

struct foo {
    int a,b,c;
};

#define FOO_DEFAULTS { .a = 1, .b = 2, .c = 3 }

int main() {
    struct {
        struct foo x;
    } baz = {
        .x = FOO_DEFAULTS,
        .x.a = 4,
    };

    printf("%d\n", baz.x.a); // prints 4
}

其实你甚至可以做到

.x = FOO_DEFAULTS,
.x = {.a = 4},

如果你真的需要 "merge" 两个初始值设定项。

这在 Clang (7.0.2) 上编译良好,但在 -Winitializer-overrides 下会生成警告。检查生成的代码确认结构是用 4, 2, 3 初始化的,所以这个技巧没有额外的开销。

这是一种可能的解决方案。先去掉宏中的大括号

#define LIBRARY_DEFAULTS .a=1, .b=2, .c=3

然后对于默认值可以的变量,将宏括在大括号中

struct something standard = { LIBRARY_DEFAULTS };

对于需要调整默认值的变量,添加一些额外的初始值设定项

struct something tweaked = { LIBRARY_DEFAULTS, .a=100 };

为什么这行得通? C 规范的第 6.7.9 节讨论了初始化列表中指示符的使用,并且对多次指定相同的指示符有以下说明:

19 The initialization shall occur in initializer list order, each initializer provided for a particular subobject overriding any previously listed initializer for the same subobject;151) all subobjects that are not initialized explicitly shall be initialized implicitly the same as objects that have static storage duration.

注释 151 是这样说的

151) Any initializer for the subobject which is overridden and so not used to initialize that subobject might not be evaluated at all.

也就是说,编译器需要使用最后指定的初始化程序,并且可以实现此解决方案而无需任何开销。