Introduction
As I wrote, C allows anonymous unions, that is a union without a name:
enum token_kind {
TOKEN_NONE,
TOKEN_INT,
TOKEN_FLOAT,
TOKEN_CHAR,
TOKEN_STR
};
struct token {
enum token_kind kind;
union { // "anonymous" union
long i;
double f;
char c;
char *s;
};
};
struct token t = { .kind = TOKEN_CHAR, .c = 'a' };
It turns out that C (but not C++) also allows anonymous structures. They tend to be used even less than anonymous unions, but, in a few cases, anonymous structures are just the thing.
In Expressions
Among my previous articles, the one on Handy C/C++ Preprocessor Macros actually used an anonymous structure, though I didn’t specifically call attention to it. In that article, what was needed was a way to use static_assert in an expression:
#define STATIC_ASSERT_EXPR(EXPR,MSG) \
(!!sizeof( struct { static_assert( (EXPR), MSG ); char c; } ))
As I wrote:
The trick is to realize that
static_assertcan be used pretty much anywhere, including inside astructdeclaration that’s an argument tosizeof()that makes the whole thing an expression.
In this case, the struct isn’t being used for its “struct-ness,” but only to make the use of static_assert there valid C.
Inside Unions
Just as anonymous unions can be used inside structures, anonymous structures can be used inside unions, for example:
union point_3d {
struct {
int x, y, z;
};
int v[3];
};
defines a 3D point such that either coordinates can be accessed individually via x, y, or z — or as a 3-element vector v. The struct is used to group members together as one member of the union. Just as with anonymous unions, the struct members behave as if they’re direct members of their enclosing union (except they have distinct offsets).
For Flexible Array Members
As I wrote, C99 introduced the ability to have the last member of a struct with more than one named member be a flexible array member, that is an array of an unspecified size:
typedef struct slist_node slist_node_t;
struct slist_node {
slist_node_t *next;
alignas( max_align_t ) char data[]; // flexible array
};
I wrote:
While you can have such a
structon the stack, it’s not useful since no size is set aside for the array (hence, accessing the array is undefined behavior). To be useful, such astructhas to be allocated on the heap ....
It turns out there is a way to allocate such a struct on the stack using a compound literal of an anonymous structure:
#define STRUCT_FAM(STRUCT,FAM,NFAM) (STRUCT*) &( \
struct { \
alignas(STRUCT) char buf[sizeof(STRUCT) + sizeof(FAM) * (NFAM)]; \
} \
){ 0 }
Then, to allocate an slist_node on the stack where data is sized to accommodate, say, a struct like:
typedef struct c_scope_data c_scope_data_t;
struct c_scope_data {
char *name;
c_type_t type;
};
we can do:
slist_node_t *const node = STRUCT_FAM( slist_node_t, c_scope_data_t, 1 );
and node will point to an slist_node in the stack frame of the enclosing function and its lifetime is the scope containing the declaration.
(These code snippets are from the source code of cdecl.)
In the macro, buf is declared to be the total size of the struct containing the flexible array member (that excludes it from the size) plus the size that we want the flexible array member to be treated as (times the number of array elements).
The problem is that an array of char might not be suitably aligned for a structure, hence the need for alignas. However, alignas can’t be used in the declaration of a compound literal — except if it’s inside a structure, here an anonymous structure.
This still works because the offset of buf relative to the start of the structure it’s in is 0, hence a pointer to the struct via & is the same as for buf that is then cast to STRUCT*.
Conclusion
Anonymous structures, though use in practice is rare, have a few niche uses that work nicely.
Top comments (2)
Hi Paul,
As always, great post !
The
STRUCT_FAMis pretty interesting.I'm curious about 2 things:
slist_node_ttype, we can't use the array notation to access itemsnode->data[1]?slist_node_t, therefore isn't the casting inSTRUCT_FAMviolating the strict aliassing rule?datais of typechar, sodata[1]doesn't help you. You'd have to cast the returned pointer toSTRUCT*.char*is a special exception to strict aliasing rules.