-
Notifications
You must be signed in to change notification settings - Fork 0
Anonymous and Nested Structs
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.
| 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 |
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.
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 typeOr fuse the definition with the first declarations:
struct point { int x; int y; } point1, point2; // type + two vars at onceCommon malformed attempt:
struct point { int x; int y; };
struct point = point1, point2; // ❌ `struct point =` is not a declarationAfter the tag exists, declare variables as type followed by a variable list:
struct point point1, point2;.
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'tThis is exactly why C programmers reach for a typedef:
typedef struct point { int x; int y; } point_t; // now: point_t p;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 nameC11 §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.
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 accessBitfield layout is implementation-defined, so this pattern is portable only within a known ABI/compiler — fine for kernel-on-a-known-target work.
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 }; // OKThe 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.
__attribute__((packed))- Bitfields
- Pointer arithmetic
-
container_of(interacts with anonymous inner structs viaoffsetof)