Structures (struct)

C · Lesson 21 of 32 6 min read Updated

A student has a name, a roll number and a percentage. A train ticket has a PNR, two stations, a date and a fare. You could keep each piece in a separate variable, but then nothing ties them together. A structure, written with the keyword struct, groups several related values of different types into one new type.

In this lesson you will define structures, create and initialise structure variables, store them in arrays, pass them to functions with pointers, nest one structure inside another and see how the compiler lays them out in memory.

Defining a struct and creating variables

A structure definition lists its members, each with a type and a name. It creates a new type, but no variable yet. Note the semicolon after the closing brace.

C
#include <stdio.h>

struct student {
    char name[30];
    int roll;
    double percentage;
};

int main(void)
{
    struct student s1 = {"Aarav Mehta", 12, 86.4};
    struct student s2 = {.roll = 15, .name = "Priya Nair", .percentage = 91.3};

    printf("%s (roll %d): %.1f%%\n", s1.name, s1.roll, s1.percentage);
    printf("%s (roll %d): %.1f%%\n", s2.name, s2.roll, s2.percentage);

    s1.percentage = 88.0;        // change one member
    struct student copy = s1;    // copies every member, including the array
    copy.roll = 99;
    printf("s1 roll is still %d, copy has %d\n", s1.roll, copy.roll);
    return 0;
}
Output
Aarav Mehta (roll 12): 86.4%
Priya Nair (roll 15): 91.3%
s1 roll is still 12, copy has 99

The type's full name is struct student. You reach each member with the dot operator: s1.name, s1.roll. The first initialiser lists values in member order. The second uses designated initialisers (.roll = 15), which name each member, so the order does not matter and the code is easier to read. Any member you leave out is set to zero.

Unlike arrays, structures can be assigned with =: copy = s1 copies every member, even the name array inside. The copy is independent, so changing copy.roll does not touch s1.

typedef: a shorter type name

Writing struct student everywhere gets long. typedef creates an alias, so many programmers define structures like this:

C
#include <stdio.h>

typedef struct {
    char name[20];
    int runs;
    int balls;
} Batter;

double strike_rate(Batter b)
{
    return b.runs * 100.0 / b.balls;
}

int main(void)
{
    Batter team[] = {
        {"Rohan", 45, 30},
        {"Kabir", 72, 60},
        {"Farhan", 33, 15},
    };
    size_t n = sizeof team / sizeof team[0];

    size_t best = 0;
    for (size_t i = 0; i < n; i++) {
        printf("%-8s %3d runs, strike rate %6.2f\n",
               team[i].name, team[i].runs, strike_rate(team[i]));
        if (strike_rate(team[i]) > strike_rate(team[best])) {
            best = i;
        }
    }
    printf("Fastest scorer: %s\n", team[best].name);
    return 0;
}
Output
Rohan     45 runs, strike rate 150.00
Kabir     72 runs, strike rate 120.00
Farhan    33 runs, strike rate 220.00
Fastest scorer: Farhan

Now the type is simply Batter. team is an array of structures: team[i] is one Batter, and team[i].name is that batter's name. You will learn more about typedef in Unions, Enums & typedef.

Pointers to structures and the -> operator

Passing a structure to a function copies all of it, like any other argument. For large structures that wastes time, and the function cannot change the original. So structures are usually passed by pointer. To reach a member through a pointer, use the arrow operator ->: acc->balance means (*acc).balance.

C
#include <stdio.h>

typedef struct {
    char holder[30];
    long long balance_paise;
} Account;

bool withdraw(Account *acc, long long amount)
{
    if (amount <= 0 || amount > acc->balance_paise) {
        return false;
    }
    acc->balance_paise -= amount;
    return true;
}

void print_account(const Account *acc)
{
    printf("%s: Rs %lld.%02lld\n", acc->holder,
           acc->balance_paise / 100, acc->balance_paise % 100);
}

int main(void)
{
    Account acc = {"Meera Iyer", 250075};   // Rs 2,500.75
    print_account(&acc);

    if (withdraw(&acc, 120050)) {
        printf("Withdrew Rs 1,200.50\n");
    }
    if (!withdraw(&acc, 500000)) {
        printf("Rs 5,000.00 refused: not enough balance\n");
    }
    print_account(&acc);
    return 0;
}
Output
Meera Iyer: Rs 2500.75
Withdrew Rs 1,200.50
Rs 5,000.00 refused: not enough balance
Meera Iyer: Rs 1300.25

withdraw receives Account * because it changes the balance. print_account receives const Account *: no copy is made, and const promises that it only reads. The money is stored in paise as a whole number, so there are no rounding errors; %02lld prints the paise with two digits.

Nested structures and compound literals

A member can itself be a structure. Use the dot operator twice to reach inside it.

C
#include <stdio.h>

struct date {
    int day, month, year;
};

struct ticket {
    char pnr[11];
    char from[20];
    char to[20];
    struct date journey;
    int fare;
};

int main(void)
{
    struct ticket t = {
        .pnr = "4521876390",
        .from = "Jaipur",
        .to = "Lucknow",
        .journey = {.day = 14, .month = 11, .year = 2026},
        .fare = 1185,
    };

    printf("PNR %s: %s to %s\n", t.pnr, t.from, t.to);
    printf("Date: %02d/%02d/%d, fare Rs %d\n",
           t.journey.day, t.journey.month, t.journey.year, t.fare);

    t.journey = (struct date){.day = 15, .month = 11, .year = 2026};
    printf("Rescheduled to %02d/%02d/%d\n",
           t.journey.day, t.journey.month, t.journey.year);
    return 0;
}
Output
PNR 4521876390: Jaipur to Lucknow
Date: 14/11/2026, fare Rs 1185
Rescheduled to 15/11/2026

The pnr array has 11 characters: 10 digits plus '\0'. The line (struct date){...} is a compound literal (C99): an unnamed structure value created on the spot, which is handy for assigning several members at once.

Size and padding

You might expect a structure's size to be the sum of its members, but often it is larger. Processors read data fastest when, for example, a 4-byte int starts at an address that is a multiple of 4. The compiler inserts unused padding bytes to keep members aligned.

C
#include <stdio.h>

struct loose {
    char grade;     // 1 byte, then 3 bytes of padding
    int marks;      // 4 bytes
    char section;   // 1 byte, then 3 bytes of padding
};

struct tight {
    int marks;      // 4 bytes
    char grade;     // 1 byte
    char section;   // 1 byte, then 2 bytes of padding
};

int main(void)
{
    printf("loose: %zu bytes\n", sizeof(struct loose));
    printf("tight: %zu bytes\n", sizeof(struct tight));
    return 0;
}
Output
loose: 12 bytes
tight: 8 bytes

These are typical values on common 64-bit systems; the exact padding is up to the compiler. Ordering members from largest to smallest often saves space, which matters when you store millions of records. Always use sizeof, never your own sum, when you need a structure's size.

Common mistakes

  • Missing semicolon after the definition. struct date { ... } without ; causes confusing errors on the following lines.
  • Comparing structures with ==. C does not allow it. Compare the members you care about, one by one.
  • Using . on a pointer. If p is a pointer, write p->roll, not p.roll.
  • Assigning a string with =. s.name = "Isha"; fails because name is an array. Use snprintf(s.name, sizeof s.name, "%s", "Isha"), or initialise it in the declaration.
  • Passing big structures by value without need. Pass a const pointer instead.

Key points

  • struct groups related members of different types into one new type; end the definition with ;.
  • Reach members with . on a variable and -> on a pointer.
  • Designated initialisers (.roll = 15) are clear and set every member you omit to zero.
  • Assigning one structure to another copies all members; == does not work on structures.
  • Pass structures by pointer, using const when the function only reads them.
  • Padding can make a structure larger than the sum of its members; always use sizeof.

Exercise

A Kochi stationery shop tracks products. Define typedef struct { char name[30]; int price; int quantity; } Product;. Create an array with a notebook (Rs 45, 120 in stock), a pen (Rs 12, 300) and a geometry box (Rs 149, 25). Print each product's stock value (price × quantity) and the total stock value of the shop.

Show solution
C
#include <stdio.h>

typedef struct {
    char name[30];
    int price;
    int quantity;
} Product;

int stock_value(const Product *p)
{
    return p->price * p->quantity;
}

int main(void)
{
    Product items[] = {
        {"Notebook", 45, 120},
        {"Pen", 12, 300},
        {"Geometry box", 149, 25},
    };
    size_t n = sizeof items / sizeof items[0];

    int total = 0;
    for (size_t i = 0; i < n; i++) {
        int value = stock_value(&items[i]);
        printf("%-14s Rs %d\n", items[i].name, value);
        total += value;
    }
    printf("Total stock value: Rs %d\n", total);
    return 0;
}
Output
Notebook       Rs 5400
Pen            Rs 3600
Geometry box   Rs 3725
Total stock value: Rs 12725

&items[i] passes the address of one element, so stock_value reads it through -> without copying the structure.

Quick Quiz

1. How do you reach the roll member through a pointer p to a structure?

2. What happens with struct student b = a;?

3. Why can sizeof a structure be larger than the sum of its members?

4. Which statement correctly compares two structures a and b that each have an int id?

Learn C with a mentor

Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call