Skip to Content
Course content

151: Implementing a Stack with Linked Lists

Click on the "Edit" button in the top corner of the screen to edit your slide content.

When you first move from array-based stacks to linked-list stacks, the logic feels intuitive: you just keep adding nodes to the front. But C's handling of pointers often leads to a very specific, frustrating bug where your stack simply... doesn't change. Or worse, it crashes the next time you try to access it.

Take a look at this pop implementation. It looks logically sound at first glance, but it's broken in a way that will keep you up until 3 AM if you aren't looking for it.

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

void pop(Node* head) {
    if (head == NULL) return;

    Node* temp = head;
    head = head->next; 
    free(temp);
    printf("Popped successfully!\n");
}

int main() {
    Node* stack = push(stack, 10);
    stack = push(stack, 20);
    
    pop(stack); // We want to remove 20
    // Now we check the stack... but wait.
}

The Pass-by-Value Trap

If you run this, you'll notice something weird. You call pop(stack), the function says it worked, but when you look at stack in main, it's still pointing to the node containing 20. Then, the moment you try to access that node, the program segfaults. Why?

Because in C, everything is passed by value. When you pass stack into pop(Node* head), the function creates a local copy of the pointer. Inside the function, head = head->next successfully moves that local copy to the next node. But it does absolutely nothing to the stack pointer back in main.

You've effectively freed the memory that stack is still pointing to. You've created a dangling pointer. This is a classic "C rite of passage."

Updating the Caller with Double Pointers

To fix this, we need to tell the pop function where the actual stack pointer lives in memory, not just what address it currently holds. We do this by passing a pointer to the pointer (Node**).

void pop(Node** head_ref) {
    if (*head_ref == NULL) {
        printf("Stack Underflow\n");
        return;
    }

    // Store the current head so we can free it
    Node* temp = *head_ref;
    
    // Update the actual pointer in main to point to the next node
    *head_ref = (*head_ref)->next;
    
    free(temp);
}

// Usage in main:
pop(&stack); // Pass the address of the pointer

By using Node** head_ref, we are dereferencing the pointer to reach back into main's stack frame and modify the original stack variable. Now, when *head_ref = (*head_ref)->next executes, the change persists after the function returns.

Managing the Push Logic

Implementing push is generally safer because you can simply return the new head. I personally prefer this pattern over double pointers for push because it makes the data flow more explicit. You're telling the caller: "Here is the new state of your stack."

Node* push(Node* head, int value) {
    Node* newNode = malloc(sizeof(Node));
    if (!newNode) return head; // Always check for malloc failure

    newNode->data = value;
    newNode->next = head; // Link new node to the old top
    return newNode;       // This becomes the new head
}

The beauty of the linked list approach here is that you're no longer bound by a fixed array size. Your stack can grow until the system runs out of heap memory. Just remember: every malloc in your push must eventually be balanced by a free in your pop, or you'll leak memory like a sieve.




📋 Practical Task

Build a Browser History "Back" Buffer

Create a program that simulates the "Back" button of a web browser using a linked-list stack. Your program should support the following operations via a simple menu loop:

  • Visit Page: (Push) Takes a string (URL) and pushes it onto the stack.
  • Go Back: (Pop) Removes the current page and displays the URL of the page you've returned to. If the stack becomes empty, notify the user that they have reached the start of their history.
  • View Current: (Peek) Displays the URL at the top of the stack without removing it.

Requirements:
1. Use a struct that handles strings (either via char arrays or char*).
2. Implement the pop function using a double pointer (Node**) to ensure the history is updated correctly in the main loop.
3. Ensure all dynamically allocated memory is freed before the program exits.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.