Static structure initialization with a pointer to array of pointers member

I'm trying to initialize a structure member that needs to point to an array of pointers. The idea is to declare the array of structures statically to avoid initialization overhead since all the data is fixed and known at compile time.

Unfortunately, I cannot get the code to compile under Visual Studio 2015. The code listed below produces the following error: C2099: initializer is not a constant. Which seems odd because list is only initialized with a fixed sized list of string literals.

#define DATA_LIST { \
    L"some",        \
    L"example",     \
    L"data",        \
    L"in a list",   \
    NULL            \
}

#define INFO_LIST { \
    L"another",     \
    L"list",        \
    L"with",        \
    L"some",        \
    L"info",        \
    NULL            \
}

typedef struct data {
    unsigned int flag;
    const wchar_t **list;
} dataset, *pdataset;

static dataset somedata[] = {
    {   .flag = 2,
        .list = (const wchar_t *[])DATA_LIST // C2099
    },
    {   .flag = 4,
        .list = (const wchar_t *[])INFO_LIST // C2099
    }
};

I've also tried to use a pointer to a flexible array (const wchar_t *list[];). Not the ideal solution because somedata will no longer be able to be declared as an array of structures. Next to that, it will also produce a warning (C4200: nonstandard extension used: zero-sized array in struct/union).

typedef struct data {
    unsigned int flag;
    const wchar_t *list[]; // C4200 (somedata can no longer be an array of structures)
} dataset, *pdataset;

static dataset somedata = {
    .flag = 2,
    .list = DATA_LIST
};

Another idea was to define list as a pointer to a fixed size array of pointers. But this requires the dataset structure to be defined with a list member that is large enough to hold the largest list. Also not ideal when there are lots of small lists and one single large list.

typedef struct data {
    unsigned int flag;
    const wchar_t *list[sizeof (wchar_t *[])INFO_LIST / sizeof *(wchar_t *[])INFO_LIST];
} dataset, *pdataset;

static dataset somedata[] = {
    {   .flag = 2,
        .list = DATA_LIST
    },
    {   .flag = 4,
        .list = INFO_LIST
    }
};

Maybe I'm overseeing something or is there some language extension feature available that can provide an elegant solution? Any suggestions are welcome.

Note: even though the visual-c++ tag is added, the code is compiled as C code.


Another interesting thing to add which might be related, is that when somedata is declared as non-static (thus without the static keyword), the compiler will produce some warnings but is able to compile the code. By declaring somedata as non-static, the constraint is removed that forces the data used for initializing somedata to be known at compile time.

As indicated by the compilation warnings, it seems that the compiler temporarily stores the address of the list of string literals in an automatic variable before initializing the list member with it. This remains speculation, though. Maybe someone experienced can shed some light on what is actually happening here.

typedef struct data {
    unsigned int flag;
    const wchar_t **list;
} dataset, *pdataset;

// C4221: nonstandard extension used: 'list': cannot be initialized using
//        address of automatic variable '$S1'
// C4204: nonstandard extension used: non-constant aggregate initializer
dataset somedata = {
    .flag = 2,
    .list = (const wchar_t *[])DATA_LIST // note: see declaration of '$S1'
};

Last but not least, when using a temporary variable initialized with the address of the list of string literals to initialize the list member, the code finally compiles fine without any warnings or errors.

static const wchar_t *temp[] = DATA_LIST;

static dataset somedata = {
    .flag = 2,
    .list = temp
};

But when declaring temp as a pointer to a pointer and typecasting the list of string literals, the code can no longer be compiled as the expression that initializes list becomes marked as an active error: expression must have a constant value

static const wchar_t **temp = (const wchar_t *[])DATA_LIST;

static dataset somedata = {
    .flag = 2,
    .list = temp // marked as active error
};

If I then decide to make somedata non-static again, the expression is no longer marked as an active error. But when trying to compile the code, the following error comes back up again: C2099: initializer is not a constant

I'm wondering if Visual Studio 2017 behaves the same way and if there is an alternative method available to organize and process the data by similar means.

1 answer

  • answered 2017-11-12 23:16 M.M

    MSVC has poor compliance to the C Standard. As a workaround you can use named objects instead of compound literals:

    static const wchar_t *x_data_list[] = DATA_LIST;
    static const wchar_t *x_info_list[] = INFO_LIST;
    
    static dataset somedata[] = {
        {   .flag = 2,
            .list = x_data_list
        },
        {   .flag = 4,
            .list = x_info_list
        }
    };
    

    I'm not sure whether you intentionally made your lists non-const, but if you are not planning to write to x_data_list at runtime then you can make it const and give the .list member the type const wchar_t * const *.