C Structures and Unions: Understanding Custom Data Types
Structures and unions allow C programmers to group related values under a single type. They are fundamental for representing records, configuration data, hardware information, and many other kinds of structured data.
What is a Structure?
A structure, declared with the struct keyword, groups multiple members that can have different data types. Each member has its own storage within the structure.
struct Student
{
int id;
char name[50];
float marks;
};
Creating a Structure Variable
After defining a structure type, you can create variables of that type and access their members using the dot operator.
struct Student student;
student.id = 101;
student.marks = 92.5f;
printf("ID: %d\n", student.id);
printf("Marks: %.1f\n", student.marks);
Structure Initialization
A structure can be initialized when it is declared. Designated initializers make it possible to initialize specific members by name.
struct Student student = {
.id = 101,
.name = "Alex",
.marks = 92.5f
};
Array of Structures
An array of structures is useful when a program needs to store multiple records of the same type.
struct Student students[3] = {
{1, "Alex", 91.0f},
{2, "Sam", 85.5f},
{3, "Jordan", 88.0f}
};
for (int i = 0; i < 3; i++)
{
printf("%d: %s - %.1f\n",
students[i].id,
students[i].name,
students[i].marks);
}
Pointers to Structures
A pointer can point to a structure. The arrow operator is used to access members through a structure pointer.
struct Student student = {
.id = 101,
.name = "Alex",
.marks = 90.0f
};
struct Student *ptr = &student;
printf("%s\n", ptr->name);
ptr->marks = 95.0f;
Structures with Functions
Structures can be passed to functions by value or by pointer. Passing a pointer avoids copying the entire structure and allows the function to modify the original object.
struct Rectangle
{
double width;
double height;
};
void printArea(const struct Rectangle *rect)
{
printf("Area: %.2f\n", rect->width * rect->height);
}
int main(void)
{
struct Rectangle rect = {
.width = 10.0,
.height = 5.0
};
printArea(&rect);
return 0;
}
Nested Structures
A structure can contain another structure as a member. This helps model related information in layers.
struct Address
{
char city[50];
int zip;
};
struct Employee
{
int id;
char name[50];
struct Address address;
};
struct Employee employee = {
.id = 10,
.name = "Alex",
.address = {
.city = "Bengaluru",
.zip = 560001
}
};
Using typedef with Structures
typedef can create a shorter name for a structure type and make declarations more convenient.
typedef struct
{
int x;
int y;
} Point;
Point p = {
.x = 10,
.y = 20
};
What is a Union?
A union is similar to a structure, but all of its members share the same memory location. At a given time, the stored representation is intended to be interpreted as one member.
union Value
{
int integer;
float decimal;
char character;
};
Using a Union
Assigning a value to one union member uses the same underlying storage that is shared by the other members.
union Value value;
value.integer = 100;
printf("Integer: %d\n", value.integer);
Structure vs Union
| Feature | Structure | Union |
|---|---|---|
| Member Storage | Each member has separate storage | Members share storage |
| Simultaneous Values | All members can hold values simultaneously | Only one active interpretation is normally intended at a time |
| Size | At least enough for all members plus possible padding | Typically enough for the largest member plus possible alignment |
| Common Use | Records and objects | Alternative representations |
Memory Size of a Structure
The size of a structure can be larger than the sum of its member sizes because implementations may insert padding to satisfy alignment requirements.
#include <stdio.h>
struct Example
{
char letter;
int number;
double value;
};
int main(void)
{
printf("Structure size: %zu\n", sizeof(struct Example));
return 0;
}
Memory Size of a Union
A union's size is sufficient to contain its largest member, subject to the implementation's alignment requirements.
#include <stdio.h>
union Data
{
int number;
double decimal;
char text[32];
};
int main(void)
{
printf("Union size: %zu\n", sizeof(union Data));
return 0;
}
Tagged Unions
A common design combines an enum with a union. The enum records which member is currently meaningful, while the union stores the associated value.
#include <stdio.h>
typedef enum
{
TYPE_INT,
TYPE_DOUBLE
} ValueType;
typedef struct
{
ValueType type;
union
{
int integer;
double decimal;
} data;
} Value;
int main(void)
{
Value value = {
.type = TYPE_DOUBLE,
.data.decimal = 42.5
};
if (value.type == TYPE_DOUBLE)
{
printf("%.2f\n", value.data.decimal);
}
return 0;
}
Anonymous Structures and Unions
C also supports anonymous structure and union members in implementations that provide the relevant language version features. They can make nested data access more convenient.
struct Device
{
union
{
int id;
float voltage;
} value;
};
struct Device device;
device.value.id = 25;
Dynamic Structures
Structures can be allocated dynamically using malloc. This is particularly useful for linked lists, trees, and other dynamic data structures.
#include <stdio.h>
#include <stdlib.h>
struct Node
{
int value;
struct Node *next;
};
int main(void)
{
struct Node *node = malloc(sizeof *node);
if (node == NULL)
{
return 1;
}
node->value = 42;
node->next = NULL;
printf("Value: %d\n", node->value);
free(node);
return 0;
}
Structures for Records
Structures are ideal for representing records containing related fields, such as products, users, devices, or configuration settings.
typedef struct
{
int id;
char name[40];
double price;
int stock;
} Product;
Product product = {
.id = 1001,
.name = "Keyboard",
.price = 49.99,
.stock = 25
};
Common Mistakes
- Confusing the dot operator with the arrow operator
- Assuming structure members are always packed without padding
- Reading a union member without understanding its stored representation
- Forgetting to initialize structure members when required
- Returning or storing pointers to invalid structure objects
- Allocating structures dynamically without checking for failure
Best Practices
- Use structures to model related data clearly
- Use const structure pointers when a function only needs to read data
- Use designated initializers for clarity
- Use unions only when shared storage provides a real benefit
- Pair unions with a tag when multiple representations are possible
- Use sizeof *pointer for dynamic structure allocation
Real-World Applications
- Database-style records
- Linked lists and trees
- Network packet representations
- Device configuration
- Embedded systems
- Game objects
- Application settings
Practice Exercises
- Create a structure representing a student
- Store multiple employees in an array of structures
- Pass a structure to a function using a pointer
- Create a nested address structure
- Build a linked-list node using a structure
- Create a union for multiple value types
- Build a tagged union using enum and union
Conclusion
Structures and unions are essential tools for organizing data in C. Structures provide separate storage for related members, while unions allow multiple members to share the same storage. Understanding the difference helps you choose the right representation for each program.