Real-world data rarely comes in a single, uniform shape. A student record needs a name, a roll number, and marks together as one unit. A video's metadata needs a title, a duration, and a view count grouped in a single, meaningful package. Arrays, as useful as they are, can only store elements of a single data type, which makes them a poor fit for combining different kinds of related information together. This is exactly the gap that structures are designed to fill.
Structures allow you to define your own custom data type, one that groups several different variables, possibly of different types, under a single name. Unions, while syntactically similar to structures, take a very different approach to memory, sharing a single memory space among all their members instead of allocating separate space for each one.
In this tutorial, you will learn how to define and use structures, how to initialize and access structure members, how structures can be nested inside one another, how arrays of structures work, how structures interact with functions, and how unions differ from structures both in behavior and in memory usage.
A structure is a user-defined data type that groups together variables of different types under a single name. Each individual variable within a structure is referred to as a member, and once defined, a structure can be used to create variables that hold an entire set of related information as a single unit.
struct StructureName {
dataType member1;
dataType member2;
};
#include <stdio.h>
struct Video {
char title[50];
int views;
float rating;
};
int main() {
struct Video tutorial;
return 0;
}
Here, a new data type called Video has been defined, capable of storing a title, a view count, and a rating together, though a variable of this type has not yet been given any actual values.
Once a structure has been defined, individual variables of that structure type can be created and assigned values, either all at once during declaration or individually afterward using the dot operator.
#include <stdio.h>
struct Video {
char title[50];
int views;
float rating;
};
int main() {
struct Video tutorial = {"Structures in C Explained", 4300, 4.7};
printf("Title: %s\n", tutorial.title);
printf("Views: %d\n", tutorial.views);
printf("Rating: %.1f", tutorial.rating);
return 0;
}
Title: Structures in C Explained Views: 4300 Rating: 4.7
The dot operator, placed between the structure variable's name and a specific member, is what allows individual pieces of data within the structure to be accessed and displayed separately.
Structure members can be updated after the structure variable has already been created, using the same dot operator that is used for accessing their values in the first place.
#include <stdio.h>
struct Video {
char title[50];
int views;
};
int main() {
struct Video tutorial;
tutorial.views = 1000;
tutorial.views = tutorial.views + 500;
printf("Updated views: %d", tutorial.views);
return 0;
}
Updated views: 1500
A structure can contain another structure as one of its members, allowing related groups of data to be organized hierarchically. This is particularly useful when a piece of information naturally breaks down into smaller, logically grouped components.
#include <stdio.h>
struct Date {
int day;
int month;
int year;
};
struct Video {
char title[50];
struct Date uploadDate;
};
int main() {
struct Video tutorial = {"Nested Structures in C", {15, 8, 2026}};
printf("Title: %s\n", tutorial.title);
printf("Uploaded on: %d/%d/%d", tutorial.uploadDate.day, tutorial.uploadDate.month, tutorial.uploadDate.year);
return 0;
}
Title: Nested Structures in C Uploaded on: 15/8/2026
Accessing a member of the nested structure requires chaining the dot operator, first reaching the outer structure's member, and then reaching into that member's own fields.
Just as an array can store multiple values of a basic data type, it can also store multiple variables of a structure type, making it possible to manage collections of related records, such as a list of videos or a list of students, using a single array.
#include <stdio.h>
struct Video {
char title[30];
int views;
};
int main() {
struct Video playlist[2] = {
{"Introduction to Structures", 3200},
{"Structures with Functions", 2800}
};
int i;
for (i = 0; i < 2; i++) {
printf("%s - %d views\n", playlist[i].title, playlist[i].views);
}
return 0;
}
Introduction to Structures - 3200 views Structures with Functions - 2800 views
This pattern of combining an array with a structure is extremely common in real-world programs, since it allows a whole collection of similar, multi-field records to be processed together using loops.
Structures can be passed to functions, just like ordinary variables, allowing related logic to be organized separately from the main program while still working with an entire grouped record of data at once.
#include <stdio.h>
struct Video {
char title[30];
int views;
};
void displayVideo(struct Video v) {
printf("%s has %d views", v.title, v.views);
}
int main() {
struct Video tutorial = {"Structures and Functions", 5000};
displayVideo(tutorial);
return 0;
}
Structures and Functions has 5000 views
By default, a structure passed to a function like this is passed by value, meaning the function receives a full copy of the structure, and any changes made inside the function do not affect the original variable back in main.
A union looks syntactically very similar to a structure, but behaves completely differently when it comes to memory. While a structure allocates separate memory for each of its members, a union allocates a single shared block of memory large enough to hold its largest member, and all members occupy that same space.
union UnionName {
dataType member1;
dataType member2;
};
#include <stdio.h>
union Data {
int intValue;
float floatValue;
};
int main() {
union Data value;
value.intValue = 10;
printf("Integer value: %d\n", value.intValue);
value.floatValue = 5.5;
printf("Float value: %.1f", value.floatValue);
return 0;
}
Integer value: 10 Float value: 5.5
Because both members share the same memory location, assigning a new value to floatValue overwrites whatever was previously stored in intValue, since they are not actually stored in separate places the way structure members are.
| Aspect | Structure | Union |
|---|---|---|
| Memory Allocation | Allocates separate memory for each member, so the total size is the sum of all members. | Allocates a single shared memory block, with a size equal to its largest member. |
| Member Access | All members can be accessed and hold valid values at the same time. | Only the most recently assigned member holds a valid, meaningful value. |
| Typical Use Case | Grouping different, unrelated pieces of data that all need to exist together. | Situations where only one of several possible values needs to be stored at any given time, saving memory. |
| Mistake | Correct Practice |
|---|---|
| Forgetting the semicolon after closing the structure or union definition. | Always place a semicolon immediately after the closing curly brace of a structure or union definition. |
| Reading a union member that was not the most recently assigned one. | Only rely on the member of a union that was most recently written, since earlier values are overwritten. |
| Assuming structures passed to functions automatically reflect changes back in the caller. | Remember that structures passed by value give the function only a copy, unless a pointer to the structure is used instead. |
| Confusing the total size of a structure with the size of a union holding similar members. | Remember that a structure's size is the sum of its members, while a union's size matches only its largest member. |
Structures and unions both allow C programmers to define custom data types that go beyond the basic types covered earlier in this series, but they take fundamentally different approaches to memory. Structures group related variables together while giving each one its own dedicated space, making them ideal for records that need to hold multiple pieces of data simultaneously. Unions, by contrast, share a single memory space among all their members, making them useful in situations where only one value needs to be active at any given moment.
In this tutorial, you learned how to define and use structures, how nested structures and arrays of structures extend these ideas further, how structures interact with functions, how unions differ from structures in behavior and memory usage, and when each of these tools is the more appropriate choice. With this understanding in place, you are ready to explore dynamic memory allocation, which builds on many of the pointer concepts covered earlier in this series.