Putting it together: a roster
A capstone that uses the whole course at once: a struct type, memory asked for at run time, a function that reads it, and a final report.
The shape of the program
This program keeps a small roster of students. Each student is a struct with a name and a score. The list is a block of memory from malloc, sized to hold the students you name.
A function walks the list and adds up the scores. The main function owns the memory: it allocates the block, fills it, calls the function, and frees the block at the end.
Where the chapters land
The struct type and its fields come from the structs chapter. The block of students from malloc is the dynamic memory chapter, and the pointer handed to the function is the pointers chapter.
The total function is the functions chapter. It walks the list with a loop from the arrays chapter. Nothing here is new; the capstone is the earlier pieces working in one file.
What a real one adds
Real programs grow the list when it fills, check every return from malloc, and free the memory on every path out, including the error ones. They also copy names carefully, since a struct field is only so much room for text.
Those are additions to the same shape you are about to write, not replacements for it. Once the struct and the block are in place, growth is a bigger request, a copy, and a free.
A roster has a count and a capacity
The list you allocate has a capacity — how many students fit — and a count — how many are filled. They are different numbers, and mixing them is how a program writes past its block. Keep both, and grow only when the count reaches the capacity.
Growth means asking for a bigger block, copying the old values, and releasing the old one. realloc does all three, but it may move the block, so pointers into the old list may become invalid after the call.
Ownership across functions
Decide who owns the block before you write the functions. Here main allocates and frees, while total only borrows. A function that frees memory it did not allocate will surprise its caller, especially if the caller still needs the list.
The same rule covers names inside a struct: a fixed char name[12] is room you own, and copying into it needs a bound. snprintf(student.name, sizeof student.name, "%s", source) stops at the edge of the buffer; strcpy does not.
A worked growth, step by step
Start with room for two and two filled. A third student arrives: the count equals the capacity, so ask for room for four. realloc may return the same address or a new one; either way it preserves the old two entries, and if it moved, the old block has already been released. Then write the third entry and raise the count to three.
The capacity doubles, so the next two arrivals cost nothing. Growing by one on every insert would copy the whole list each time, which turns a linear job into a quadratic one. Doubling trades a little wasted room for a bound on the copying, and it is the same trade a dynamic array makes in every language.
Testing the edges
A roster program has four cases worth running before you trust it: zero students, one student, a count exactly at capacity, and a count one past it. The first checks that the report survives an empty list, and the last forces a growth. A program that only ever sees three students never exercises the branch that moves the block.
Print the count and the capacity after each change, and free the block on every path. The report is the easy part; the value of the capstone is that the ownership stays visible from the first malloc to the last free. If you can point at the line that releases each block, the program is sound.
Three capstone mistakes undo the earlier chapters: writing list = realloc(list, ...), which loses the block if the resize fails; copying a name with strcpy into a field that is too small; and freeing the roster inside a function that only borrowed it. Use a temporary for realloc, bound every copy, and free only where you allocated.
Try these next
- Add a
capacityvariable and grow the roster only whencount == capacity. - Write a function that prints one student through a
Student *, then call it for every entry. - Give the name field more room and use
snprintfwithsizeofto copy a longer name safely.
The pattern in miniature
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int score;
} Entry;
int main(void) {
Entry *list = malloc(2 * sizeof(Entry));
list[0].score = 5;
list[1].score = 7;
printf("Total %d\n", list[0].score + list[1].score);
free(list);
return 0;
}A roster with memory and a function
Run it, then add a third student to the block and include it in the total. Keep the request size and the list in step, and free the block at the end.
Find the best student
Run it, then add a fourth student and check that the best pointer still names the highest score. Change one score to a tie and decide which name should win.
Trace the ownership as you read: main allocates the block, the function borrows the pointer, and main frees at the end. Who frees what is the heart of clean C.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
Who frees the block of students in the capstone?
