resource modeling
Structure vs Union in C: Memory and Use Cases
Understand how structures and unions organize related data, how their memory layouts differ, when all members can hold values simultaneously, and how tagged unions safely represent values that may have different types.
Introduction
Structures and unions are user-defined C types that group multiple members under one type name. Their declaration syntax is similar, but their memory models are fundamentally different.
struct Data
{
int integer;
double real;
char character;
};
union DataValue
{
int integer;
double real;
char character;
};
In a structure, each member has its own storage within the structure object. All members can retain their values simultaneously.
In a union, all members overlap in the same storage region. The program normally stores and interprets one selected alternative at a time.
Core idea: Use a structure when member values must exist together. Use a union when one storage region represents one of several alternative value types.
Prerequisites
| # | Prerequisite | Why It Is Needed |
|---|---|---|
| 1 | Fundamental C types | Structure and union members can use different object types. |
| 2 | Structure declarations | Union syntax closely resembles structure syntax. |
| 3 | Member-access operators | Both structures and unions use . and ->. |
| 4 | sizeof and alignment |
The two type categories calculate storage requirements differently. |
| 5 | Enumerations | An enumeration can record which union member is currently selected. |
| 6 | Pointers and object lifetime | A union can contain pointer members whose ownership must be managed. |
| 7 | Object representation | Overlapping storage does not create a portable external data format automatically. |
What Is a Structure?
A structure groups related members into one object while giving each member a distinct position in that object.
struct Student
{
unsigned int identifier;
double average_score;
char grade;
};
A struct Student object can retain its identifier, average score and grade simultaneously.
struct Student student = {
.identifier = 101,
.average_score = 87.5,
.grade = 'A'
};
Conceptual Structure Layout
struct Student
+-------------------+
| identifier |
+-------------------+
| possible padding |
+-------------------+
| average_score |
+-------------------+
| grade |
+-------------------+
| possible padding |
+-------------------+
The named members do not overlap. The implementation may insert padding between members or after the final member to satisfy alignment requirements.
What Is a Union?
A union defines several alternative members that occupy overlapping storage.
union Number
{
int integer;
double real;
};
The same union object can be used to store an integer value or a double value at different times.
union Number number;
number.integer = 25;
printf(
"%d\n",
number.integer
);
number.real = 19.95;
printf(
"%.2f\n",
number.real
);
Conceptual Union Layout
union Number
+-------------------------+
| integer storage |
| overlaps real storage |
| overlaps every member |
+-------------------------+
Writing to number.real reuses storage that was previously used through number.integer. The application must track which interpretation is intended.
Structure vs Union Summary
| Basis | Structure | Union |
|---|---|---|
| Keyword | struct |
union |
| Member storage | Each member occupies a distinct region | Members overlap in a shared region |
| Values retained | All members can retain values simultaneously | Normally one selected alternative is stored at a time |
| Writing a member | Does not overwrite other members | Reuses storage shared with other members |
| Size requirement | Includes all members and padding | Sufficient for its largest member and required alignment |
| Typical purpose | Model a record containing related fields | Model one value chosen from several alternatives |
| Member access | . or -> |
. or -> |
sizeof a Structure
struct Example
{
char character;
int integer;
double real;
};
The size of the structure includes:
- Storage for
character - Storage for
integer - Storage for
real - Any internal padding
- Any trailing padding
printf(
"Structure size: %zu\n",
sizeof(struct Example)
);
Do not predict the result by adding assumed sizes. Measure the target implementation.
sizeof a Union
union Example
{
char character;
int integer;
double real;
};
The union must provide enough storage and suitable alignment for any of its members.
printf(
"Union size: %zu\n",
sizeof(union Example)
);
The union size is therefore at least large enough for its largest member. The implementation may include padding to meet alignment and array-layout requirements.
Accurate size rule: A structure accommodates all members in distinct regions. A union accommodates its largest required member representation in shared storage. Exact numeric sizes remain implementation-dependent.
Measuring Both Types
#include <stdalign.h>
#include <stdio.h>
struct StructureData
{
char character;
int integer;
double real;
};
union UnionData
{
char character;
int integer;
double real;
};
int main(void)
{
printf(
"Structure size : %zu\n",
sizeof(struct StructureData)
);
printf(
"Structure alignment: %zu\n",
alignof(struct StructureData)
);
printf(
"Union size : %zu\n",
sizeof(union UnionData)
);
printf(
"Union alignment : %zu\n",
alignof(union UnionData)
);
return 0;
}
The output can differ between compilers, architectures, ABIs and compiler options.
Member Addresses
Union members begin at the same union storage location.
#include <stdio.h>
union Data
{
int integer;
double real;
char character;
};
int main(void)
{
union Data data;
printf(
"union address : %p\n",
(void *)&data
);
printf(
"integer : %p\n",
(void *)&data.integer
);
printf(
"real : %p\n",
(void *)&data.real
);
printf(
"character : %p\n",
(void *)&data.character
);
return 0;
}
In comparison, structure members have distinct ordered positions, although padding can exist between them.
Writing Structure Members
struct Data
{
int integer;
double real;
};
struct Data data;
data.integer = 25;
data.real = 19.95;
Both values remain available:
printf(
"%d %.2f\n",
data.integer,
data.real
);
Writing Union Members
union Data
{
int integer;
double real;
};
union Data data;
data.integer = 25;
printf(
"%d\n",
data.integer
);
data.real = 19.95;
printf(
"%.2f\n",
data.real
);
After assigning data.real, the program should treat real as the selected alternative unless a narrowly defined language rule or verified implementation contract explicitly supports another interpretation.
data.integer = 25;
data.real = 19.95;
printf(
"%d\n",
data.integer
);
The integer value should not be assumed to remain logically stored after the shared storage is written as a double.
The Active-member Problem
A union object contains no automatic metadata identifying which member the application intends to use.
union Value
{
int integer;
double real;
const char *text;
};
Looking only at a union Value object does not tell application code whether it should use integer, real or text.
A separate discriminator, commonly called a tag, solves this problem.
Tagged Union
enum ValueType
{
VALUE_INTEGER,
VALUE_REAL,
VALUE_TEXT
};
struct Value
{
enum ValueType type;
union
{
int integer;
double real;
const char *text;
} data;
};
The enumeration records which union member is selected.
Creating Tagged Values
struct Value first = {
.type = VALUE_INTEGER,
.data.integer = 25
};
struct Value second = {
.type = VALUE_REAL,
.data.real = 19.95
};
struct Value third = {
.type = VALUE_TEXT,
.data.text = "C Programming"
};
Reading the Selected Member
void value_print(
const struct Value *value)
{
if (value == NULL)
{
return;
}
switch (value->type)
{
case VALUE_INTEGER:
printf(
"Integer: %d\n",
value->data.integer
);
break;
case VALUE_REAL:
printf(
"Real: %.2f\n",
value->data.real
);
break;
case VALUE_TEXT:
printf(
"Text: %s\n",
value->data.text != NULL
? value->data.text
: "(null)"
);
break;
default:
puts("Invalid value type.");
break;
}
}
Tagged-union invariant: The tag and selected union member must agree. If type is VALUE_REAL, application code must store and read data.real.
Why the Tag Is Outside the Union
union InvalidValue
{
enum ValueType type;
int integer;
double real;
};
If the tag is another member of the same union, writing the value reuses the storage occupied by the tag.
struct Value
{
enum ValueType type;
union
{
int integer;
double real;
} data;
};
Anonymous Union Members
C11 supports anonymous unions under the applicable language rules.
struct Value
{
enum ValueType type;
union
{
int integer;
double real;
const char *text;
};
};
Members can then be accessed directly:
struct Value value = {
.type = VALUE_INTEGER,
.integer = 25
};
printf(
"%d\n",
value.integer
);
A named union member such as data can make the storage relationship more explicit, while an anonymous union can make access shorter. Follow the project's readability standard consistently.
Initializing Structures and Unions
Structure Initialization
struct Student
{
unsigned int identifier;
double score;
char grade;
};
struct Student student = {
.identifier = 101,
.score = 87.5,
.grade = 'A'
};
Multiple structure members can be initialized because each has independent storage.
Union Initialization
union Number
{
int integer;
double real;
};
union Number number = {
.real = 12.5
};
A designated initializer clearly identifies the initially selected union member.
Assignment
Structure and union objects can be assigned to compatible objects of the same type.
struct Student first = {
.identifier = 101,
.score = 87.5,
.grade = 'A'
};
struct Student second =
first;
union Number first_number = {
.integer = 50
};
union Number second_number =
first_number;
For a union, application-level correctness still requires the receiver to know which alternative the copied representation denotes. A tagged union copies both the tag and union when the containing structure is assigned.
Comparing Structures and Unions
C does not provide a built-in equality operator for complete structure or union objects.
if (left == right)
{
/* Not valid for structure objects. */
}
Avoid using memcmp() as a general logical comparison because structure or union representations can contain padding bytes.
Compare a Tagged Union by Meaning
int values_equal(
const struct Value *left,
const struct Value *right)
{
if (left == NULL ||
right == NULL)
{
return left == right;
}
if (left->type != right->type)
{
return 0;
}
switch (left->type)
{
case VALUE_INTEGER:
return
left->data.integer ==
right->data.integer;
case VALUE_REAL:
return
left->data.real ==
right->data.real;
case VALUE_TEXT:
if (left->data.text == NULL ||
right->data.text == NULL)
{
return
left->data.text ==
right->data.text;
}
return strcmp(
left->data.text,
right->data.text
) == 0;
default:
return 0;
}
}
Unions with Dynamically Allocated Members
enum ResourceType
{
RESOURCE_NONE,
RESOURCE_INTEGER_ARRAY,
RESOURCE_TEXT
};
struct Resource
{
enum ResourceType type;
union
{
struct
{
int *values;
size_t count;
} integer_array;
char *text;
} data;
};
Cleanup must inspect the tag before releasing owned resources.
void resource_destroy(
struct Resource *resource)
{
if (resource == NULL)
{
return;
}
switch (resource->type)
{
case RESOURCE_INTEGER_ARRAY:
free(
resource->data
.integer_array
.values
);
resource->data
.integer_array
.values = NULL;
resource->data
.integer_array
.count = 0;
break;
case RESOURCE_TEXT:
free(
resource->data.text
);
resource->data.text =
NULL;
break;
case RESOURCE_NONE:
break;
default:
break;
}
resource->type =
RESOURCE_NONE;
}
Ownership rule: A union does not automatically construct, copy or release the selected alternative. The program must perform the correct operation according to the tag.
Safely Changing a Tagged-union Alternative
When an existing alternative owns resources, release it before selecting a different alternative.
int resource_set_text(
struct Resource *resource,
const char source[])
{
if (resource == NULL ||
source == NULL)
{
return 0;
}
size_t length =
strlen(source);
if (length == SIZE_MAX)
{
return 0;
}
char *copy =
malloc(length + 1);
if (copy == NULL)
{
return 0;
}
memcpy(
copy,
source,
length + 1
);
resource_destroy(resource);
resource->data.text = copy;
resource->type = RESOURCE_TEXT;
return 1;
}
The new allocation is completed before the existing resource is destroyed. Therefore, allocation failure leaves the original value unchanged.
Structures, Unions and External Data
Neither a structure nor a union automatically defines a portable file or network representation.
fwrite(
&value,
sizeof value,
1,
file
);
Raw object representations can depend on:
- Member sizes
- Structure padding
- Union alignment and padding
- Integer representation
- Floating-point representation
- Byte order
- Pointer representation
- Compiler and ABI rules
Unions and Hardware Interfaces
Low-level code sometimes uses unions to provide multiple views of a register or data word.
union RegisterView
{
uint32_t complete_value;
unsigned char bytes[4];
};
The observed relationship between complete_value and bytes depends on representation details such as byte order.
Do not assume that a union declaration alone establishes compatibility with a device register or communication format. Such code requires verified compiler, ABI and hardware documentation.
Type Punning and Representation Inspection
Union-based reinterpretation is sensitive to language rules and implementation behavior. It can also produce target-dependent results because object representations differ.
When the goal is to inspect or copy representation bytes portably, use character storage and memcpy().
#include <string.h>
void copy_float_bytes(
float value,
unsigned char output[
sizeof(float)
])
{
memcpy(
output,
&value,
sizeof value
);
}
This safely copies the representation bytes. It does not make those bytes a platform-independent encoding.
Common Structure Use Cases
- Student, employee and product records
- Coordinates containing multiple dimensions
- Dates containing day, month and year
- Configuration objects
- Linked-list and tree nodes
- Function results containing several related values
- Objects containing metadata and owned resources
Coordinate Example
struct Point3D
{
double x;
double y;
double z;
};
A point requires all three coordinates simultaneously, making a structure appropriate.
Common Union Use Cases
- Tagged variant values
- Parser tokens with alternative payload types
- Abstract syntax tree nodes with alternative node data
- Events carrying different payloads
- Memory-sensitive internal representations
- Implementation-specific hardware or binary views under a verified contract
Token Example
enum TokenType
{
TOKEN_IDENTIFIER,
TOKEN_INTEGER,
TOKEN_REAL
};
struct Token
{
enum TokenType type;
union
{
const char *identifier;
long integer;
double real;
} value;
};
Every token has one type and one corresponding payload.
Complete Tagged-union Program
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum ValueType
{
VALUE_EMPTY,
VALUE_INTEGER,
VALUE_REAL,
VALUE_TEXT
};
struct Value
{
enum ValueType type;
union
{
long integer;
double real;
char *text;
} data;
};
void value_initialize(
struct Value *value)
{
if (value == NULL)
{
return;
}
value->type = VALUE_EMPTY;
value->data.integer = 0;
}
void value_destroy(
struct Value *value)
{
if (value == NULL)
{
return;
}
if (value->type == VALUE_TEXT)
{
free(value->data.text);
}
value->type = VALUE_EMPTY;
value->data.integer = 0;
}
int value_set_integer(
struct Value *value,
long integer)
{
if (value == NULL)
{
return 0;
}
value_destroy(value);
value->data.integer =
integer;
value->type =
VALUE_INTEGER;
return 1;
}
int value_set_real(
struct Value *value,
double real)
{
if (value == NULL)
{
return 0;
}
value_destroy(value);
value->data.real = real;
value->type = VALUE_REAL;
return 1;
}
int value_set_text(
struct Value *value,
const char text[])
{
if (value == NULL ||
text == NULL)
{
return 0;
}
size_t length =
strlen(text);
if (length == SIZE_MAX)
{
return 0;
}
char *copy =
malloc(length + 1);
if (copy == NULL)
{
return 0;
}
memcpy(
copy,
text,
length + 1
);
value_destroy(value);
value->data.text = copy;
value->type = VALUE_TEXT;
return 1;
}
void value_print(
const struct Value *value)
{
if (value == NULL)
{
return;
}
switch (value->type)
{
case VALUE_EMPTY:
puts("Value: empty");
break;
case VALUE_INTEGER:
printf(
"Integer: %ld\n",
value->data.integer
);
break;
case VALUE_REAL:
printf(
"Real: %.2f\n",
value->data.real
);
break;
case VALUE_TEXT:
printf(
"Text: %s\n",
value->data.text
);
break;
default:
puts("Value: invalid type");
break;
}
}
int main(void)
{
struct Value value;
value_initialize(&value);
if (!value_set_integer(
&value,
250))
{
return EXIT_FAILURE;
}
value_print(&value);
if (!value_set_real(
&value,
49.95))
{
value_destroy(&value);
return EXIT_FAILURE;
}
value_print(&value);
if (!value_set_text(
&value,
"C Programming"))
{
value_destroy(&value);
return EXIT_FAILURE;
}
value_print(&value);
printf(
"Tagged value size: %zu\n",
sizeof value
);
value_destroy(&value);
return EXIT_SUCCESS;
}
Program Analysis
- The outer structure provides separate storage for the tag and union.
- The enumeration records the selected union alternative.
- The union stores an integer, real number or text pointer in shared storage.
value_initialize()establishes a valid empty state.- Every setter changes both the selected union member and the tag.
- The text alternative owns dynamically allocated character storage.
- The text setter allocates the replacement before destroying the previous value.
value_destroy()releases storage only when the text alternative is selected.value_print()checks the tag before reading a union member.- The printed
sizeofvalue is measured rather than assumed.
Common Mistakes
Assuming Union Members Have Separate Storage
All union members overlap in one shared storage region.
Expecting All Union Values to Survive
data.integer = 25;
data.real = 19.95;
The second assignment reuses the union's shared storage.
Forgetting the Union Tag
A union does not automatically record which alternative the application intends to read.
Placing the Tag Inside the Same Union
The payload assignment would overlap and replace the tag representation.
Using the Wrong Member for the Tag
value.type = VALUE_REAL;
value.data.integer = 25;
The tag and selected member no longer agree.
Assuming Union Size Is Always Exactly One Member's Declared Size
The union must satisfy the storage and alignment requirements of every member, and implementation padding can affect the complete size.
Comparing Aggregates with ==
C does not provide direct equality comparison for complete structure or union objects.
Using memcmp() for Logical Equality
Padding bytes and alternative representations can make raw comparison unsuitable for domain-level equality.
Writing Raw Union Storage to a Portable File
Define an explicit external representation with a type tag and encoded payload.
Assuming Union-based Byte Views Are Platform-independent
Byte order and type representations can differ across implementations.
Forgetting Resource Cleanup
If one alternative owns memory, cleanup must inspect the tag and release the correct resource.
Destroying the Old Value Before a Replacement Is Ready
Allocate a new owned resource first when failure should preserve the original value.
Choosing a Union When Values Are Needed Together
Use a structure when all member values must remain available simultaneously.
Using a Union Only Because It Appears Smaller
Memory reduction is useful only when overlapping alternatives correctly model the data.
Choosing Structure or Union
| Requirement | Recommended Type |
|---|---|
| Store a person's identifier, name and age together | Structure |
| Store x, y and z coordinates together | Structure |
| Represent either an integer, real number or text value | Tagged union |
| Represent a parser token with alternative payloads | Tagged union |
| Store metadata plus several independent values | Structure |
| Model an event with one of several payload types | Tagged union |
| Create a portable file or network format | Explicit serialization, not raw structure or union bytes |
| View hardware storage in several ways | Only a verified implementation-specific design |
Best Practices
Recommended Practices
- Use a structure when members must retain values simultaneously.
- Use a union when the value is one of several alternatives.
- Place a discriminator outside the union to create a tagged union.
- Keep the tag and selected union member synchronized.
- Initialize every tagged union to a documented valid state.
- Switch on the tag before reading or destroying a payload.
- Document ownership for pointer-containing alternatives.
- Release the current owned alternative before replacing it.
- Allocate a replacement first when failure should preserve the old value.
- Measure structure and union sizes instead of assuming them.
- Do not compare structures or unions using raw equality assumptions.
- Do not serialize raw object representations as portable data.
- Use
memcpy()when copying representation bytes deliberately. - Verify compiler and ABI rules for low-level union techniques.
- Test every tagged-union alternative and invalid-tag path.
Recommended Test Cases
| Test Case | Expected Concern |
|---|---|
| Initialize every structure member | All values remain available simultaneously |
| Initialize each union alternative | Tag and selected member agree |
| Change union alternative | Old owned resources are released correctly |
| Allocation failure during replacement | Original value remains valid |
| Null text alternative | Printing and cleanup follow the documented policy |
| Invalid tag | Operation rejects or reports the invalid state |
| Copy tagged union | Pointer ownership is not accidentally duplicated |
| Compare values of equal tags | Corresponding selected members are compared |
| Compare values of different tags | Values are considered different |
| Multiple toolchains | Exact measured sizes are not assumed to match |
Recommended Compiler Commands
Warning-enabled C17 Build
gcc -std=c17 -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wswitch-enum program.c -o program
Clang C17 Build
clang -std=c17 -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wswitch-enum program.c -o program
Development Build with Sanitizers
gcc -std=c17 -Wall -Wextra -Wpedantic -fsanitize=undefined,address -fno-omit-frame-pointer -g program.c -o program
Complete switch diagnostics are especially useful for tagged unions because adding a new enumeration alternative should prompt a review of printing, copying, comparison and destruction logic.
Practice Exercises
- Create a structure and union containing
char,intanddoublemembers. - Print their measured sizes and alignments.
- Print the addresses of every union member.
- Demonstrate that structure members retain values simultaneously.
- Create a tagged union representing integer, real and text values.
- Write constructor functions for every tagged-union alternative.
- Write a printing function that checks the tag.
- Write a member-aware equality function.
- Add dynamically allocated text ownership.
- Write correct cleanup logic for every alternative.
- Implement safe copying of the tagged union with deep copying for text.
- Encode the tagged value into an explicit byte-oriented format.
- Decode the external format with bounds validation.
- Compare structure and union layouts under two supported compilers.
Frequently Asked Questions
What is the main difference between a structure and a union?
Structure members occupy distinct storage regions, while union members overlap in one shared storage region.
Can all structure members hold values simultaneously?
Yes. Each structure member has its own position in the structure object.
Can all union members retain independent values simultaneously?
No. Union members overlap, so writing one member reuses the shared storage.
How is structure size determined?
It includes storage for all members plus internal and trailing padding selected by the implementation.
How is union size determined?
It must provide sufficient storage and alignment for every member, which means it is at least large enough for its largest required member representation.
What is an active or selected union member?
It is the member corresponding to the value alternative that the application most recently stored and intends to interpret.
Does a union remember its selected member?
No. Store a separate tag when the program needs to identify the selected alternative reliably.
What is a tagged union?
It is commonly a structure containing a discriminator and a union, where the discriminator identifies the selected union member.
Can structures and unions be assigned?
Yes. Compatible objects of the same structure or union type can be assigned.
Can structures or unions be compared using ==?
No. Compare their logical members explicitly.
Should memcmp() be used for logical comparison?
Generally no. Padding and alternative object representations can make raw byte comparison different from domain-level equality.
Are unions always smaller than structures?
They commonly require less storage when several large alternatives would otherwise be stored simultaneously, but exact sizes must be measured.
Are unions safe for protocol serialization?
Do not use raw union storage as a portable protocol format. Encode the type and payload explicitly.
Can a union contain structures and arrays?
Yes. A union member can use an eligible object type, including structure and array types.
When should a structure be preferred?
Prefer a structure when the object conceptually contains several related values that must remain available together.
When should a union be preferred?
Prefer a union when the object represents one alternative from a known set and the program can reliably track that alternative.
Key Takeaway
A structure gives each member distinct storage so all member values can exist together. A union overlays all members in shared storage and is appropriate when one object represents one of several alternatives. Because a union does not automatically identify the selected alternative, robust applications commonly place an enumeration tag beside the union in an outer structure. Measure sizes instead of assuming them, manage resource-owning alternatives explicitly, and never treat raw structure or union memory as an automatically portable file, network or hardware format.