在编译时初始化灵活的数组成员

initialize flexible array members at compile time

我正在尝试优化嵌入式应用程序的启动时间和 运行 时间,为此,我想在编译时初始化一些常量灵活数组成员。我发现一些帖子说你必须使用 malloc,但理论上,应该可以在没有...

举个例子,假设我有:

typedef struct _foo_t {
   foo_t *next;
   int   num_bars;
   bar_t bars[];
} foo_t __attribute((__packed__));

我确实有数百万个 foo_t 实例。然后我有一个脚本来生成一个包含所有信息的头文件。所以我可能有这样的东西:

const foo_t foo1 = {.next = &foo2, .num_bars = 2};
const bar_t foo1_bar1 = {...};
const bar_t foo1_bar2 = {...};

foo_t *const first_foo = &foo1;

但是,这个问题是编译器规范不保证 &foo1_bar1 不保证在 &foo1.bars[0]。我想知道是否有人知道有什么技巧可以强制将 fooX_barY 成员放置在内存中的正确位置。

注意我的目标是:

如果有人知道这样做的任何好技巧,我很想听听他们

GCC 和 clang 似乎支持灵活数组的标准初始值设定项,因此如果您可以限制自己使用这些编译器,则只需添加用于 const 删除的强制转换。初始化器不必使用指定成员,经典初始化器至少在 clang.

下工作正常

这个扩展实际上与数组初始化器的 C 语法非常一致,如果可以从初始化器确定数组的长度,则可以省略数组的长度。

对于不支持这种语法的编译器,这里有一个技巧可以实现你想要的:

//----------------
// you can move these to foo.h
typedef int bar_t;

typedef struct foo_t {
    struct foo_t *next;
    char name[8];
    int num_bars;
    bar_t bars[];
} foo_t;

extern foo_t * const first_foo;
extern foo_t * const second_foo;

//----------------
// The definitions can be generated automatically into a separate module

// disable warnings cf: 
#pragma GCC diagnostic ignored "-Wcast-qual"
#pragma clang diagnostic ignored "-Wcast-qual"

// Trick for VLA initialization
#define foo_t(n)  struct { foo_t *next; char name[8]; int num_bars; bar_t bars[n]; }

// using a classic structure initializers
static const foo_t(2) foo2 = { 0,              "foo2", 2, { 1, 2 }};
static const foo_t(1) foo1 = { (foo_t *)&foo2, "foo1", 1, { 42 }};
foo_t * const first_foo = (foo_t *)&foo1;

// using a compound literal
foo_t * const second_foo = (foo_t *)&(foo_t(3)){ 0, "foo3", 3, { 10, 20, 30 }};

// using gcc / clang flexible array initializer
#pragma clang diagnostic ignored "-Wgnu-flexible-array-initializer"
static foo_t third_foo = { 0, "foo4", 2, { 1, 2 }};

//----------------
// Test framework

#include <stdio.h>

void foo_print(const char *name, foo_t *p) {
    printf("%s: {\n", name);
    for (; p; p = p->next) {
        printf("  { \"%s\", %d, { ", p->name, p->num_bars);
        for (int i = 0; i < p->num_bars; i++)
            printf("%d, ", p->bars[i]);
        printf("}},\n");
    }
    printf("}\n");
}

int main() {
    foo_print("first_foo", first_foo);
    foo_print("second_foo", second_foo);
    foo_print("third_foo", &third_foo);
    return 0;
}

输出:

first_foo: {
  { "foo1", 1, { 42, }},
  { "foo2", 2, { 1, 2, }},
}
second_foo: {
  { "foo3", 3, { 10, 20, 30, }},
}
third_foo: {
  { "foo4", 2, { 1, 2, }},
}

您尝试使用相关变量的特定顺序很容易出错,因为编译器不需要在您想要的位置分配变量。

使用静态变量而不是动态分配内存的唯一方法是将灵活数组的成员直接添加到变量的初始化程序中:

const foo_t foo1 =
{
  .next = &foo2,
  .num_bars = 2,
  .bars =
  { [0] = {...},
    [1] = {...}
  }
};

专用初始化程序是可选的。

至少对于 GCC 这应该有效。

遗憾的是,无法使用常见的 (sizeof(arr)/sizeof(arr[0])) 技巧来获取数组中的元素数量:

foo_t foo1 =
{
  .next = &foo2,
  .bars =
  { [0] = 1,
    [1] = 2
  },
  .num_bars = sizeof(foo1.bars)/sizeof(foo1.bars[0]),
};
test.c:21:21: error: invalid application of ‘sizeof’ to incomplete type ‘bar_t[]’ {aka ‘int[]’}
   21 |   .num_bars = sizeof(foo1.bars)/sizeof(foo1.bars[0]),
      |                     ^