Advanced Topics
Structures and Unions
Master C Structures & Unions — what/why/applications, structure vs union, initialization, pointers, dynamic memory, passing to functions, and bit fields — using LEARNCODEGURU’s 6-level learning model.
Level 1 Basics Content
C Structures & Unions Overview
Arrays group many values of the same type. A structure groups values of different types under one name. A union looks similar but stores only one member at a time in shared memory. This level covers what they are, why structures matter, their applications, a structure vs union comparison, initialization, pointers with structures, dynamic memory for structures, passing structures to functions, and bit fields.
What is a structure? What is a union?
A structure is a user-defined data type that bundles related variables (called members) into a single unit. Each member gets its own memory.
struct Student {
char name[50];
int roll;
float marks;
}; /* semicolon is required */
struct Student s1; /* declare a variable */
s1.roll = 101; /* dot operator accesses members */
A union uses the same syntax but all members share one memory location. Only one member holds a valid value at any moment.
union Value {
int i;
float f;
char c;
};
union Value v;
v.i = 65; /* now i is valid */
v.f = 3.14; /* now f is valid, i is garbage */
| Term | Meaning |
|---|---|
| Tag name | The name after struct / union (e.g. Student) |
| Member | One variable inside the structure |
. (dot) | Access a member of a structure variable |
-> (arrow) | Access a member through a pointer |
struct definition. It is required and produces confusing errors when missing.
Why structures?
- Group related data — one
Studentinstead of three loose variables - Mixed types — arrays cannot hold a string, an int, and a float together
- Pass one argument — send an entire record to a function at once
- Arrays of records —
Student list[100];models a whole class - Model the real world — the code starts describing the problem domain
- Build data structures — linked lists, stacks, trees all need self-referential structs
- File records — write and read a fixed-size record in one operation
/* Without a structure — three parallel arrays, easy to desynchronize */
char names[100][50];
int rolls[100];
float marks[100];
/* With a structure — one array of complete records */
struct Student list[100];
Applications of structures
| # | Application | Example |
|---|---|---|
| 1 | Student / employee records | name, id, marks, salary in one unit |
| 2 | Linked data structures | Linked lists, stacks, queues, trees, graphs |
| 3 | File handling | Write and read fixed-size records with fwrite / fread |
| 4 | Coordinates and geometry | Point, Rectangle, Circle |
| 5 | Date and time | day, month, year grouped together |
| 6 | Returning multiple values | A function returns one struct instead of using several pointers |
| 7 | Bank / inventory systems | Account, transaction, and product records |
| 8 | Grouping function parameters | Pass one config struct instead of eight arguments |
Where unions are used instead:
- Memory-constrained systems — embedded devices where every byte counts
- Variant / tagged types — a value that is an int or a float, never both
- Type reinterpretation — inspecting the bytes of a value (implementation-specific)
- Protocol messages — one of several message bodies in a fixed buffer
Structure vs union
| # | Aspect | Structure | Union |
|---|---|---|---|
| 1 | Keyword | struct | union |
| 2 | Memory allocated | Sum of all members (plus padding) | Size of the largest member only |
| 3 | Members stored | Each member has its own location | All members share one location |
| 4 | Valid at one time | All members simultaneously | Only the last one written |
| 5 | Changing one member | Does not affect the others | Overwrites the others |
| 6 | Initialization | All members can be initialized | Only one member (first, or by designator) |
| 7 | Memory efficiency | Uses more memory | Uses less memory |
| 8 | Typical use | Records with several fields | One value out of several possible types |
| 9 | Access syntax | . and -> | . and -> (identical) |
struct S { int i; float f; char c; }; /* sizeof ~ 12 bytes */
union U { int i; float f; char c; }; /* sizeof ~ 4 bytes */
union U u;
u.i = 10;
u.f = 2.5; /* u.i is now meaningless */
printf("%d\n", u.i); /* garbage — the bytes of 2.5 read as int */
Structure and union initialization
1. Positional initializer list:
struct Student s1 = { "Asha", 101, 88.5 };
2. Designated initializers (C99) — by member name:
struct Student s2 = { .roll = 102, .name = "Ravi" };
/* order does not matter; marks becomes 0 */
3. Partial initialization — the rest become zero:
struct Student s3 = { "Neha" }; /* roll = 0, marks = 0.0 */
struct Student s4 = { 0 }; /* everything zeroed */
4. Member-by-member assignment after declaration:
struct Student s5;
strcpy(s5.name, "Kiran"); /* strings need strcpy, not = */
s5.roll = 103;
s5.marks = 91.0;
5. Copying one structure into another:
struct Student s6 = s1; /* member-wise copy — allowed */
Union initialization — only one member:
union Value v1 = { 65 }; /* initializes the FIRST member */
union Value v2 = { .f = 3.14 }; /* designator picks another */
s.name = "Asha"; is invalid because you cannot
assign to an array. Use strcpy(s.name, "Asha"); after declaration, or a string
literal inside the initializer list.
Pointers with structures
A pointer to a structure uses the arrow operator -> to reach
members.
struct Student s = { "Asha", 101, 88.5 };
struct Student *p = &s;
printf("%d\n", p->roll); /* arrow — preferred */
printf("%d\n", (*p).roll); /* same thing, parentheses required */
p->marks = 95.0; /* changes s.marks */
| You have | Use | Example |
|---|---|---|
| A structure variable | Dot . | s.roll |
| A pointer to a structure | Arrow -> | p->roll |
| A pointer, using dot | Dereference first | (*p).roll |
| Nested structure | Chain them | e.doj.year or p->doj.year |
Self-referential structure — the basis of a linked list:
struct Node {
int data;
struct Node *next; /* pointer to the same type — allowed */
};
*p.roll? The dot binds tighter than *, so it
parses as *(p.roll) — a compile error. Use p->roll.
Dynamic memory for structures
Allocating a single structure:
#include <stdlib.h>
struct Student *p = malloc(sizeof(*p));
if (p == NULL) return 1;
strcpy(p->name, "Asha");
p->roll = 101;
p->marks = 88.5;
free(p);
p = NULL;
Allocating an array of structures:
int n, i;
scanf("%d", &n);
struct Student *list = calloc(n, sizeof(*list));
if (list == NULL) return 1;
for (i = 0; i < n; i++)
scanf("%d", &list[i].roll); /* list[i] is a struct → use dot */
free(list);
When the structure itself owns a pointer — free inside-out:
struct Person { char *name; int age; };
struct Person *q = malloc(sizeof(*q));
q->name = malloc(50); /* second allocation */
free(q->name); /* inner first */
free(q); /* then the struct */
list[i].roll uses a dot because list[i] is a
structure, while p->roll uses an arrow because p is a pointer.
Passing structures as function arguments
(a) By value — the function gets a copy:
void show(struct Student s) {
printf("%s %d\n", s.name, s.roll);
s.roll = 999; /* changes the copy only */
}
(b) By pointer — the function can modify the original:
void promote(struct Student *s) {
s->marks += 5; /* changes the caller's struct */
}
promote(&s1);
(c) By const pointer — efficient and read-only:
void print(const struct Student *s) {
printf("%s %.2f\n", s->name, s->marks);
/* s->marks = 0; would be a compile error */
}
(d) Returning a structure by value:
struct Point makePoint(int x, int y) {
struct Point p;
p.x = x;
p.y = y;
return p; /* safe — the whole struct is copied out */
}
| # | Method | Parameter | Copies data? | Can modify original? | Best for |
|---|---|---|---|---|---|
| 1 | By value | struct S s | Yes — whole struct | No | Small structs, read-only |
| 2 | By pointer | struct S *s | No — address only | Yes | Modifying the caller's record |
| 3 | By const pointer | const struct S *s | No — address only | No (enforced) | Large structs, read-only |
&s explicitly.
Bit fields
A bit field lets a structure member occupy an exact number of bits instead of a whole integer — useful when memory is tight or when modelling flags.
struct Flags {
unsigned int isActive : 1; /* 1 bit → 0 or 1 */
unsigned int isAdmin : 1; /* 1 bit */
unsigned int level : 4; /* 4 bits → 0 to 15 */
};
struct Flags f = { 0 };
f.isActive = 1;
f.level = 12;
printf("size = %zu\n", sizeof(f)); /* often 4, not 12 */
| Rule | Detail |
|---|---|
| Syntax | type name : width; |
| Allowed types | int, unsigned int, signed int, _Bool |
| Value range | n bits unsigned holds 0 .. 2^n − 1 |
| Address-of | Not allowed — &f.level is illegal |
| Arrays of bit fields | Not allowed |
| Unnamed field | unsigned : 3; reserves padding bits |
| Zero-width field | unsigned : 0; forces the next field to a new storage unit |
| Memory layout | Implementation-defined — not portable |
Sample program — structure, pointer, union, and bit field
/* Structures and unions in one program */
#include <stdio.h>
#include <string.h>
struct Student {
char name[50];
int roll;
float marks;
};
union Value {
int i;
float f;
};
struct Flags {
unsigned int passed : 1;
unsigned int grade : 3;
};
void print(const struct Student *s);
int main(void) {
struct Student s = { "Asha", 101, 88.5 };
struct Student *p = &s;
union Value v;
struct Flags f = { 0 };
print(p);
p->marks = 92.0; /* modify through the pointer */
printf("updated marks: %.2f\n", s.marks);
v.i = 10;
printf("union as int: %d\n", v.i);
v.f = 2.5; /* overwrites the same bytes */
printf("union as float: %.2f\n", v.f);
f.passed = 1;
f.grade = 5;
printf("passed=%u grade=%u size=%zu\n",
f.passed, f.grade, sizeof(f));
printf("sizeof struct = %zu, sizeof union = %zu\n",
sizeof(struct Student), sizeof(union Value));
return 0;
}
void print(const struct Student *s) {
printf("%s (%d) : %.2f\n", s->name, s->roll, s->marks);
}
Level 2 Advanced Content
Going deeper into structures and unions
Level 1 covered definitions, comparison, initialization, pointers, dynamic memory, function
passing, and bit fields. Level 2 explains typedef, nested structures,
arrays of structures, padding and alignment, shallow vs deep
copy, tagged unions, self-referential structures, and common mistakes.
typedef — cleaner declarations
/* Without typedef */
struct Point { int x, y; };
struct Point p1; /* must repeat 'struct' */
/* With typedef */
typedef struct {
int x, y;
} Point;
Point p2; /* clean */
/* Both tag and typedef — needed for self-reference */
typedef struct Node {
int data;
struct Node *next; /* typedef name not ready yet here */
} Node;
Nested structures and arrays of structures
struct Date {
int day, month, year;
};
struct Employee {
char name[50];
struct Date doj; /* nested structure */
float salary;
};
struct Employee e = { "Ravi", { 15, 8, 2020 }, 55000 };
printf("%d\n", e.doj.year); /* chain the dots */
/* Array of structures */
struct Student list[3] = {
{ "Asha", 101, 88.5 },
{ "Ravi", 102, 76.0 },
{ "Neha", 103, 91.5 }
};
for (i = 0; i < 3; i++)
printf("%s %d\n", list[i].name, list[i].roll);
Padding, alignment, and sizeof
The compiler inserts padding bytes so each member starts at an address suitable
for its type. That is why sizeof often exceeds the sum of the members.
| Definition | Sum of members | Typical sizeof | Why |
|---|---|---|---|
struct { char c; int i; } | 5 | 8 | 3 padding bytes before i |
struct { int i; char c; } | 5 | 8 | 3 trailing bytes for array alignment |
struct { char a; int i; char b; } | 6 | 12 | Padding around i |
struct { int i; char a, b; } | 6 | 8 | Better ordering — less padding |
Shallow copy vs deep copy
struct Person { char *name; int age; };
struct Person a, b;
a.name = malloc(50);
strcpy(a.name, "Asha");
b = a; /* SHALLOW: both point at the SAME buffer */
strcpy(b.name, "Ravi"); /* a.name also becomes "Ravi" */
/* DEEP copy — allocate separate storage */
b.name = malloc(50);
strcpy(b.name, a.name);
=on structures copies members, including pointer values- Two shallow copies sharing a buffer means a double free risk
- Deep copy means allocating new memory for every pointer member
Tagged unions — the safe way to use a union
A union alone cannot tell you which member is valid. Wrap it in a structure with a tag that records the current type.
enum Kind { KIND_INT, KIND_FLOAT, KIND_CHAR };
struct Variant {
enum Kind kind; /* which member is valid */
union {
int i;
float f;
char c;
} value;
};
void show(const struct Variant *v) {
switch (v->kind) {
case KIND_INT: printf("%d\n", v->value.i); break;
case KIND_FLOAT: printf("%f\n", v->value.f); break;
case KIND_CHAR: printf("%c\n", v->value.c); break;
}
}
Self-referential structures — a linked list node
struct Node {
int data;
struct Node *next;
};
/* Build two linked nodes on the heap */
struct Node *head = malloc(sizeof(*head));
head->data = 10;
head->next = malloc(sizeof(*head));
head->next->data = 20;
head->next->next = NULL; /* end of list */
/* Free every node before exiting */
free(head->next);
free(head);
Common mistakes
| Mistake | Why it hurts | Fix |
|---|---|---|
Missing ; after the closing brace | Confusing cascade of syntax errors | }; ends every struct definition |
Using . on a pointer | Compile error | Use -> or (*p).member |
s.name = "Asha"; | Cannot assign to an array | strcpy(s.name, "Asha"); |
Comparing structs with == | Not allowed in C | Compare member by member |
| Reading the wrong union member | Reinterprets unrelated bytes | Track the active member with a tag |
Expecting the sum of members from sizeof | Ignores padding | Trust sizeof, reorder to shrink |
| Passing a large struct by value | Expensive full copy | Pass const struct T * |
| Shallow copy of a struct owning a pointer | Shared buffer, double free | Deep copy each pointer member |
| Freeing the struct before its inner pointers | Leaks the inner allocations | Free inside-out |
Taking & of a bit field | Illegal — no guaranteed address | Copy into a normal variable first |
Level 3 C Tricky Interview Points
Advanced tricky interview points on structure vs union memory, padding, assignment vs comparison, arrow syntax, self-reference, passing conventions, and bit fields.
Tricky Interview Point-1:
A structure allocates space for every member; a union allocates only the largest
sizeof(struct) is at least the sum of members plus padding. sizeof(union) equals the size of its largest member (rounded for alignment). This single sentence answers most structure-vs-union questions.
Tricky Interview Point-2:
All union members share the same memory — writing one corrupts the others
Only the most recently written member holds a meaningful value. Reading a different member reinterprets the same bytes, which is why unions need a separate "tag" field to track what is currently stored.
Tricky Interview Point-3:
sizeof(struct) is usually larger than the sum of its members — padding
Compilers insert padding so each member sits at a properly aligned address. struct { char c; int i; } is typically 8 bytes, not 5. Reordering members largest-to-smallest often shrinks the struct.
Tricky Interview Point-4:
Structures cannot be compared with == , even if all members match
There is no built-in structure comparison because padding bytes are indeterminate. Compare member by member, or use memcmp only when you know the padding was zeroed.
Tricky Interview Point-5:
Structures CAN be assigned and copied with = , unlike arrays
s2 = s1; performs a member-wise copy. This is a shallow copy — pointer members are duplicated as addresses, so both structures then point at the same data.
Tricky Interview Point-6:
p->member is shorthand for (*p).member — and the parentheses matter
*p.member parses as *(p.member) because . binds tighter than *. That is a compile error for a pointer. Always use -> or write (*p).member explicitly.
Tricky Interview Point-7:
A structure cannot contain itself, but it can contain a pointer to itself
struct Node { struct Node next; }; is impossible (infinite size). struct Node { struct Node *next; }; is fine and is the foundation of linked lists and trees.
Tricky Interview Point-8:
Passing a structure by value copies the whole thing
For large structures this is expensive. Pass const struct T *p instead: the function gets an address (cheap) and const documents that it will not modify the original.
Tricky Interview Point-9:
Designated initializers let you initialize members by name (C99)
struct Point p = { .y = 5, .x = 3 }; is order-independent and self-documenting. Unlisted members are zero-initialized, which is safer than positional lists when a struct grows.
Tricky Interview Point-10:
Only the first member of a union can be initialized without designators
union U u = { 10 }; initializes the first member. To set another, use a designator: union U u = { .f = 3.14 };. Attempting to initialize two members at once is an error.
Tricky Interview Point-11:
typedef removes the need to write struct every time
typedef struct { int x, y; } Point; lets you declare Point p; instead of struct Point p;. The tag name and typedef name are in different namespaces, so both can be the same word.
Tricky Interview Point-12:
Bit fields have no defined memory layout or bit order
Whether bits pack left-to-right or right-to-left, and how they straddle storage units, is implementation-defined. Never use bit fields to parse a hardware register or file format portably.
Tricky Interview Point-13:
You cannot take the address of a bit field
&s.flag is illegal because a bit field may not start at a byte boundary. They also cannot be members of arrays and are restricted to integer types.
Tricky Interview Point-14:
An unnamed bit field of width 0 forces alignment to the next storage unit
unsigned : 0; is a special directive that starts the next field at a fresh boundary. An unnamed field with a nonzero width simply reserves padding bits you cannot access.
Tricky Interview Point-15:
A flexible array member must be last and needs a matching malloc
struct S { int n; int data[]; }; is allocated with malloc(sizeof(struct S) + n * sizeof(int)). It lets one allocation hold both the header and a variable-length payload.
Level 4 Simple Programs
Short programs covering records, arrays of structures, function passing, dynamic allocation, unions, and bit fields.
Program 1 — Student record: read and print
/* Basic structure input and output */
#include <stdio.h>
struct Student {
char name[50];
int roll;
float marks;
};
int main(void) {
struct Student s;
printf("Enter name, roll, marks: ");
scanf("%49s %d %f", s.name, &s.roll, &s.marks);
printf("\nName : %s\n", s.name);
printf("Roll : %d\n", s.roll);
printf("Marks : %.2f\n", s.marks);
return 0;
}
Program 2 — Array of structures: find the topper
/* n records, then the highest marks */
#include <stdio.h>
struct Student {
char name[50];
int roll;
float marks;
};
int main(void) {
struct Student list[50];
int n, i, best = 0;
printf("How many students? ");
scanf("%d", &n);
for (i = 0; i < n; i++) {
printf("Student %d (name roll marks): ", i + 1);
scanf("%49s %d %f",
list[i].name, &list[i].roll, &list[i].marks);
}
for (i = 1; i < n; i++)
if (list[i].marks > list[best].marks)
best = i;
printf("\nTopper: %s (%d) with %.2f\n",
list[best].name, list[best].roll, list[best].marks);
return 0;
}
Program 3 — Passing a structure by value and by pointer
/* Only the pointer version changes the original */
#include <stdio.h>
typedef struct {
int roll;
float marks;
} Student;
void byValue(Student s);
void byPointer(Student *s);
void show(const Student *s);
int main(void) {
Student s = { 101, 80.0 };
byValue(s);
printf("after byValue: ");
show(&s);
byPointer(&s);
printf("after byPointer: ");
show(&s);
return 0;
}
void byValue(Student s) {
s.marks += 10; /* local copy only */
}
void byPointer(Student *s) {
s->marks += 10; /* the real record */
}
void show(const Student *s) {
printf("roll=%d marks=%.2f\n", s->roll, s->marks);
}
Program 4 — Dynamic array of structures
/* calloc an array of records, then free it */
#include <stdio.h>
#include <stdlib.h>
typedef struct {
char name[50];
int roll;
} Student;
int main(void) {
Student *list;
int n, i;
printf("How many students? ");
scanf("%d", &n);
list = calloc(n, sizeof(*list));
if (list == NULL) {
printf("Out of memory\n");
return 1;
}
for (i = 0; i < n; i++) {
printf("Name and roll: ");
scanf("%49s %d", list[i].name, &list[i].roll);
}
printf("\nRecords:\n");
for (i = 0; i < n; i++)
printf("%-10s %d\n", list[i].name, list[i].roll);
free(list);
list = NULL;
return 0;
}
Program 5 — Union behaviour and size comparison
/* Shared memory in action */
#include <stdio.h>
struct S { int i; float f; char c; };
union U { int i; float f; char c; };
int main(void) {
struct S s;
union U u;
s.i = 10;
s.f = 2.5;
s.c = 'A';
printf("struct: i=%d f=%.1f c=%c\n", s.i, s.f, s.c);
u.i = 10;
printf("union after u.i = 10 : %d\n", u.i);
u.f = 2.5;
printf("union after u.f = 2.5: i is now %d\n", u.i);
printf("\nsizeof struct = %zu\n", sizeof(struct S));
printf("sizeof union = %zu\n", sizeof(union U));
return 0;
}
Program 6 — Bit fields for flags
/* Pack several small values into a few bytes */
#include <stdio.h>
struct Packed {
unsigned int isActive : 1;
unsigned int isAdmin : 1;
unsigned int level : 4;
unsigned int zone : 6;
};
struct Plain {
unsigned int isActive, isAdmin, level, zone;
};
int main(void) {
struct Packed p = { 0 };
p.isActive = 1;
p.level = 12; /* fits in 4 bits (max 15) */
p.zone = 40; /* fits in 6 bits (max 63) */
printf("active=%u level=%u zone=%u\n",
p.isActive, p.level, p.zone);
printf("packed size = %zu\n", sizeof(struct Packed));
printf("plain size = %zu\n", sizeof(struct Plain));
return 0;
}
Level 5 Problem Solving Tricks
How to master structures and unions while solving problems
Most structure bugs come from mixing up . and ->, assigning to
array members, copying structures that own pointers, and reading the wrong union member.
These habits keep record-based code clean.
Use typedef so declarations read like the domain
typedef struct { char name[50]; int roll; float marks; } Student; then write Student s; The code starts describing the problem instead of the language.
Pass const pointers for read-only structures
void print(const Student *s) avoids a full copy and tells both the compiler and the reader that nothing will be modified. Use a plain pointer only when you intend to change the struct.
Always use -> when you have a pointer
If the variable is a pointer, reach for ->. If it is a struct value, use the dot. Deciding this from the declaration, not by trial and error, eliminates a whole class of compile errors.
Give unions a tag field to know what is stored
struct { int kind; union { int i; float f; } value; } is the standard "tagged union" pattern. Without the tag you have no safe way to know which member is currently valid.
Use designated initializers for structs with many members
Point p = { .x = 3, .y = 5 }; survives reordering and additions to the struct, while a positional list silently assigns to the wrong members when the definition changes.
Order members largest to smallest to reduce padding
Placing double, then int, then char usually produces a smaller structure than the reverse. Check with sizeof if memory matters, especially for arrays of thousands of structs.
For arrays of structs, allocate with calloc
Student *list = calloc(n, sizeof(*list)); gives you n zeroed records in one call, so numeric fields start at 0 and strings start empty without an initialization loop.
Free nested pointers before freeing the structure
If a struct owns a malloc'd name field, free(s->name) first, then free(s). Freeing the struct first loses the inner address and leaks it.
Quick success checklist
- Before run: semicolon after the definition?
strcpyfor strings? dot vs arrow correct? - Debug order: print each member → check
sizeof→ verify which union member is active - Memory habit: pass large structs by
constpointer → deep copy pointer members → free inside-out
Level 6 Practice Programs List
Programming questions only — focused on structures and unions: records, initialization, pointers, dynamic memory, function passing, unions, and bit fields.
- 1. Write a C program that defines a structure Student with name, roll number, and marks; reads one student; and prints the details.
- 2. Write a C program that reads n students into an array of structures and prints the one with the highest marks.
- 3. Write a C program that defines a structure and initializes it three ways: positional list, designated initializers, and member assignment.
- 4. Write a C program that uses a pointer to a structure and accesses all members with the -> operator.
- 5. Write a C program that passes a structure to a function by value and by pointer, and shows which one can modify the original.
- 6. Write a C program that returns a structure from a function (for example, a function that builds a Point from two integers).
- 7. Write a C program that allocates a single structure with malloc, fills it, prints it, and frees it.
- 8. Write a C program that allocates an array of n structures dynamically with calloc, reads them, and frees the memory.
- 9. Write a C program that defines a union with an int, a float, and a char array, and prints sizeof for the union and an equivalent structure.
- 10. Write a C program that demonstrates that writing one union member changes the value read from another.
- 11. Write a C program that uses a bit field structure to store several 1-bit flags and prints sizeof the structure.
- 12. Write a C program that defines a nested structure (for example, Employee containing a Date of joining) and prints all fields.