cgccstatic-initialization

Intentionally Disregarding Excess Elements in Initializer Warning Under GCC


I am attempting to abuse static initialization to build a menu tree without any runtime initialization:

typedef struct {
    menu_entry_t entry;
    uint16_t can_loop:1;
    uint16_t sz:15;
    uint32_t entries[0];
} __attribute__((packed)) menu_data_t;

static menu_data_t menu_list[] = {
    {
        .entry = MENU_COIL_POWER,
        .can_loop = 0,
        .sz = 5,
        .entries = {
            RGB(0x0F, 0x0A, 0x00),
            RGB(0x1F, 0x15, 0x00),
            RGB(0x3F, 0x29, 0x00),
            RGB(0x7F, 0x53, 0x00),
            RGB(0xFF, 0xA5, 0x00),
        }
    },
    {
        .entry = MENU_LED_MODE,
        .can_loop = 0,
        .sz = 3,
        .entries = {
            RGB(63, 63, 0),
            RGB(127, 127, 0),
            RGB(255, 255, 0),
        }
    },
    // ...
};

This builds but GCC, predictably complains about the initialization of the entries sub-array:

warning: excess elements in array initializer

How can I silence this specific warning? I am fully aware that it is generated because I have declared uint32_t entries[0] with an array size of zero. This is a paradigm I have exploited dynamically in the past via malloc(sizeof(menu_data_t) + num_entries * sizeof(uint32_t)); I am simply attempting to do this statically now.

So, in short, How can I disable GCC's excess elements in initializer warning (preferably something that can be fed into #pragma GCC diagnostic ignored "...")? Searching GCC's documentation doesn't seem to have an explicit -W flag for it (or, at least, it is called something unexpected)?

Some related research:


Solution

  • Looking at GCC force warning to be an error: excess elements in array initializer and gcc source code, there is no option to silence the warning.

    Notes: This is an XY question. It's asking about silencing a compiler warning. Instead consider researching how to properly initailize an array of structures with flexible array length.

    The question additionally contains a false premise. All the "excess" data are ignored, they are not stored anywhere and can just be removed from the source code.

    The array of lenght zero is a gcc extensions. As gcc documentation notes https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html prefer the C99 syntax.

    Bottom line, ignoring your question, I do not think there exists way to statically initialize an array of structures with flexible array memebrs. I would do just pointers and forget about it. Note: the pointers themselves are const and can be stored in ROM.

    #include <stdint.h>
    typedef enum { MENU_COIL_POWER, MENU_LED_MODE } menu_entry_t;
    #define RGB(a,b,c) a
    typedef struct {
        menu_entry_t entry;
        uint16_t can_loop:1;
        uint16_t sz:15;
        uint32_t entries[];
    } menu_data_t;
    static menu_data_t a = {
        .entry = MENU_COIL_POWER,
        .can_loop = 0,
        .sz = 5,
        .entries = {
            RGB(0x0F, 0x0A, 0x00),
            RGB(0x1F, 0x15, 0x00),
            RGB(0x3F, 0x29, 0x00),
            RGB(0x7F, 0x53, 0x00),
            RGB(0xFF, 0xA5, 0x00),
        }
    };
    static menu_data_t b = {
        .entry = MENU_LED_MODE,
        .can_loop = 0,
        .sz = 3,
        .entries = {
            RGB(63, 63, 0),
            RGB(127, 127, 0),
            RGB(255, 255, 0),
        }
    };
    static menu_data_t *const menu_list[] = {&a, &b};
    

    If this is not enough for you and you really do care about it, you would write a program to generate the bytes that compose of the array or manage the bytes that compose of the array yourself, and use deserialization or abstractions to get the data from the bytes into C programming language. For example on X86 machine the following code outputs 3 7f7f:

    #include <stdint.h>
    #include <stdio.h>
    enum { MENU_COIL_POWER, MENU_LED_MODE };
    typedef uint16_t menu_entry_t;
    #define RGB(a,b,c)  /*little endian*/ a,b,c,0
    typedef struct {
        menu_entry_t entry;
        uint16_t can_loop:1;
        uint16_t sz:15;
        uint32_t entries[];
    } __attribute__((packed)) menu_data_t;
    #define MAKE_MENU_DATA(entry, can_loop, sz)   \
        /* assuming some bit order and endianess of target platform */ \
        entry, entry >> 8, sz << 1 | can_loop, sz >> 7
    static _Alignas(menu_data_t) unsigned char menu_data[] = {
        MAKE_MENU_DATA(MENU_COIL_POWER, 0, 5),
        RGB(0x0F, 0x0A, 0x00),
        RGB(0x1F, 0x15, 0x00),
        RGB(0x3F, 0x29, 0x00),
        RGB(0x7F, 0x53, 0x00),
        RGB(0xFF, 0xA5, 0x00),
        MAKE_MENU_DATA(MENU_LED_MODE, 0, 3),
        RGB(63, 63, 0),
        RGB(127, 127, 0),
        RGB(255, 255, 0),
    };
    menu_data_t *get_menu_data(unsigned i) { 
        char *pnt = menu_data;
        while (1) {
            menu_data_t *tmp = (void*)pnt;
            if (!i--) return tmp;
            pnt += sizeof(menu_data_t) + sizeof(uint32_t) * tmp->sz;
        }
    }
    int main() {
        printf("%d %x\n", get_menu_data(1)->sz, get_menu_data(1)->entries[1]);
    }
    

    Note: that code above has undefined behavior - the char array is cast to menu_data_t. It might be preferable to write good unit tests to test what compiler code generates, and put the "getter" into a separate translation unit and not optimize it.