1

I have a complex data structure (with lots of incomplete array types / heterogenous length arrays of structures and pointer to structures to arrays of structs ...)

I would like to put those in flash memory, so I thought about putting them in const objects of static storage (they would be stored on flash) and let the compiler do its job.

I'm working on embedded environment, where ROM==flash==data that i cannot physically change. I have few RAM and certainly not enough to store all my data. GCC can be told to put store static const data to ROM with no issues.

The data cannot be constructed dynamically, as it should stay in flash.

I'm currently using C (not C++), with a 4.8 gcc, and don't mind about using GCCisms.

However I keep encoutering error messages like :

  • initializer element is not constant
  • incompatible pointer types
  • ...

differently with different very recent gcc releases, suggesting this functionality (mixing compound literals, designated initializers , ...) is recent or not mainline.

Note that this code is generated by a program (script).

Besides the errors I keep making (I could be more specific and ask for help there), what strategy would you suggest :

  • keep trying using complex, nested literal structures using compound literals
    • having a massive type compound literal
    • having several compound literals types pointing to each other
  • building many intermediary objects with their names (rendering the whole thing quite unreadable)
  • building a big uint32_t datablob[] and casting my structures appropriately (with bonus unability to not be able to store pointers between objects as my linker will specify where this will end up)

  • any other options ?

(edit : added specifics)

Well, my question was more about a generic strategy, but here is an example :

struct A
{
    int id; 
    int codes[]; 
};

struct B 
{
    int b_member;
    struct A *a[]; // array of ptr to A objects
};


struct C 
{
    int c_member;
    struct B *objects[]; // array of ptrs on B
};

const struct A rom_data = { .id=4, .codes = {1,2,3,4}}; // this is in ROM

int main(void) {}

I would like to declare like I did for A an array of C structs. (That means I don't want to copy data, read it from disk or malloc it, just declare a const with the data in place.)

All examples I have about literals are very simple.

The specifics of my platform is an ARM microcontroller, but just consider I want to declare a const.

10
  • 1
    Without seeing some example code, it's difficult to comment on the specific errors you're seeing... Commented Sep 3, 2014 at 20:21
  • I suggest putting together a simplified example that demonstrates what you're having trouble with. As it stands, the question is much too broad. Commented Sep 3, 2014 at 20:21
  • sure, my question was more about the strategy to use than specific errors, but I could provide specifics.
    – makapuf
    Commented Sep 3, 2014 at 20:21
  • initializer element is not constant, is at least due to char array[int* fromsomewhereelse] = "";
    – JoeManiaci
    Commented Sep 3, 2014 at 20:26
  • storing the struct bottom-up and turning the pointer into file offset could be a proper solution.
    – Jason Hu
    Commented Sep 3, 2014 at 20:29

3 Answers 3

2

You should use const-qualified pointers instead of flexible array members.

Have some example code:

#include <stddef.h>

struct A
{
    int id; 
    const int *codes; 
};

struct B 
{
    int b_member;
    const struct A *const *a;
};

struct C 
{
    int c_member;
    const struct B *const *objects;
};

static const struct C ROOT = { 0, (const struct B *[]){
    &(const struct B){ 0, (const struct A *[]){
        &(const struct A){ 0, (const int []){ 1, 2, 3 } },
        &(const struct A){ 1, (const int []){ 0 } },
    } },
    &(const struct B){ 42, NULL },
} };

As mentioned in the comments, it seems to be unnecessary to reference the structures by pointers. This simplifies the code:

#include <stddef.h>

struct A
{
    int id; 
    const int *codes; 
};

struct B 
{
    int b_member;
    const struct A *a;
};

struct C 
{
    int c_member;
    const struct B *objects;
};

static const struct C ROOT = { 0, (const struct B []){
    { 0, (const struct A []){
        { 0, (const int []){ 1, 2, 3 } },
        { 1, (const int []){ 0 } },
    } },
    { 42, NULL },
} };

If you want or need C90 compatibility, you could flatten your tree and have the generating script keep track of offsets within the corresponding arrays:

static const int ARRAY_OF_INT[] = {
    1, 2, 3,
    0,
};

static const struct A ARRAY_OF_A[] = {
    { 0, ARRAY_OF_INT + 0 },
    { 1, ARRAY_OF_INT + 3 },
};

static const struct B ARRAY_OF_B[] = {
    { 0, ARRAY_OF_A + 0 },
    { 42, NULL },
};

static const struct C ROOT = { 0, ARRAY_OF_B + 0 };
4
  • @makapuf: do you actually need to store pointers to the structures, or can we drop that level of indirection? ie, does a single structure appear in multiple lists?
    – Christoph
    Commented Sep 3, 2014 at 21:07
  • @makapuf: you used arrays of pointers, which means they could be shared; I'll edit in a bit
    – Christoph
    Commented Sep 3, 2014 at 21:20
  • yes, because I didnt find a way to store arrays of different lengths in an array ...
    – makapuf
    Commented Sep 3, 2014 at 21:23
  • OK, I finally mixed your implementation and my code. For the record, some of the errors I was having was by superfluous casting/qualifiers to literals, giving non constant errors (maybe by creating intermediary literals, which renders the enclosing struct non constant)
    – makapuf
    Commented Sep 3, 2014 at 22:25
1

Assuming that you have a large number of each type of struct, it may be worth your time to write a code generator. Basically, you define a syntax that's somewhat human readable, and very easy to parse. Then write a code generator that takes that syntax a converts it into completely unreadable C code. Finally, compile that C code into the project. (You should also compile that C code into a verifier to make sure that you don't have any bugs in your code generator.)

Let me illustrate by example. First, here are the structure definitions. Note that I've added a count to A, and the const keyword as necessary in B and C.

struct A
{
    int id;
    int count;    // number of entries in the codes array
    int codes[];
};

struct B
{
    int b_member;
    const struct A *a[];
};

struct C
{
    int c_member;
    const struct B *objects[];
};

Here's what the input to the code generator might look like

 C hello  333
 B    11
 A       55    1 2 3
 A       56    4 5 6 7
 B    12
 A       57    1 8
 A       58    9
 X

 C world  444
 B    17
 A       73    20
 A       74    21 22
 A       75    23 24 25
 X

Lines that begin with the letter C define a top level structure. The string after the C is the name for the structure, and that's followed by the c_member initializer. Lines that begin with B have the b_member initializer. Lines that begin with A have the id followed by any number of codes. The lines with the X indicate the end of the C structure.

This is the C code that the code generator would produce

const struct A A1 = { 55, 3, { 1, 2, 3 } };
const struct A A2 = { 56, 4, { 4, 5, 6, 7 } };
const struct A A3 = { 57, 2, { 1, 8 } };
const struct A A4 = { 58, 1, { 9 } };
const struct A A5 = { 73, 1, { 20 } };
const struct A A6 = { 74, 2, { 21, 22 } };
const struct A A7 = { 75, 3, { 23, 24, 25 } };

const struct B B1 = { 11, { &A1, &A2, NULL } };
const struct B B2 = { 12, { &A3, &A4, NULL } };
const struct B B3 = { 17, { &A5, &A6, &A7, NULL } };

const struct C hello = { 333, { &B1, &B2, NULL } };
const struct C world = { 444, { &B3, NULL } };

Obviously the hard part is to write the parser. If you are familiar with lex and yacc, they can make your life easier. Personally, I've always written the parser/code-generator by hand.

Once you have the code generator written, verification is the next issue, since obviously a buggy code generator would be a never ending nightmare. Fortunately, verification is easy. Compile the auto-generated code into a test program that prints the structures out. Other than white space differences, the output from the test program should be identical to the original input.

The following code demonstrates how to print the structures so that the output matches the input at the top of this post. (Granted the code is a little difficult to read, but that's mainly due to the generic A B C structure names. More descriptive structure names would make the code a little easier to read.)

void ShowStruct( const struct C *cptr, const char *name )
{
    int i;
    const struct B * const *bptr;
    const struct B *bEntry;
    const struct A * const *aptr;
    const struct A *aEntry;

    printf( "C %s %d\n", name, cptr->c_member );
    for ( bptr = cptr->objects; *bptr != NULL; bptr++ )
    {
        bEntry = *bptr;

        printf( "B    %d\n", bEntry->b_member );

        for ( aptr = bEntry->a; *aptr != NULL; aptr++ )
        {
            aEntry = *aptr;

            printf( "A       %d   ", aEntry->id );
            for ( i = 0; i < aEntry->count; i++ )
                printf( " %d", aEntry->codes[i] );
            printf( "\n" );
        }
    }
    printf( "X\n\n" );
}

int main( void )
{
    ShowStruct( &hello, "hello" );
    ShowStruct( &world, "world" );
}

PS. Thanks for reminding me why I always try to avoid the const keyword ;)

2
  • well, that's a good idea ... and where the project comes from ;). I Your well written answer and idea is what I meant- well, not so thoroughly- by "Note that this code is generated by a program (script)." However the generated code was crap, so I needed to go on a simple example. (my code is generated by a python lexer / hand recursive descent parser). and the code thing is named ... bytecode.
    – makapuf
    Commented Sep 3, 2014 at 22:19
  • @makapuf Ah, I missed that line somehow. Oh well, I'll leave the answer anyways. Maybe someone who hasn't yet considered a code generator will stumble across it. Commented Sep 3, 2014 at 23:10
0

I would consider JSON / BSON for such configuration tasks.

Or any other settings alike formats. Including things like proto-buffers.

4
  • Note that I don't have files or RAM as I work in embedded space (hence the ROM data)
    – makapuf
    Commented Sep 3, 2014 at 20:33
  • @makapuf BSON, stored in ROM, is a structured data that you can access in the same way as JSON: get element of array, get value by name in name/value map, etc.
    – c-smile
    Commented Sep 4, 2014 at 0:35
  • sure, but the size of C libraries (and cost in memory) should be compared to the size of accessing a C struct and array, i.e. none (besides developer time, but the balance is much higher in my case).
    – makapuf
    Commented Sep 4, 2014 at 18:57
  • @makapuf You probably missed the fact that BSON is just a way of data organization. It is quite simple to write a function that will give you data item by its path in data structure. Something like get("A/B/C/12", &data, &datatype )
    – c-smile
    Commented Sep 5, 2014 at 4:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.