Why pointers to structs? When you pass a struct to a function by value, C makes a complete copy of every field — expensive for large structs. Passing a pointer to the struct instead sends only 8 bytes (the address) regardless of struct size. The function then reaches through the pointer to access the real fields with the arrow operator
➤
➤ To allow modification: pass
->. Two rules to remember:
➤
p->field = (*p).field — both read the field through a pointer. Arrow is preferred.➤ To allow modification: pass
Student *p. To prevent modification: pass const Student *p.
| Syntax | What it means | Can modify struct? |
|---|---|---|
| display(Student s) | Pass by value — full copy made | No — copy only |
| display(Student *p) | Pass pointer — no copy | Yes — original accessible |
| display(const Student *p) | Pass pointer, read-only | No — compiler enforces |
| Student* build(...) | Return a pointer to struct | Caller owns the struct |
| Student build(...) | Return struct by value | Caller gets a copy |
1
🔒 Read a Struct via const Pointer — Display Function
const Student *p — access fields with arrow, compiler blocks any modification
const ptr
Passing
const Student *p tells the compiler: "I promise this function will not modify the struct." You can still read every field through p->field, but any attempt to write — like p->marks = 0 — causes a compile error. This is the correct signature for any display or print function that only needs to read. It documents intent and prevents accidental writes.
pass by pointer — no copy, arrow operator accesses original fields
- 1Define
Studentstruct with name, roll, marks, and grade fields. - 2Write
void display(const Student *p)— theconsttells the compiler this function only reads. - 3Access each field with the arrow operator:
p->name,p->marks, etc. - 4Call with
display(&s)— pass the address, not the struct itself. No copy is made.
#include <stdio.h> #include <string.h> typedef struct { char name[25]; int roll; float marks; char grade; } Student; /* const ptr — read-only access, no copy made */ void display(const Student *p) { printf("------ Student Card ------\n"); printf("Name : %s\n", p->name); /* p->field = (*p).field */ printf("Roll : %d\n", p->roll); printf("Marks : %.1f\n", p->marks); printf("Grade : %c\n", p->grade); printf("--------------------------\n"); /* p->marks = 0; ← compile ERROR: const disallows this */ } /* Assign grade based on marks — also read-only param */ char getGrade(const Student *p) { if (p->marks >= 90) return 'A'; else if (p->marks >= 75) return 'B'; else if (p->marks >= 55) return 'C'; else return 'F'; } int main() { Student s1 = {"Ananta", 101, 88.5f, '\0'}; Student s2 = {"Priya", 102, 95.0f, '\0'}; Student s3 = {"Rahul", 103, 52.0f, '\0'}; /* Assign grades — pointer to allow function to read marks */ s1.grade = getGrade(&s1); s2.grade = getGrade(&s2); s3.grade = getGrade(&s3); /* Display — pass address, no copy */ display(&s1); display(&s2); display(&s3); printf("\nsizeof(Student) = %zu bytes\n", sizeof(Student)); printf("Passing pointer = 8 bytes (address only)\n"); return 0; }
------ Student Card ------ Name : Ananta Roll : 101 Marks : 88.5 Grade : B -------------------------- ------ Student Card ------ Name : Priya Roll : 102 Marks : 95.0 Grade : A -------------------------- ------ Student Card ------ Name : Rahul Roll : 103 Marks : 52.0 Grade : F -------------------------- sizeof(Student) = 32 bytes Passing pointer = 8 bytes (address only)
const Student *p vs Student *const p — these are different. const Student *p means the struct the pointer points to is read-only (the pointer itself can be changed). Student *const p means the pointer is fixed but the struct can be changed. For display functions, always use const Student *p.example 2
2
✏️ Modify a Struct In-Place via Pointer
Student *p — function writes directly to the original struct's fields
Modify via ptr
Drop the
const and the function can write directly into the original struct through the pointer. The change is immediately visible to the caller — there is no copy. Three focused functions each accept a Student*: applyBonus adds marks, promote updates grade, and resetStudent zeros every field. Every change is permanent in the caller's variable.
- 1Declare function with
Student *p(no const) to allow writes. - 2Use
p->field = newValueto write directly into the original struct. - 3Changes survive after the function returns — the caller sees them immediately.
- 4Use
strcpy(p->name, "new")to modify string fields — cannot assignp->name = "new"directly.
#include <stdio.h> #include <string.h> typedef struct { char name[25]; int id; double salary; int level; /* 1=Junior 2=Mid 3=Senior */ } Employee; /* Adds bonus — modifies salary through pointer */ void applyBonus(Employee *p, double pct) { p->salary += p->salary * (pct / 100.0); } /* Promote — increments level, adjusts salary */ void promote(Employee *p) { if (p->level < 3) { p->level++; p->salary *= 1.20; /* 20% raise on promotion */ printf(" %s promoted to level %d\n", p->name, p->level); } else { printf(" %s already at max level\n", p->name); } } /* Zero out — reset the entire struct */ void resetEmployee(Employee *p) { strcpy(p->name, "(vacant)"); p->id = 0; p->salary = 0.0; p->level = 0; } void print(const Employee *p) { const char *lvl[] = {"-", "Junior", "Mid", "Senior"}; printf(" %-12s ID:%-4d Rs%8.2f %s\n", p->name, p->id, p->salary, lvl[p->level]); } int main() { Employee e = {"Ananta", 1001, 40000.00, 1}; printf("Original:\n"); print(&e); applyBonus(&e, 10); /* 10% bonus */ printf("\nAfter 10%% bonus:\n"); print(&e); promote(&e); printf("After promotion:\n"); print(&e); promote(&e); promote(&e); /* already at max on 3rd call */ printf("After 2 more promotions:\n"); print(&e); resetEmployee(&e); printf("\nAfter reset:\n"); print(&e); return 0; }
Original: Ananta ID:1001 Rs 40000.00 Junior After 10% bonus: Ananta ID:1001 Rs 44000.00 Junior Ananta promoted to level 2 After promotion: Ananta ID:1001 Rs 52800.00 Mid Ananta promoted to level 3 Ananta already at max level After 2 more promotions: Ananta ID:1001 Rs 76032.00 Senior After reset: (vacant) ID:0 Rs 0.00 -
The pointer is the key. Without
&, applyBonus(e, 10) would pass a copy — the original salary stays unchanged. With &, applyBonus(&e, 10) gives the function the actual address — every change sticks in the original.example 3
3
↩ Function Returns a Struct — Factory Pattern
Build a struct inside a function, return it by value — caller owns the result
Return struct
A function can return an entire struct by value. The function builds the struct internally, then returns it — the struct is copied to the caller at the return statement. This is called the factory pattern — the function is responsible for creating a properly-initialised struct. Combine it with a pointer parameter to also fill a struct the caller already owns (the "output pointer" pattern).
- 1Declare return type as
Rectangle(the struct type):Rectangle makeRect(float w, float h). - 2Inside the function, declare a local struct, fill all its fields, then
return r. - 3In the caller:
Rectangle r = makeRect(10, 5);— the returned struct is assigned directly. - 4For the "output pointer" pattern:
void fillRect(Rectangle *out, float w, float h)— writes directly into caller's variable via pointer.
#include <stdio.h> #include <math.h> typedef struct { float width; float height; float area; float diagonal; } Rectangle; /* Factory — returns a fully computed Rectangle */ Rectangle makeRect(float w, float h) { Rectangle r; r.width = w; r.height = h; r.area = w * h; r.diagonal = sqrtf(w*w + h*h); return r; /* struct copied to caller */ } /* Output-pointer pattern — fills caller's variable */ void fillRect(Rectangle *out, float w, float h) { out->width = w; out->height = h; out->area = w * h; out->diagonal = sqrtf(w*w + h*h); /* no return needed — wrote directly via pointer */ } void printRect(const char *label, const Rectangle *r) { printf("%-12s w=%-5.1f h=%-5.1f area=%-8.2f diag=%.2f\n", label, r->width, r->height, r->area, r->diagonal); } /* Compare two rectangles — both passed as const ptrs */ void compare(const Rectangle *a, const Rectangle *b) { printf("\nBigger area : "); if (a->area > b->area) printf("first (%.2f)\n", a->area); else if (b->area > a->area) printf("second (%.2f)\n", b->area); else printf("equal\n"); } int main() { /* Pattern 1 — return by value */ Rectangle r1 = makeRect(10.0f, 5.0f); Rectangle r2 = makeRect(7.0f, 7.0f); /* Pattern 2 — output pointer */ Rectangle r3; fillRect(&r3, 4.0f, 9.0f); printf("%-12s %5s %5s %8s %s\n", "Shape", "W", "H", "Area", "Diagonal"); printf("--------------------------------------------------\n"); printRect("Rectangle 1", &r1); printRect("Rectangle 2", &r2); printRect("Rectangle 3", &r3); compare(&r1, &r2); return 0; }
Shape W H Area Diagonal -------------------------------------------------- Rectangle 1 w=10.0 h=5.0 area=50.00 diag=11.18 Rectangle 2 w=7.0 h=7.0 area=49.00 diag=9.90 Rectangle 3 w=4.0 h=9.0 area=36.00 diag=9.85 Bigger area : first (50.00)
Return by value vs output pointer — when to use which: Return by value is cleaner and readable — use it when the function creates one result. The output pointer pattern is used when: (1) the function needs to fill multiple structs, (2) you want to avoid the copy cost on very large structs, or (3) you need to signal success/failure via the return value.
example 4
4
📊 Array of Structs Passed to Functions
arr[] decays to pointer — process, search, and find max across all records
Array + Func
When an array of structs is passed to a function, it decays to a pointer to its first element — just like a plain int array. The function signature
Product arr[], int n is identical to Product *arr, int n. Inside, you index with arr[i].field or use pointer arithmetic (arr+i)->field. Three focused functions show the three most common patterns: print all, compute total, and find the most expensive item.
- 1Pass the array and its size:
printAll(products, 5). The array decays to a pointer automatically. - 2Use
arr[i].priceor(arr+i)->priceinside the function — both are equivalent. - 3To return a pointer to a specific element:
return &arr[i]. This points into the original array — no copy. - 4Pass
const Product *arrfor read-only functions. Dropconstonly if the function needs to modify elements.
#include <stdio.h> #include <string.h> typedef struct { char name[20]; float price; int qty; } Product; /* Print all — const: read-only */ void printAll(const Product *arr, int n) { printf("%-16s %8s %5s %12s\n", "Product", "Price", "Qty", "Value"); printf("-------------------------------------------\n"); for (int i = 0; i < n; i++) { printf("%-16s %8.2f %5d %12.2f\n", arr[i].name, arr[i].price, arr[i].qty, arr[i].price * arr[i].qty); } } /* Total stock value */ float totalValue(const Product *arr, int n) { float total = 0; for (int i = 0; i < n; i++) total += arr[i].price * arr[i].qty; return total; } /* Returns POINTER to most expensive product in the array */ const Product* mostExpensive(const Product *arr, int n) { const Product *best = &arr[0]; for (int i = 1; i < n; i++) if (arr[i].price > best->price) best = &arr[i]; return best; /* pointer into original array — no copy */ } /* Apply discount — modifies price through pointer */ void applyDiscount(Product *arr, int n, float pct) { for (int i = 0; i < n; i++) arr[i].price -= arr[i].price * (pct / 100.0f); } int main() { Product shop[] = { {"Rice 1kg", 60.0f, 200}, {"Cooking Oil", 180.0f, 80}, {"Sugar 1kg", 45.0f, 150}, {"Tea Powder", 220.0f, 60}, {"Salt", 20.0f, 300} }; int n = 5; printAll(shop, n); printf("-------------------------------------------\n"); printf("Total stock value : Rs %.2f\n", totalValue(shop, n)); const Product *top = mostExpensive(shop, n); printf("Most expensive : %s (Rs %.2f)\n", top->name, top->price); printf("\nApplying 10%% discount...\n"); applyDiscount(shop, n, 10); printAll(shop, n); printf("-------------------------------------------\n"); printf("Total after discount : Rs %.2f\n", totalValue(shop, n)); return 0; }
Product Price Qty Value ------------------------------------------- Rice 1kg 60.00 200 12000.00 Cooking Oil 180.00 80 14400.00 Sugar 1kg 45.00 150 6750.00 Tea Powder 220.00 60 13200.00 Salt 20.00 300 6000.00 ------------------------------------------- Total stock value : Rs 52350.00 Most expensive : Tea Powder (Rs 220.00) Applying 10% discount... Product Price Qty Value ------------------------------------------- Rice 1kg 54.00 200 10800.00 Cooking Oil 162.00 80 12960.00 Sugar 1kg 40.50 150 6075.00 Tea Powder 198.00 60 11880.00 Salt 18.00 300 5400.00 ------------------------------------------- Total after discount : Rs 47115.00
const Product *best = &arr[0] — best is a pointer into the original shop[] array. When you do best = &arr[i], you're updating the pointer to point at a different element — not copying the struct. The caller accesses top->name and gets the real data, zero copies made.example 5
5
🎓 Complete Mini App — Student Management System
All patterns together — create, display, update, swap, search, topper
Complete App
Every pointer-and-struct pattern from examples 1–4 combined into one cohesive program. Six functions each demonstrate a different technique:
makeStudent returns a struct, displayAll uses a const pointer walk, updateMarks modifies in-place, swapStudents swaps two structs via pointers, findTopper returns a pointer to the best element, and applyGrades modifies every element of the array.
| Function | Signature pattern | Technique |
|---|---|---|
| makeStudent() | Student makeStudent(...) | Return struct by value — factory |
| displayAll() | void f(const Student*, int) | const pointer — read-only array walk |
| updateMarks() | void f(Student*, float) | Non-const pointer — modify one field |
| swapStudents() | void f(Student*, Student*) | Two pointers — classic temp-swap |
| findTopper() | Student* f(Student*, int) | Return pointer into array — no copy |
| applyGrades() | void f(Student*, int) | Modify every element via pointer walk |
#include <stdio.h> #include <string.h> typedef struct { char name[20]; int roll; float marks; char grade; } Student; /* 1. Factory — return struct by value */ Student makeStudent(const char *nm, int roll, float marks) { Student s; strncpy(s.name, nm, 19); s.name[19] = '\0'; s.roll = roll; s.marks = marks; s.grade = '-'; /* assigned later by applyGrades */ return s; } /* 2. Display all — const pointer walk */ void displayAll(const Student *arr, int n) { printf(" %-12s %5s %7s %6s\n", "Name", "Roll", "Marks", "Grade"); printf(" -----------------------------------\n"); for (int i = 0; i < n; i++) printf(" %-12s %5d %7.1f %6c\n", arr[i].name, arr[i].roll, arr[i].marks, arr[i].grade); } /* 3. Update marks in-place via pointer */ void updateMarks(Student *p, float newMarks) { printf(" Updating %s: %.1f -> %.1f\n", p->name, p->marks, newMarks); p->marks = newMarks; } /* 4. Swap two students using pointers */ void swapStudents(Student *a, Student *b) { Student tmp = *a; /* copy all fields of *a */ *a = *b; /* copy all fields of *b into *a */ *b = tmp; /* copy saved fields into *b */ } /* 5. Find topper — returns pointer into array */ Student* findTopper(Student *arr, int n) { Student *best = &arr[0]; for (int i = 1; i < n; i++) if (arr[i].marks > best->marks) best = &arr[i]; return best; } /* 6. Apply grades to entire array */ void applyGrades(Student *arr, int n) { for (int i = 0; i < n; i++) { float m = arr[i].marks; arr[i].grade = (m >= 90) ? 'A' : (m >= 75) ? 'B' : (m >= 55) ? 'C' : 'F'; } } int main() { Student batch[5]; /* Pattern 1 — factory */ batch[0] = makeStudent("Ananta", 101, 88.5f); batch[1] = makeStudent("Priya", 102, 73.0f); batch[2] = makeStudent("Rahul", 103, 91.5f); batch[3] = makeStudent("Sneha", 104, 58.0f); batch[4] = makeStudent("Vikram", 105, 45.0f); /* Pattern 6 — grade entire array */ applyGrades(batch, 5); printf("=== Initial Records ===\n"); displayAll(batch, 5); /* Pattern 2 */ /* Pattern 3 — update one student */ printf("\n=== Update Priya's marks to 96.0 ===\n"); updateMarks(&batch[1], 96.0f); applyGrades(batch, 5); displayAll(batch, 5); /* Pattern 4 — swap first and last */ printf("\n=== Swap Ananta and Vikram ===\n"); swapStudents(&batch[0], &batch[4]); displayAll(batch, 5); /* Pattern 5 — find topper via returned pointer */ Student *top = findTopper(batch, 5); printf("\n=== Topper ===\n"); printf(" %s (Roll %d) — %.1f marks Grade: %c\n", top->name, top->roll, top->marks, top->grade); /* Modifying through returned pointer changes real array */ top->marks += 2.0f; /* bonus marks for topper */ printf(" After 2 bonus marks: %.1f\n", top->marks); return 0; }
=== Initial Records === Name Roll Marks Grade ----------------------------------- Ananta 101 88.5 B Priya 102 73.0 C Rahul 103 91.5 A Sneha 104 58.0 C Vikram 105 45.0 F === Update Priya's marks to 96.0 === Updating Priya: 73.0 -> 96.0 Name Roll Marks Grade ----------------------------------- Ananta 101 88.5 B Priya 102 96.0 A Rahul 103 91.5 A Sneha 104 58.0 C Vikram 105 45.0 F === Swap Ananta and Vikram === Name Roll Marks Grade ----------------------------------- Vikram 105 45.0 F Priya 102 96.0 A Rahul 103 91.5 A Sneha 104 58.0 C Ananta 101 88.5 B === Topper === Priya (Roll 102) — 96.0 marks Grade: A After 2 bonus marks: 98.0
All six patterns at a glance:
makeStudent returns a struct · displayAll reads with const* · updateMarks writes with * · swapStudents uses Student tmp = *a to copy all fields · findTopper returns &arr[i] · modifying through the returned pointer changes the real array. These six cover every struct-pointer-function interaction you will encounter in C.checklist
- Ex 1 — const ptr:
const Student *p= read-only access. Usep->field(arrow operator). Compiler blocks any write. Always use for display/print functions. 8 bytes sent, not sizeof(struct). - Ex 2 — modify in-place: Drop
const→ function can writep->salary = newVal. Changes survive the function return. Usestrcpy(p->name, "x")for string fields — cannot assign directly. - Ex 3 — return struct:
Rectangle makeRect(w, h)returns by value — struct copied to caller. Output pointer pattern:void fillRect(Rectangle *out, ...)writes directly via pointer, no return needed. - Ex 4 — array of structs: Array decays to pointer on passing.
arr[i].fieldand(arr+i)->fieldare identical. Return&arr[i]for a pointer into the original — no copy. Useconstfor read-only functions. - Ex 5 — complete app: Factory (return struct) + const ptr walk + modify in-place + swap with temp (
Student tmp = *a; *a = *b; *b = tmp) + return pointer + modify via returned pointer. All six patterns in one program.