cunions

How to access a member of a union without naming it?


I have some code that is very similar to the following

struct T {
    union {
        unsigned int x;
        struct {
            unsigned short xhigh;
            unsigned short xlow;
        };
    } x;
    /* ...repeated a handful of times for different variables in T... */
};

This does exactly what you'd expect: It allows me to declare a variable of type struct T and access either t.x.x or t.x.xhigh or t.x.xlow. So far so good.

However, I would really like it if I could do just t.x in the common case of wanting to access the value of the union as an unsigned int quantity, but retain the ability to access the high- and low-order portions independently without resorting to bit masking and shifting, and without invoking undefined behavior.

Is that possible in C?

If it is possible, then what is the C syntax for the declaration?

When I try the naiive approach of simply accessing t.x instead of t.x.x, I get warning messages like (this particular one is from a printf() call):

cc -ansi -o test -Wall test.c
test.c: In function ‘my_function’:
test.c:13:2: warning: format ‘%X’ expects argument of type ‘unsigned int’, but argument 2 has type ‘const union <anonymous>’ [-Wformat]

Using -std=c11 instead of -ansi yields the same warning.


Solution

  • Anonymous unions are a thing, if you can use anonymous structs (they are both C11 features or compiler extensions).

    Just as you've used a struct with no name to inject its members into the union's namespace, so you can also use a union with no name to inject its members into the enclosing namespace. Like so:

    struct T {
        union {
            unsigned int x;
            struct {
                unsigned short xhigh;
                unsigned short xlow;
            };
        }; /* <-- no name here */
    
        /* ...repeated a handful of times for different variables in T... */
    };
    

    You just have to make sure that none of the injected names clash with other injected names or regular names that are there, otherwise it won't compile.


    One concern though: you seem to be relying on the "fact" that unsigned short is half the size of unsigned int, and that these types are big-endian. But if that's what happens on your system, then that's fine. If not, I suggest you rethink the structure.