Home > Mobile >  initialize flexible array members at compile time
initialize flexible array members at compile time

Time:05-31

I'm trying to optimize boot-time and run-time of an embedded application, and to do that, I'd like to initialize some constant flexible array members at compile time. I've found a few posts that say you must use malloc, but theoretically, it should be possible to do this without...

As an example, say I have:

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

And I have literally have several million instances of foo_t. I then have a script to generate a header file with all the information. So I might have something like this:

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;

But, the problem with this is that the compiler spec does not guarantee that &foo1_bar1 is not guaranteed to be at &foo1.bars[0]. I'm wondering if there's any trickery anyone knows to force the fooX_barY members to be placed in the correct locations in memory.

Note that my goals are:

  • reduce boot time by avoiding unnecessary mallocs
  • reduce memory thrashing by having the bar's typically in the same cache pages as corresponding foos
  • reduce memory requirements by not having pointers to bars

If anyone knows of any good tricks to do this, I'd love to hear them

CodePudding user response:

GCC and clang seem to support standard initializers for flexible arrays so if you can restrict yourself to these compilers, you only need to add casts for const removal. The initializer does not have to use designated members, classic intializers work fine at least for clang.

This extension is actually quite consistent with the C syntax for array initializers where the length of the array can be omitted if it can be determined from the initializer.

For compilers that do not support this syntax, here is a trick to achieve what you want:

//----------------
// 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: https://stackoverflow.com/a/55877109/4593267
#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;
}

Output:

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

CodePudding user response:

Your attempts using certain ordering of the related variables is prone to error as the compiler does not need to allocate the variables at the place you want.

The only way to use static variables instead of dynamically allocated memory is to add the members of that flexible array directly into the initializer of your variable:

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

Dedicated initializers are optional.

At least for GCC this should work.

Unfortunately it is not possible to use the common (sizeof(arr)/sizeof(arr[0])) trick to get the number of elements in an array:

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]),
      |                     ^
  • Related