C
Build a stack in C from scratch with static arrays and dynamic memory, and learn the manual memory management that every C program requires.
Static Array Stack
The simplest C stack uses a fixed-size array plus a top index. The struct holds the array and the current top position, and push and pop manipulate that index.
#include <stdio.h>
#include <stdbool.h>
#define MAX 100
typedef struct {
int data[MAX];
int top;
} Stack;
void init(Stack *s) { s->top = -1; }
bool push(Stack *s, int value) {
if (s->top >= MAX - 1) return false; // overflow
s->data[++s->top] = value;
return true;
}
bool pop(Stack *s, int *value) {
if (s->top < 0) return false; // underflow
*value = s->data[s->top--];
return true;
}
bool peek(const Stack *s, int *value) {
if (s->top < 0) return false;
*value = s->data[s->top];
return true;
}
bool empty(const Stack *s) { return s->top < 0; }
The top starts at -1, meaning an empty stack. Pushing increments it, popping decrements it. C does no bounds checking, so you must verify overflow and underflow yourself before every push and pop.
Dynamic Array Stack
When you do not know the maximum size in advance, allocate the array on the heap and grow it with realloc. The struct stores a pointer, the current top, and the current capacity.
#include <stdlib.h>
typedef struct {
int *data;
int top;
int capacity;
} DynStack;
void init_dyn(DynStack *s, int cap) {
s->data = malloc(cap * sizeof(int));
s->top = -1;
s->capacity = cap;
}
bool push_dyn(DynStack *s, int value) {
if (s->top + 1 >= s->capacity) { // grow
int new_cap = s->capacity * 2;
int *grown = realloc(s->data, new_cap * sizeof(int));
if (!grown) return false;
s->data = grown;
s->capacity = new_cap;
}
s->data[++s->top] = value;
return true;
}
void destroy_dyn(DynStack *s) {
free(s->data);
s->data = NULL;
s->top = -1;
s->capacity = 0;
}
Doubling the capacity when full gives O(1) amortized push time. Remember that every malloc must be matched by a free, and keep a destroy function so callers can release the memory. Array-backed stack design
Linked List Stack in C
To avoid resizing altogether, build the stack from nodes. Each node stores a value and a pointer to the next node, and the top of the stack is the head of the list.
typedef struct Node {
int value;
struct Node *next;
} Node;
typedef struct {
Node *top;
} ListStack;
void init_list(ListStack *s) { s->top = NULL; }
bool push_list(ListStack *s, int value) {
Node *node = malloc(sizeof(Node));
if (!node) return false;
node->value = value;
node->next = s->top;
s->top = node;
return true;
}
bool pop_list(ListStack *s, int *value) {
if (!s->top) return false;
Node *old = s->top;
*value = old->value;
s->top = old->next;
free(old);
return true;
}
Every push and pop is O(1) with no reallocation, but each node is a separate allocation with poorer cache locality. Full linked list guide
Memory Management
C gives you full control of memory, which is powerful and also the source of most bugs. Follow these rules for a stack:
- Every
mallocmust eventually have a matchingfree. - Check the return value of
mallocandrealloc; they return NULL when they fail. - Free the stack's array in a destroy function before the program ends.
- For linked stacks, free every node when destroying, not just the head.
- Set freed pointers to NULL to avoid use after free.
void destroy_list(ListStack *s) {
Node *cur = s->top;
while (cur) {
Node *next = cur->next;
free(cur);
cur = next;
}
s->top = NULL;
}
Complete C Stack Implementation
Here is a full, compilable dynamic array stack program:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct {
int *data;
int top;
int capacity;
} Stack;
void init(Stack *s, int cap) {
s->data = malloc(cap * sizeof(int));
s->top = -1;
s->capacity = cap;
}
bool push(Stack *s, int value) {
if (s->top + 1 >= s->capacity) {
s->capacity *= 2;
int *bigger = realloc(s->data, s->capacity * sizeof(int));
if (!bigger) return false;
s->data = bigger;
}
s->data[++s->top] = value;
return true;
}
bool pop(Stack *s, int *value) {
if (s->top < 0) return false;
*value = s->data[s->top--];
return true;
}
bool peek(const Stack *s, int *value) {
if (s->top < 0) return false;
*value = s->data[s->top];
return true;
}
void destroy(Stack *s) {
free(s->data);
s->data = NULL;
s->top = -1;
s->capacity = 0;
}
int main(void) {
Stack s;
init(&s, 4);
push(&s, 10);
push(&s, 20);
push(&s, 30);
int v;
while (pop(&s, &v)) {
printf("%d\n", v); // 30 20 10
}
destroy(&s);
return 0;
}
Explore more of the stack data structure
Stack Overflow and Underflow Handling
In C, overflow and underflow are not exceptions; they are silent bugs unless you handle them. If you push past the end of a fixed array, you overwrite adjacent memory. If you pop below the bottom, you read memory that does not belong to your stack.
The safe pattern is to have every push and pop return a boolean that reports success, and to check it in the calling code. This turns a memory corruption bug into a handleable failure, which is what production C code does.
Whether you check top + 1 >= capacity before push or let realloc grow the array, the goal is the same: never read or write outside the owned region. See the main visualizer for stack code in five languages.
Key Takeaway
C gives you fine-grained control over a stack: a fixed array for known sizes, dynamic memory with realloc for growth, or a linked list when you never want to reallocate. Whatever you choose, manual memory management means you must free what you allocate and check overflow and underflow yourself.
Frequently Asked Questions
Should I use a fixed array or dynamic memory for a C stack?
Use a fixed array when the maximum stack size is known in advance and stays small, because it is simple, fast, and needs no allocation. Use dynamic memory with realloc when the stack must grow unpredictably, because that lets it scale without wasting memory in advance.
What is stack overflow and underflow in a C stack?
Stack overflow happens when you push onto a full stack: the top index reaches the capacity. Stack underflow happens when you pop from an empty stack: the top index is below zero. Both must be checked explicitly in C, because C does not throw exceptions or bounds-check arrays.
How do I avoid memory leaks in a dynamic C stack?
Free every block you malloc. For a dynamic array stack, call free() on the array in a destroy function. For a linked list stack, pop nodes and free each one until the list is empty. Use a tool like valgrind or AddressSanitizer to verify there are no leaks or invalid frees.