Skip to main content

Stack using linked list in C

 A stack data structure is used on cases where the last-in-first-out (LIFO) is needed. For example, Undo is implemented using stack, ie., the most recently made change is undone first. The article explains a sample implementation of the stack using linked list in C.

Environment

  • Operating System - Windows 11
  • MSYS2 MinGW - MinGW version MINGW64_NT-10.0-22000
  • Compiler - GCC 10.2.0

The implementation in the article uses a linked list, and the first structure used for the code is the entry structure, which holds the data, in this case a single integer variable, and a pointer to the same structure. The integer value can be replaced by a void pointer for a more generic implementation of the stack, but in this article an integer data is used throughout. The second structure is the stack itself, which has a pointer to an entry structure, instead of the structure a single pointer could also be used, but a structure is chosen for better clarity and encapsulation. The two structures are defined as follows,

typedef struct entry {
    int data;
    struct entry *next;
} entry;

typedef struct stack {
    entry *top;
} stack;

Then comes the a list of operations that can be performed on the stack,

  • add a value to the top of stack, also known as push operation
  • remove the value from the top of stack, also known as pop operation
  • get the value at the top of the stack (without removal), also known as peek operation
  • check if the stack is empty
  • initialize a new stack
  • print all elements in the stack

Initializing the stack

The stack has only one pointer called top, assigning the pointer to NULL is the only operation done as part of stack initialization. Initializing the stack is a required step,  otherwise the poniter will have garabage value and de-referencing the poniter will cause segmentation fault at runtime.

void init_stack(stack *s)
{
    s->top = NULL;
}

Checking empty stack

The initial state of the stack is known now, so checking for an empty stack is just checking whether the top pointer is NULL. The function returns a 0 or 1 (can be interpreted to false and true respectively) by the callers.

int empty(stack *s)
{
    return s->top == NULL;
}

Add a value to stack

Adding a new value is little complicated, since linked list is being used. For every new entry, the correspoding memory has to be allocated, and failures during memory allocation also needs to be handled. Once memory is sucessfully allocated, the input data is added to the structure and it will be linked to the existing list pointed by top. Once linked, the new entry will act as the new top as stack follows LIFO model.

int push(stack *s, int data)
{
    entry *e = malloc(sizeof(*e));
    if (e == NULL) {
        fprintf(stderr, "Unable to allocate memory.");
        return -1;
    }
    e->data = data;
    e->next = s->top;
    s->top = e;
    return 0;
}

Get the value at top of stack

While getting the value at the top is rather simple, the stack needs to be checked whether it is empty. So, the function checks for an empty stack before returning the value at top of the stack.

int peek(stack *s, int *data)
{
    if (empty(s)) {
        return -1;
    }
    *data = s->top->data;
    return 0;
}

Remove entry from the top of stack

Again, an entry can not removed from an empty stack. So, the first check during removal is to check for an empty stack. If not empty, the current top is referenced using a variable and will be set to the next element in the list. The data from the old top is returned, and the memory for the entry at old top is freed.

int pop(stack *s, int *data)
{
    entry *temp;

    if (empty(s)) {
        return -1;
    }
    temp = s->top;
    *data = temp->data;
    s->top = temp->next;
    free(temp);
    return 0;
}

Printing the elements in the stack

For printing the elements, start from the top print the data if available, and move to the next. Since the top should not be adjusted for printing, a temporary pointer is used to traverse the list.

void dump_stack(stack *s)
{
    entry *e = s->top;
    while (e != NULL) {
        printf("%d -> ", e->data);
        e = e->next;
    }
    printf("\n");
}

Cleaning up the stack

While the operating system cleans up any allocated memory by an application that exited, it is a good practice to clean up all the allocated memory for the program before exiting. So to achieve this, the entries are popped one by one till the stack is empty as a cleanup procedure.

void cleanup_stack(stack *s)
{
    int data;
    while (!empty(s)) {
        pop(s, &data);
    }
}

Unit testing

The code is testing using a set of tests which covers most of the code and branches. The various test methods that were used are,

  • Push test - push a value to stack, peek and check if the value is same
  • Empty stack test - test if stack is empty
  • Empty stack pop test - failure branch case, expected to return error during pop
  • Pop test - pop a value, and verify it to be the same as expected data
  • Stack cleanup test - cleanup stack and verify it is empty

void push_entry_test(stack *s, int in_data)
{
    int ret;
    int out_data;

    printf("Push entry (%d) in stack test - ", in_data);
    ret = push(s, in_data);
    assert(ret == 0);
    ret = peek(s, &out_data);
    assert(ret == 0);
    assert(out_data == in_data);
    printf("[PASS]\n");
}

void empty_stack_test(stack *s)
{
    int ret = empty(s);
    assert(ret == 1);
}

void empty_stack_pop_test(stack *s)
{
    int ret;
    int data;

    empty_stack_test(s);

    printf("Pop empty stack test - ");
    ret = pop(s, &data);
    assert(ret == -1);
    printf("[PASS]\n");
}

void pop_entry_test(stack *s, int verify_data)
{
    int ret;
    int data;

    printf("Pop entry (%d) in stack test - ", verify_data);
    ret = pop(s, &data);
    assert(ret == 0);
    assert(data == verify_data);
    printf("[PASS]\n");
}

void stack_cleanup_test(stack *s)
{
    printf("Stack cleanup test - ");
    cleanup_stack(s);
    empty_stack_test(s);
    printf("[PASS]\n");
}

void run_unit_tests()
{
    stack s;

    init_stack(&s);

    empty_stack_pop_test(&s);
    push_entry_test(&s, 5);
    pop_entry_test(&s, 5);
    push_entry_test(&s, 7);
    push_entry_test(&s, 8);
    push_entry_test(&s, 9);
    dump_stack(&s);
    stack_cleanup_test(&s);

    empty_stack_pop_test(&s);
    push_entry_test(&s, 2);
    push_entry_test(&s, 3);
    push_entry_test(&s, 4);
    pop_entry_test(&s, 4);
    pop_entry_test(&s, 3);
    dump_stack(&s);
    push_entry_test(&s, 8);
    pop_entry_test(&s, 8);
    pop_entry_test(&s, 2);
    stack_cleanup_test(&s);
}

After running the test code, the following output is generated on the MinGW terminal.

$ ./a.exe
Pop empty stack test - [PASS]
Push entry (5) in stack test - [PASS]
Pop entry (5) in stack test - [PASS]
Push entry (7) in stack test - [PASS]
Push entry (8) in stack test - [PASS]
Push entry (9) in stack test - [PASS]
9 -> 8 -> 7 ->
Stack cleanup test - [PASS]
Pop empty stack test - [PASS]
Push entry (2) in stack test - [PASS]
Push entry (3) in stack test - [PASS]
Push entry (4) in stack test - [PASS]
Pop entry (4) in stack test - [PASS]
Pop entry (3) in stack test - [PASS]
2 ->
Push entry (8) in stack test - [PASS]
Pop entry (8) in stack test - [PASS]
Pop entry (2) in stack test - [PASS]
Stack cleanup test - [PASS]

Thanks for reading, please leave suggestions/comments if any.


Comments

Popular posts from this blog

BMI Calculator using Python and Tkinter

Body Mass Index can be calculated using a simple formula kg/m 2 , where the weight is represented in kilograms (kg) and the height is represented in metres (m). The article presents code to create a simple GUI to calculate BMI based on user input values. The GUI is implemented using tkinter and tkinter.ttk libraries in Python language.

Using hilite.me API in Python

hilite.me is a light weight website made by Alexander Kojevnikov , used to format source code from various programming languages into formatted HTML snippets which can be added to blogs, articles, and other web pages as needed. I personally use the API to format the source code snippets in this blog, and it has greatly reduced the time taken to complete an article, much thanks to the creator. In this article, an automated way of using the hilite.me API through Python and a simple HTML document is created with formatted code snippets.

Tic-tac-toe game using Python and tkinter

Tic-tac-toe is a popular two player game where each player tries to occupy an empty slot in a 3x3 board until one of them wins or the entire board is filled without any winner. The winner has to occupy three continuous cells in any direction, including the two diagonals. In this article, a version of the tic-tac-toe game is coded using Python and tkinter library.