DEV Community

Paul J. Lucas
Paul J. Lucas

Posted on • Edited on

Anonymous Structures in C

#c

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' };
Enter fullscreen mode Exit fullscreen mode

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; } ))
Enter fullscreen mode Exit fullscreen mode

As I wrote:

The trick is to realize that static_assert can be used pretty much anywhere, including inside a struct declaration that’s an argument to sizeof() 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];
};
Enter fullscreen mode Exit fullscreen mode

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
};
Enter fullscreen mode Exit fullscreen mode

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 a struct 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 }
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

we can do:

slist_node_t *const node = STRUCT_FAM( slist_node_t, c_scope_data_t, 1 );
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
gberthiaume profile image
G. Berthiaume

Hi Paul,

As always, great post !

The STRUCT_FAM is pretty interesting.

I'm curious about 2 things:

  • Am I correct when I say that because the underlying slist_node_t type, we can't use the array notation to access items node->data[1]?
  • The Anonymous struct is not the same type as slist_node_t, therefore isn't the casting in STRUCT_FAM violating the strict aliassing rule?