2

I'm wondering if it is possible to use designated initializers in unnamed data members of structs... (Yikes, a mouthful, but yes, it is the cleanest way to do what I'm trying to do...). If I have:

typedef struct MainStruct {
    union {
         uint8_t   a8[16];
         uint64_t  a64[2];
    };
    uint64_t       i64;
} MainStruct_t;

typedef struct OtherStruct {
    MainStruct_t main;
    int          otherval;
} OtherStruct_t;

OtherStruct_t instance = { .main.a64 = { 0, 0 }, .otherval = 3 };

and I try to compile, I get the error:

tst3.c:16: error: unknown field ‘a64’ specified in initializer

I've also tried using .main..a64, but I'm getting other issues. This is with gcc 4.4. Unfortunately, the MainStruct is used all over the code, so naming the union would involve changing hundreds of files, so I'd like to avoid that. I'd also like to avoid any assumptions about position of MainStruct within OtherStruct if possible.

blackghost
  • 1,730
  • 11
  • 24
  • Trying to find the name of an unnamed entity? :-) If you want them zeroed, that might work anyway. [What happens to fields not named by a designated initalizer?](http://stackoverflow.com/questions/3374446/what-happens-to-fields-not-named-by-a-designated-initializer) – Bo Persson Mar 29 '17 at 20:17
  • Actually, I need to initialize to non-zero values as well, depending on the instance... I would have hoped that gcc could resolve `main.a64` in this case, but it doesn't seem to work. This seems to make it rather difficult to initialize using the second member of an unnamed union (using the first would be simple using non-designated unions... just tried it...) – blackghost Mar 29 '17 at 20:34

1 Answers1

4

You need to change the syntax a bit, initializing .main within the initializer for instance:

typedef struct MainStruct {
    union {
         uint8_t   a8[16];
         uint64_t  a64[2];
    };
    uint64_t       i64;
} MainStruct_t;

typedef struct OtherStruct {
    MainStruct_t main;
    int          otherval;
} OtherStruct_t;

OtherStruct_t instance = { .main = {.a64 = { 0, 0 }}, .otherval = 3 };

Here is a working test program:

#include <stdio.h>
#include <stdint.h>

typedef struct MainStruct {
    union {
         uint8_t   a8[16];
         uint64_t  a64[2];
    };
    uint64_t       i64;
} MainStruct_t;

typedef struct OtherStruct {
    MainStruct_t main;
    int          otherval;
} OtherStruct_t;

OtherStruct_t instance = { .main = {.a64 = { 5, 10 }}, .otherval = 3 };

int main(void)
{
    printf("%d, %d\n", (int) instance.main.a64[0], (int) instance.main.a64[1]);
    printf("%d\n", instance.otherval);

}

Compiled with gcc -std=c11 -Wall -Wextra -Wpedantic, here is the program output:

5, 10
3

Update

This use of designated initializers should also work with at least C99, though C99 does not support unnamed structures or unions. Here is an example:

#include <stdio.h>

struct Inner {
    int x;
    int arr[2];
};

struct Outer {
    char id[100];
    struct Inner state;
};

int main(void)
{
    struct Outer instance = { .id = "first",
                              .state = {.x = 5, .arr[0] = 1, .arr[1] = 2 }};

    printf("instance id: %s\n", instance.id);
    printf("instance state.x = %d\n", instance.state.x);
    printf("instance state.arr[0] = %d\n", instance.state.arr[0]);
    printf("instance state.arr[1] = %d\n", instance.state.arr[1]);

    return 0;
}

Compiled with gcc -std=c99 -Wall -Wextra -Wpedantic, here is the program output:

instance id: first
instance state.x = 5
instance state.arr[0] = 1
instance state.arr[1] = 2

Final Note

It turns out that OP's original syntax of:

OtherStruct_t instance = { .main.a64 = { 0, 0 }, .otherval = 3 };

should also work on both C99 and C11, but is not supported in older standards which do not allow initialization of subobjects.

Unnamed unions are not supported in C99, but are available as a GNU extension. Further investigation has turned up this bug report which suggests that designated initializers for unnamed structs and unions were fixed in gcc 4.6. As workaround, it was suggested at this link to enclose the offending initializer in braces; it is also mentioned that this workaround is a bit finicky and position dependent, which may explain why it does not work here for OP.

ad absurdum
  • 19,498
  • 5
  • 37
  • 60
  • Thanks -- I had tried that, but I got `tst3.c:19: error: unknown field ‘a64’ specified in initializer`. I'm taking it this works on newer versions of gcc? – blackghost Mar 30 '17 at 13:26
  • It worked for me, and it should work according to the Standard. I compiled with `-Wpedantic`. I will post a working test momentarily. I presume you are using the `-std=c11` option? – ad absurdum Mar 30 '17 at 13:29
  • gcc 4.4.7 does not support the `-std=c1X` option (just checked, it is not supported until gcc 4.6). If this solution works in newer versions of gcc, then that's good to know. I currently had to settle for another (somewhat ugly) workaround for my specific case. I'll mark your answer as accepted, as there doesn't seem to be a solution for 4.4, and it might be useful for others looking to do the same going forward. – blackghost Mar 30 '17 at 13:45
  • I hadn't checked before (!), but your original syntax works also. Even though I get a warning when compiled with `-std=c99`, both versions work. Maybe it is just the age of the compiler coupled with the unnamed union issue. – ad absurdum Mar 30 '17 at 14:43
  • [This appears to be a bug, fixed in gcc 4.6](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=10676). The suggested workaround was to enclose the unnamed union initializer in braces, but the bug report contains comments which suggest that this may not always work. – ad absurdum Mar 30 '17 at 16:40