Menu

Union in C: Shared Memory, Union vs Struct, and Tagged Unions

How a C union stores several types in the same bytes - why its size is the largest member, why you must read the member you last wrote, and how a tagged union (enum plus union) makes it safe.

This page includes runnable editors - edit, run, and see output instantly.

A struct says "all of these, together". A union says "exactly one of these, at a time". The members are laid on top of each other at the same address, so the union is only as big as its largest member and writing one member destroys the others.

That sounds like a trap, and unchecked it is. But it is also how C expresses a value that could be one of several kinds - a token that is a number or a string, a message that is a click or a keypress - without paying for all of them at once.

Declaring and Using a Union

The syntax mirrors a struct exactly; only the keyword changes.

Members are accessed with . (or -> through a pointer), exactly as with a struct. What is different is that only the most recently written member holds a meaningful value. After v.f = 3.5f, reading v.i does not give you 3 - it gives you whatever integer the bit pattern of 3.5f happens to spell.

Size: the Largest Member Wins

Compare the memory layout of a struct and a union with the same members:

On a typical machine the struct is 24 bytes (4 for the int, 8 for the double, 1 for the char, plus padding) while the union is 8 - the size of its double, rounded up for alignment. The three addresses printed are identical, which is the whole story of unions in one line of output.

Note the %p casts to void *. printf expects exactly that for %p; passing a different pointer type is undefined behavior even though it usually appears to work. See format specifiers.

Initializing a Union

A brace initializer with no designator initializes the first member:

The designated form is the one to use. {42} silently depends on member order, so reordering the declaration later changes which member gets initialized - a genuinely nasty bug, because nothing about the code looks different.

The Real Problem: Which Member Is Live?

A union does not record which member you last wrote. It is just bytes; the knowledge lives in your head, and that is exactly where knowledge goes missing.

The number printed is large and strange - the bit pattern of 1.0f read as an int. Nothing crashed, nothing warned, and the program is quietly wrong. The union did exactly what it promised; the mistake was ours in forgetting which member was live.

The Fix: a Tagged Union

The standard solution is to pair the union with an enum that records the live member, and wrap both in a struct. That combination is called a tagged union (or discriminated union), and it is how you should write essentially every union in application code.

Every read now goes through the switch on kind, so it is impossible to read a member that was never written - as long as every write sets the tag too. Wrapping the writes in small constructor functions (value_from_int, value_from_string) is the usual way to make that impossible to forget.

The memory saving is real: each Value here costs 24 bytes of payload plus the tag, rather than 4 + 4 + 24 for a struct holding all three. With a hundred thousand of them, that matters.

Compiling with -Wall adds a second safety net: if you later add a VAL_BOOL to the enum and forget a case for it, GCC warns about the unhandled enumeration value.

Anonymous Unions

C11 allows a union member with no name inside a struct, promoting its members to the outer struct's namespace:

Because the union itself is unnamed, you write s->circle.r rather than s->as.circle.r. Shorter to read, at the cost of hiding the fact that a union is involved at all - which is fine when the tag is right there beside it.

Where Unions Actually Earn Their Keep

Four recurring uses:

  • Variant values. Interpreters, JSON and configuration parsers, and message queues all carry values whose type is decided at runtime. A tagged union is the canonical representation.
  • Memory-tight records. When a struct has several mutually exclusive fields and you have millions of them, overlapping them is a direct saving.
  • Protocol and hardware layouts. A packet whose payload depends on a header byte maps naturally onto a tagged union, as do device registers.
  • Byte inspection. Overlaying a value with a unsigned char[] array lets you look at its individual bytes, for example to determine endianness:

This is type punning - deliberately reading bytes back as a different type. Reading through a union like this is explicitly allowed as an implementation-defined result in C (unlike casting pointers between unrelated types, which breaks the aliasing rules), and inspecting bytes as unsigned char is always safe. Reinterpreting an int as a float is a different matter: the result depends entirely on your platform's representation, so keep it out of portable code.

Common Mistakes

  • Reading a member you did not write. The headline hazard. Use a tag.
  • Assuming a union converts. It does not. u.i = 3; float f = u.f; reinterprets bits; int i = 3; float f = i; converts. See type casting.
  • Putting a pointer in a union and losing track of it. If one branch holds a char * you allocated, overwriting the union with a different member leaks it - nothing is left pointing at the buffer. Free before you switch branches.
  • Expecting the compiler to check. It will not. Unions are one of the few C features where the language offers no help at all beyond size and alignment; the tag is your only guard rail.

Frequently Asked Questions

What is a union in C?

A union is a type whose members all share the same memory. Writing one member overwrites the others, so a union holds exactly one of its members at a time. It is declared like a struct but with the union keyword: union Value { int i; float f; };.

What is the difference between a union and a struct in C?

A struct gives every member its own storage and holds all of them at once, so its size is at least the sum of its members. A union overlays every member at the same address and holds one at a time, so its size is that of its largest member.

What is the size of a union in C?

Large enough for its biggest member, rounded up for alignment. A union of an int (4 bytes) and a double (8 bytes) is 8 bytes - not 12. sizeof is the way to check on your own platform.

What happens if you read a union member you did not write?

You reinterpret the same bytes as a different type. Writing u.i = 1 then reading u.f does not convert - it reads the integer's bit pattern as a float, giving a meaningless number. In standard C that is unspecified at best, which is why the tagged union pattern exists: keep a tag alongside saying which member is live.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED