Structures (struct)
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.
#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;
}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:
#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;
}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.
#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;
}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.
#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;
}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.
#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;
}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. Ifpis a pointer, writep->roll, notp.roll. - Assigning a string with
=.s.name = "Isha";fails becausenameis an array. Usesnprintf(s.name, sizeof s.name, "%s", "Isha"), or initialise it in the declaration. - Passing big structures by value without need. Pass a
constpointer instead.
Key points
structgroups 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
constwhen 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
#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;
}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?
p->roll is shorthand for (*p).roll; *p.roll would apply * to p.roll.
2. What happens with struct student b = a;?
Structure assignment copies all members, and the two variables stay independent.
3. Why can sizeof a structure be larger than the sum of its members?
Padding bytes are inserted so that each member starts at a suitably aligned address.
4. Which statement correctly compares two structures a and b that each have an int id?
C has no == for whole structures; compare the members instead.
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
