Anonymous and Nested Structs - MarekBykowski/readme GitHub Wiki
Anonymous and Nested Structs in C
Notes on the difference between an unnamed struct type, a named (tagged) struct type, and a truly anonymous struct whose members are promoted into the enclosing aggregate.
TL;DR
| Form | Tag? | Member name? | Reusable type? | Access |
|---|---|---|---|---|
struct { ... } var; |
no | โ (var is the variable) |
no, dies at ; |
var.x |
struct { ... } m; inside a struct |
no | yes (m) |
no | outer.m.x |
struct { ... }; inside a struct/union |
no | no | no | outer.x (promoted) |
struct tag { ... }; |
yes | โ | yes | struct tag v; v.x |
1. Unnamed type, one variable
struct { int x; int y; } point1;
Declares an unnamed struct type and defines exactly one variable point1
of it, in the same statement.
- No tag โ the type can never be named again.
- You cannot declare a second variable "of the same type" later.
- Two separate anonymous struct definitions with identical members are distinct, incompatible types.
So this is genuinely one-shot.
2. Named (tagged) type, reusable
A tag is what buys reusability.
struct point { int x; int y; }; // defines the TYPE only, no variable
struct point point1, point2; // two variables of that type
Or fuse the definition with the first declarations:
struct point { int x; int y; } point1, point2; // type + two vars at once
Common malformed attempt:
struct point { int x; int y; };
struct point = point1, point2; // โ `struct point =` is not a declaration
After the tag exists, declare variables as type followed by a variable list:
struct point point1, point2;.
Namespace note
Tags live in a separate namespace from ordinary identifiers, so these are all legal:
struct point point1; // tag `point`, variable `point1` โ fine
struct point point; // tag `point`, variable `point` โ legal but don't
This is exactly why C programmers reach for a typedef:
typedef struct point { int x; int y; } point_t; // now: point_t p;
3. Nested struct WITH a member name (all C versions)
Valid in every version of C. The inner struct has no tag, but the member is named, so you reach fields through the member.
struct outer {
int id;
struct {
int x;
int y;
} point; // no tag, but member is named "point"
};
struct outer o;
o.point.x = 1; // access via the member name
4. Truly anonymous struct โ members promoted (C11)
C11 ยง6.7.2.1p13 (a GCC/Clang extension long before C11). No tag and no member name โ the inner members become members of the containing struct.
struct outer {
int id;
struct {
int x;
int y;
}; // no tag, no member name -> anonymous
};
struct outer o;
o.x = 1; // promoted into the enclosing struct
o.y = 2;
Tradeoffs:
x/ymust not collide with any other member name inouter.- You can never name the inner type or take its
sizeofโ it has no name.
5. Where it earns its keep: register overlays
Anonymous struct inside an anonymous union โ overlay a raw value with a
bitfield view of the same storage, with no .bits. indirection.
struct ctrl_reg {
union {
uint32_t raw;
struct {
uint32_t enable : 1;
uint32_t mode : 2;
uint32_t rsvd : 29;
}; // anonymous struct inside anonymous union
};
};
struct ctrl_reg r;
r.raw = 0;
r.enable = 1; // same 32 bits as r.raw, flat access
Bitfield layout is implementation-defined, so this pattern is portable only within a known ABI/compiler โ fine for kernel-on-a-known-target work.
6. Initialization gotcha
You can't use a designated initializer with the anonymous type's name (there isn't one), but you can designate the promoted member directly:
struct outer o = { .id = 5, .x = 1, .y = 2 }; // OK
7. Standard-conformance caveat
The C standard only blesses anonymous structs and unions. Anonymous members
of other aggregate kinds, or nesting a named variable of an anonymous type,
are not part of C11 โ some of what GCC accepts there is -fms-extensions
territory, not ISO C.
See also
__attribute__((packed))- Bitfields
- Pointer arithmetic
container_of(interacts with anonymous inner structs viaoffsetof)