Structures
Last Updated: Jan 2026
Arrays let you group elements of the same data type, but real-world data is rarely uniform. A student record, for example, consists of a name (char[]), an ID (int), and a GPA (float).
Structures (struct) are custom, user-defined data types in C that allow you to group variables of different data types under a single name in memory.
Without structures, managing related data requires maintaining separate parallel arrays or disconnected variables:
char student_names[100][50];
int student_ids[100];
float student_gpas[100];
With a structure, you bundle them into a single type:
struct Student {
char name[50];
int id;
float gpa;
};
Declaration, Variables, & Member Access (.)
Declaring a struct creates a template/blueprint. Memory is allocated only when you declare a structure variable.
You use the dot operator (.) to access individual members inside a structure variable.
#include <stdio.h>
#include <string.h>
// 1. Declare the structure template
struct Student {
char name[50];
int id;
float gpa;
};
int main(void) {
// 2. Declare and initialize structure variables
struct Student s1 = {"Smith", 101, 3.90f}; // Designated order
struct Student s2; // Uninitialized
// 3. Access and assign members using the dot (.) operator
s2.id = 102;
s2.gpa = 3.65f;
strcpy(s2.name, "Amit"); // Strings must be copied, not assigned directly
printf("Student 1: %s (ID: %d) - GPA: %.2f\n", s1.name, s1.id, s1.gpa);
printf("Student 2: %s (ID: %d) - GPA: %.2f\n", s2.name, s2.id, s2.gpa);
return 0;
}
Passing & Returning Structures
When you pass a structure directly to a function, C makes a complete byte-for-byte copy of the structure on the stack (Pass by Value).
#include <stdio.h>
struct Point {
int x;
int y;
};
// 1. Passing structure by value (receives a copy)
void printPoint(struct Point p) {
printf("Point: (%d, %d)\n", p.x, p.y);
}
// 2. Returning a structure from a function
struct Point createPoint(int x, int y) {
struct Point temp = {x, y};
return temp; // Returns a copy of the structure
}
Pointer to Structure & the Arrow Operator (->)
Passing large structures by value wastes memory and CPU time because copying large blocks of memory on the stack is slow. Instead, pass a pointer to the structure.
To access members via a structure pointer, you can either:
- Dereference and use dot: (*ptr).member (Parentheses are mandatory due to operator precedence!)
- Use the arrow operator (->): ptr->member (Cleaner and standard convention)
#include <stdio.h>
struct Player {
char username[20];
int score;
};
// Modify original structure in-place using pointer
void addScore(struct Player *p, int points) {
p->score += points; // Equivalent to (*p).score += points;
}
int main(void) {
struct Player p1 = {"PixelKing", 1500};
addScore(&p1, 500); // Pass address
printf("Updated Score: %d\n", p1.score); // Output: 2000
return 0;
}
Array of Structures
Just like built-in types, you can create arrays of structures to represent database-like records.
struct Book {
char title[40];
float price;
};
int main(void) {
// Array storing 3 Book structures
struct Book library[3] = {
{"C Programming Language", 45.50},
{"Data Structures", 55.00},
{"Clean Code", 40.00}
};
for (int i = 0; i < 3; i++) {
printf("%s - $%.2f\n", library[i].title, library[i].price);
}
}
Nested Structures
A structure can contain another structure as one of its members.
struct Date {
int day;
int month;
int year;
};
struct Employee {
int id;
char name[30];
struct Date hire_date; // Nested structure member
};
int main(void) {
struct Employee emp = {1001, "Sah", {15, 8, 2024}};
// Access nested members using chained dot operators
printf("Hired on: %d/%d/%d\n", emp.hire_date.day, emp.hire_date.month, emp.hire_date.year);
}
Structure Padding & Memory Alignment
You might expect a structure's total size in RAM to equal the sum of its individual member sizes. However, CPUs read memory in word alignment blocks (typically 4 or 8 bytes). To optimize performance, compilers insert invisible empty bytes called padding.
struct UnpaddedExample {
char a; // 1 byte
// 3 bytes of compiler padding inserted here!
int b; // 4 bytes
char c; // 1 byte
// 3 bytes of compiler padding inserted here!
};
// Total size = 12 bytes! (Not 1 + 4 + 1 = 6 bytes)
Optimizing Memory Layout
You can minimize padding by ordering members from largest data type to smallest:
struct OptimizedExample {
int b; // 4 bytes
char a; // 1 byte
char c; // 1 byte
// 2 bytes padding at end to align boundary
};
// Total size = 8 bytes!
Tip: You can inspect exact runtime memory allocation using sizeof(struct YourStruct).
Self-Referential Structures
A structure that contains a pointer to an instance of its own type. This is the fundamental building block of dynamic, non-contiguous data structures like Linked Lists, Trees, and Graphs.
struct Node {
int data;
struct Node *next; // Pointer to the next struct Node in memory
};
typedef with Struct
Without typedef, declaring structure variables requires repeatedly writing the struct keyword. Combining typedef with struct creates clean, first-class type abstractions.
#include <stdio.h>
// Option 1: Anonymous struct with typedef
typedef struct {
double x;
double y;
} Point; // 'Point' is now a custom type name
// Option 2: Named struct with typedef (Required for self-referential structures)
typedef struct Node {
int data;
struct Node *next; // Must use 'struct Node*' inside because 'Node' alias isn't finished yet
} Node;
int main(void) {
Point p1 = {10.5, 20.0}; // No 'struct' keyword needed!
Node n1 = {100, NULL};
printf("Point: (%.1f, %.1f)\n", p1.x, p1.y);
return 0;
}
