Stack Data Structure
Time to combine structs and pointers into a real, reusable data structure: a stack (last-in, first-out), backed by the heap so it can grow.
#include <stdlib.h>
typedef struct IntStack {
int *items;
int count;
int capacity;
} IntStack;
struct IntStack *stackCreate(int capacity) {
IntStack *s = malloc(sizeof(IntStack));
s->items = malloc(capacity * sizeof(int));
s->count = 0;
s->capacity = capacity;
return s;
}
void stackPush(IntStack *s, int value) {
if (s->count == s->capacity) return;
s->items[s->count] = value;
s->count++;
}
int stackPop(IntStack *s) {
s->count--;
return s->items[s->count];
}
void stackFree(IntStack *s) {
free(s->items);
free(s);
}
The arrow operator
s->items is shorthand for (*s).items, "dereference the pointer s, then access the items member". Since working with pointers-to-structs is so common in C, -> exists purely to avoid writing (*s). everywhere.
Where does each piece live?
This is the key insight for this lesson: stackCreate allocates two separate heap blocks, the IntStack struct itself, and the items array it points to. They're independent allocations linked only by the pointer stored inside the struct, which is exactly why stackFree must free both, and in the right order, freeing s before s->items would leak the array's memory forever since you'd lose the only pointer to it.
#include <stdio.h>
#include <stdlib.h>
typedef struct IntStack {
int *items;
int count;
int capacity;
} IntStack;
struct IntStack *stackCreate(int capacity) {
IntStack *s = malloc(sizeof(IntStack));
s->items = malloc(capacity * sizeof(int));
s->count = 0;
s->capacity = capacity;
return s;
}
void stackPush(IntStack *s, int value) {
if (s->count == s->capacity) return;
s->items[s->count] = value;
s->count++;
}
int stackPop(IntStack *s) {
s->count--;
return s->items[s->count];
}
void stackFree(IntStack *s) {
free(s->items);
free(s);
}
int main(void) {
IntStack *s = stackCreate(10);
stackPush(s, 1);
stackPush(s, 2);
printf("%d\n", stackPop(s));
stackFree(s);
return 0;
}