Introduction
In this blog post, we will explore the implementation of a stack data structure in C using dynamic memory allocation. A stack follows the Last-In, First-Out (LIFO) principle, meaning the most recently added element is the first to be removed. By utilizing dynamic memory allocation, we can create a flexible-size stack that adjusts its capacity as needed during program execution.
Understanding the Stack
A stack is a fundamental abstract data type with two main operations: push and pop. The push operation adds an element to the top of the stack, while the pop operation removes the top element from the stack. Additionally, we can perform supporting operations such as peek (to view the top element without removal), isEmpty (to check if the stack is empty), and isFull (to check if the stack is at its maximum capacity).
Declaring the Stack Structure
To implement a dynamic memory allocation stack, we define a structure to hold the key components:
typedef struct {
int *data;
int top;
int capacity;
} Stack;
data
: A pointer to an integer array, which will store the stack elements.top
: An integer representing the index of the top element in the stack.capacity
: An integer indicating the maximum number of elements the stack can hold.
Initializing the Stack
Prior to using the stack, it must be initialized with a given capacity. The initializeStack
function will create the required memory for the stack:
void initializeStack(Stack *stack, int capacity) {
stack->data = (int *)malloc(capacity * sizeof(int));
stack->top = -1;
stack->capacity = capacity;
}
malloc
: Allocates memory for thedata
array based on the specified capacity.top
: Set to -1 to indicate an empty stack initially.
Pushing Elements
The push
function adds an element to the top of the stack:
void push(Stack *stack, int value) {
if (isFull(stack)) {
printf("Stack overflow!\n");
return;
}
stack->top++;
stack->data[stack->top] = value;
}
isFull
is a helper function that checks if the stack is already at its maximum capacity.
If the stack is not full, top
is incremented to point to the new top element, and the value is assigned to data[top]
.
Popping Elements
The pop
function removes the top element from the stack:
int pop(Stack *stack) {
if (isEmpty(stack)) {
printf("Stack underflow!\n");
return -1; // Return some sentinel value to indicate an error or undefined behavior
}
int value = stack->data[stack->top];
stack->top--;
return value;
}
isEmpty
: A helper function that checks if the stack is empty before popping.
If the stack is not empty, the value of the top element is retrieved, top
is decremented to remove it, and the value is returned.
Peeking at the Top Element
The peek
function allows us to examine the top element without removing it:
int peek(Stack *stack) {
if (isEmpty(stack)) {
printf("Stack is empty!\n");
return -1; // Return some sentinel value to indicate an error or undefined behavior
}
return stack->data[stack->top];
}
If the stack is not empty, the value of the top element is returned.
If the stack is empty ideally the returned value should be something that cannot exist in the stack. Whatever value is decided to be returned to indicate an empty stack should be documented for future reference.
Freeing Memory
Upon completing the use of the stack, it is vital to free the dynamically allocated memory to prevent memory leaks:
void freeStack(Stack *stack) {
free(stack->data);
stack->data = NULL;
stack->top = -1;
stack->capacity = 0;
}
Final Code
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int *data;
int top;
int capacity;
} Stack;
void initializeStack(Stack *stack, int capacity) {
stack->data = (int *)malloc(capacity * sizeof(int));
stack->top = -1;
stack->capacity = capacity;
}
int isEmpty(Stack *stack) {
return stack->top == -1;
}
int isFull(Stack *stack) {
return stack->top == stack->capacity - 1;
}
void push(Stack *stack, int value) {
if (isFull(stack)) {
printf("Stack overflow!\n");
return;
}
stack->top++;
stack->data[stack->top] = value;
}
int pop(Stack *stack) {
if (isEmpty(stack)) {
printf("Stack underflow!\n");
return -1;
}
int value = stack->data[stack->top];
stack->top--;
return value;
}
int peek(Stack *stack) {
if (isEmpty(stack)) {
printf("Stack is empty!\n");
return -1;
}
return stack->data[stack->top];
}
void freeStack(Stack *stack) {
free(stack->data);
stack->data = NULL;
stack->top = -1;
stack->capacity = 0;
}
int main() {
Stack stack;
initializeStack(&stack, 5); // Create a stack with a capacity of 5
push(&stack, 10);
push(&stack, 20);
push(&stack, 30);
push(&stack, 40);
printf("Top element: %d\n", peek(&stack)); // Output: Top element: 40
pop(&stack);
pop(&stack);
printf("Top element after two pops: %d\n", peek(&stack)); // Output: Top element after two pops: 20
push(&stack, 50);
printf("Top element after push: %d\n", peek(&stack)); // Output: Top element after push: 50
freeStack(&stack);
return 0;
}
I added a little main
function, it creates a stack with a capacity of 5, pushes four elements (10, 20, 30, and 40) onto the stack, peeks at the top element, pops two elements, pushes one more element (50), and peeks at the top element again. Finally, it frees the dynamically allocated memory used by the stack before the program terminates.
Conclusion
In this blog post, we have covered the implementation of a stack data structure in C, utilizing dynamic memory allocation. The stack enables us to store elements in a Last-In, First-Out manner. Dynamic memory allocation provides the stack with the flexibility to resize based on our requirements during program execution. Understanding and implementing fundamental data structures like the stack is crucial for building efficient and organized algorithms in C and other programming languages.
This is the third data structure implementation in the C Magic series. But if you wanna know more about stacks check out my in-depth blog post, Revealing the Impact of Stacks in Data Structures series.
Also, feel free to hit me up on Twitter to have a chat.
Happy coding!