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_assert
can be used pretty much anywhere, including inside astruct
declaration 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
struct
on 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 astruct
has 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 (1)
Hi Paul,
As always, great post !
The
STRUCT_FAM
is pretty interesting.I'm curious about 2 things:
slist_node_t
type, we can't use the array notation to access itemsnode->data[1]
?slist_node_t
, therefore isn't the casting inSTRUCT_FAM
violating the strict aliassing rule?