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:

  1. Dereference and use dot: (*ptr).member (Parentheses are mandatory due to operator precedence!)
  2. 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;
}


Liked This Tutorial

Scan QR Code To Leave A Review Or

Click Here To Leave A Review
Scan QR