Structures in C – A Complete Guide with Examples

A structure in C is a user-defined type that groups related variables of different types under one name. Where an array holds many values of the same type, a struct holds a fixed set of named fields — each with its own type. This is what makes it possible to represent a student record, a geometric point, a network packet header, or any compound object in a single variable that can be passed around, stored in arrays, and pointed to by a pointer.

Defining and Using a Struct

The struct keyword introduces the definition. Each field is a normal variable declaration inside the braces:

#include <stdio.h>

struct Point {
    int x;
    int y;
};

int main(void)
{
    struct Point p;   /* declare a variable of type struct Point */
    p.x = 3;
    p.y = 4;

    printf("Point: (%d, %d)\n", p.x, p.y);
    return 0;
}
Point: (3, 4)

The dot (.) operator accesses a member. p.x reads or writes the x field of p.

Initializing a Struct

Structs can be initialised at the point of declaration using a brace-enclosed list, in field order:

#include <stdio.h>

struct Student {
    int  id;
    char name[32];
    float gpa;
};

int main(void)
{
    struct Student s = {101, "Alice", 3.8f};

    printf("ID:   %d\n",   s.id);
    printf("Name: %s\n",   s.name);
    printf("GPA:  %.1f\n", s.gpa);
    return 0;
}
ID:   101
Name: Alice
GPA:  3.8

typedef — Naming the Type

typedef lets you refer to the struct without repeating the struct keyword every time:

typedef struct {
    int x;
    int y;
} Point;

/* Now you can write: */
Point p = {3, 4};   /* instead of: struct Point p = {3, 4}; */

Both styles are valid. In larger codebases the typedef form is more common; in system code and K&R style the explicit struct tag is preferred because it is always explicit about the type.

Passing Structs to Functions

Passing a struct by value copies all its fields. Modifying the copy does not change the original:

#include <stdio.h>

struct Point { int x; int y; };

double distance_from_origin(struct Point p)
{
    double dx = p.x, dy = p.y;
    /* simple integer-only Euclidean for demo */
    return dx * dx + dy * dy;  /* returns squared distance */
}

int main(void)
{
    struct Point p = {3, 4};
    printf("Distance squared: %.0f\n", distance_from_origin(p));
    return 0;
}
Distance squared: 25

For large structs, copying is expensive. Pass a pointer instead — and use const if the function only reads the struct:

#include <stdio.h>

struct Student {
    int  id;
    char name[32];
    float gpa;
};

void print_student(const struct Student *s)
{
    printf("%d  %-20s  %.2f\n", s->id, s->name, s->gpa);
}

void raise_gpa(struct Student *s, float amount)
{
    s->gpa += amount;
}

int main(void)
{
    struct Student s = {42, "Bob", 3.2f};
    print_student(&s);
    raise_gpa(&s, 0.3f);
    print_student(&s);
    return 0;
}
42  Bob                   3.20
42  Bob                   3.50

The arrow operator (->) is shorthand for (*ptr).field. Use . when you have a struct variable; use -> when you have a pointer to a struct.

Arrays of Structs

An array of structs is the natural way to represent a table of records:

#include <stdio.h>

struct Student {
    int  id;
    char name[32];
    float gpa;
};

int main(void)
{
    struct Student class[3] = {
        {1, "Alice", 3.8f},
        {2, "Bob",   3.2f},
        {3, "Carol", 3.9f}
    };
    int i;

    printf("%-4s  %-20s  %s\n", "ID", "Name", "GPA");
    printf("----------------------------------------\n");
    for (i = 0; i < 3; i++)
        printf("%-4d  %-20s  %.2f\n",
               class[i].id, class[i].name, class[i].gpa);

    return 0;
}
ID    Name                  GPA
----------------------------------------
1     Alice                 3.80
2     Bob                   3.20
3     Carol                 3.90

Nested Structs

A struct field can itself be a struct. This is useful for composing larger types from smaller reusable ones:

#include <stdio.h>

struct Date {
    int day;
    int month;
    int year;
};

struct Employee {
    int         id;
    char        name[32];
    struct Date joining_date;
};

int main(void)
{
    struct Employee e = {101, "Alice", {15, 3, 2024}};

    printf("Name:    %s\n", e.name);
    printf("Joined:  %02d/%02d/%04d\n",
           e.joining_date.day,
           e.joining_date.month,
           e.joining_date.year);
    return 0;
}
Name:    Alice
Joined:  15/03/2024

Structs with Pointer Fields — Dynamic Strings

A struct field can be a pointer. This is common when the data size is not known at compile time:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Employee {
    int   id;
    char *name;   /* pointer to heap-allocated string */
};

int main(void)
{
    struct Employee e;
    e.id   = 7;
    e.name = (char *)malloc(32);
    if (e.name == NULL) {
        fprintf(stderr, "malloc failed\n");
        return 1;
    }
    strcpy(e.name, "Diana");

    printf("ID: %d  Name: %s\n", e.id, e.name);

    free(e.name);
    e.name = NULL;
    return 0;
}
ID: 7  Name: Diana

Self-Referential Structs — The Foundation of Linked Lists

A struct can contain a pointer to its own type. This is how linked lists, trees, and graphs are built in C:

struct Node {
    int          data;
    struct Node *next;   /* pointer to the next node */
};

A full working linked list built on this foundation is in the Priority Queue using Structures post and the Singly Linked List in C guide.

Common Struct Mistakes

Mistake What goes wrong Fix
Comparing structs with == Compilation error — == is not defined for structs Compare field by field: a.x == b.x && a.y == b.y
Using . on a pointer Compilation error: ptr.field when ptr is struct S * Use ptr->field or (*ptr).field
Passing large struct by value to every function call Unnecessary copy of every field on every call Pass const struct S * for read-only access
Forgetting to free pointer fields Memory leak Free every heap-allocated field before the struct goes out of scope
Padding assumptions Struct size is not always the sum of field sizes; compiler may insert padding bytes Use sizeof(struct S), never hand-calculate

Struct Size and Padding

#include <stdio.h>

struct A { char c; int i; };          /* likely 8 bytes, not 5 */
struct B { int i; char c; };          /* likely 8 bytes, not 5 */
struct C { char a; char b; int i; };  /* likely 8 bytes          */

int main(void)
{
    printf("sizeof A: %zu\n", sizeof(struct A));
    printf("sizeof B: %zu\n", sizeof(struct B));
    printf("sizeof C: %zu\n", sizeof(struct C));
    return 0;
}
sizeof A: 8
sizeof B: 8
sizeof C: 8

The compiler inserts padding to align fields to their natural alignment boundary. Order your fields from largest to smallest to minimise wasted space.

How to Compile

gcc -ansi -Wall -Wextra -o structs structs.c

Related C Programs

📖 Chapter 6 of The C Programming Language by K&R (Amazon.in) · Amazon.com covers structures in depth, including bit-fields, unions, and typedef — all the patterns used in real systems code.

Preparing for a C interview? Practice with the C Programming Quiz — 150+ questions, free on Android.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>