Skip to content

Stacks

A stack adds and removes items only at its top, last in, first out. Built here on an array with a top index, then on linked nodes; every operation takes the same few steps.

Helps to know firstSingly linked lists

Helps to know first
This topic

First, the picture

How does an editor know what to undo next? It keeps every old value on a stack. Watch the last one saved come back first.

Undoing in reverse order

Each edit saves the old value on a stack; undo takes them back

void push(struct stack *s, int v){    if (s->top == CAP)        return;    s->items[s->top] = v;    s->top = s->top + 1;} int pop(struct stack *s){    s->top = s->top - 1;    return s->items[s->top];}int main(void){    struct stack history = { .top = 0 };    int value = 10;    push(&history, value);    value = 25;    push(&history, value);    value = 40;    push(&history, value);    value = 55;    printf("now %d, last saved %d\n", value, peek(&history));    while (history.top > 0) {        value = pop(&history);        printf("undo: %d\n", value);    }    return 0;}

main has started. history, a struct stack, is in main's frame: 16 ints and top.

Output

0.00 / 18.60 s
Describe this picture

Lines 10 to 22 (push and pop) and 29 to 45 (main) of undo.c, with the line about to run marked; the clip does not step into peek, so main’s line 39 stays marked while it runs. Beside the code is a picture of the stack. On the left, history.items stands upright: items[0] at the bottom up to items[5], each cell with its index on its left and its address on its right, from 0x7fa0 to 0x7fb4, and above them one dashed stub for the other ten cells, all 0. top is a bold line across the strip at the boundary above the last item, with a label “top” and its value; when top is 3, the line sits between items[2] and items[3]. Cells at or above top are dimmed but keep their numbers. On the right are main’s frame, with value, and below it, while the first push or the first pop runs line by line, that call’s frame: push with s and v, or pop with s; s holds 0x7fa0, history’s address. The other calls take a beat each and show no frame. The box the last line wrote is outlined, and a cell written rises into place. The clip plays once, about 19 seconds. history is zeroed and value becomes 10. The first push runs line by line: 10 rises into items[0], then top moves up to 1. value becomes 25 and is pushed in a beat, top 2; then 40, top 3. value becomes 55 and line 39 runs in one beat, peek reading 40 without a frame drawn; the output shows now 55, last saved 40. Then three undos: the first pop runs line by line, top drops to 2 and items[2] dims with its 40 still in it; value goes back to 40 and the output adds undo: 40. Two more undos, a beat each, bring value back to 25 and then 10, top to 1 and then 0. The loop ends with all three numbers still in the array, dimmed, and the clip rests there. Under the picture are one sentence on the current step and the output. The transport plays and pauses, steps one line back or forward, and scrubs; a paused step is kept in the page address as undo.step.

Last in, first out

Each time the editor is about to change a value, it saves the old one. When you press undo, it should bring back the most recent one saved, then the one before, and so on: the last saved is the first restored. A structure that hands things back in reverse order like that is a stack, named after a stack of plates, where you take from the top what you last put there. Last in, first out.

A stack has four operations:

  • push: put an item on top.
  • pop: take the top item off and return it.
  • peek: read the top item without taking it.
  • is empty: are there no items at all?

Nothing else is allowed: no reading the middle, no adding at the bottom. That restriction is what makes a stack simple and fast.

The code and its cost

An array and a top index

#define CAP 16

struct stack {
    int items[CAP];
    int top;
};

The items live in an array, the bottom one in items[0]. top is not the index of the top item: it is the number of items, so it is also the index of the next free cell. An empty stack has top 0; the top item, when there is one, is items[top - 1]. sizeof (struct stack) is 68 bytes: 64 for the array, 4 for top. In the clip, history sits at 0x7fa0, so items[i] is at 0x7fa0 + 4i and top at 0x7fe0.

void push(struct stack *s, int v)
{
    if (s->top == CAP)
        return;
    s->items[s->top] = v;
    s->top = s->top + 1;
}

int pop(struct stack *s)
{
    s->top = s->top - 1;
    return s->items[s->top];
}

int peek(const struct stack *s)
{
    return s->items[s->top - 1];
}

push writes into the next free cell, then moves top up. pop moves top down, then reads the cell top now points at. The functions take the stack’s address, &history, so that they change main’s stack and not a copy of it.

pop does not erase anything. After the three undos in the clip, items[0], items[1] and items[2] still hold 10, 25 and 40. They are simply above top, so they are no longer on the stack, and the next push will write over them.

Full and empty

A stack in an array has room for CAP items. A push on a full stack is an overflow; this push refuses it (lines 12 and 13) and the value is lost, which a real program would report. A pop or a peek on an empty stack is an underflow. These two functions do not check: they rely on the caller, and main only pops while history.top > 0 (line 40). The worked example shows what happens otherwise.

The same stack on nodes

The array is not the only way. Built on the nodes of Singly linked lists, the top of the stack is the head of the list: push is push_front, and pop removes the head node.

struct node *pop(struct node *top, int *v)
{
    struct node *next = top->next;
    *v = top->key;
    free(top);
    return next;
}

pop must give back two things, the key and the new top. It returns the new top, and writes the key through the pointer v into a variable of the caller’s.

The same stack on nodes

Push is push_front; pop unlinks the top node, hands back its key and frees it

#include <stdio.h>#include <stdlib.h> struct node {    int key;    struct node *next;}; struct node *push(struct node *top, int v){    struct node *n = malloc(sizeof *n);    if (n == NULL)        exit(1);    n->key = v;    n->next = top;    return n;} struct node *pop(struct node *top, int *v){    struct node *next = top->next;    *v = top->key;    free(top);    return next;} int main(void){    struct node *top = NULL;    int v;    top = push(top, 10);    top = push(top, 25);    top = push(top, 40);    while (top != NULL) {        top = pop(top, &v);        printf("%d ", v);    }    printf("\n");    return 0;}

main has started.

Output

0.00 / 24.40 s
Describe this picture

linked-stack.c, all of it, with the line about to run marked, beside a picture of memory. At the top is the stack: main’s frame with top at 0x7fe8 and v at 0x7fe4, and beside it the frame of the function main has called. Below is the heap: one row of 16-byte nodes in address order, 10 at 0x1000, 25 at 0x1020 and 40 at 0x1040, each with its key and next field; arrows run from each next field to the node it holds the address of, and from main’s top down to its node; a NULL pointer is a short bar. A pointer of a called function is a small tag on its node. The replay plays once, about 24 seconds. 10 and 25 are pushed a beat each; then push(top, 40) runs line by line: the node appears at 0x1040, its next arrow reaches 25, and main’s top arrow moves onto it. The first pop runs line by line: next is tagged on 25, main’s v becomes 40, the 40 node is hatched, and top’s arrow moves to 25; the output shows 40. The other two pops follow in a beat each, hatching 25 and then 10, until top is NULL and the output reads 40 25 10. The transport plays and pauses, steps one line back or forward, and scrubs; a paused step is kept in the page address as nodes.step.

The linked stack has no fixed capacity: it can grow until malloc fails. It pays with one malloc per push and one free per pop, and a next pointer in every node.

What it costs

Every operation is a fixed handful of steps, in both versions. Counted for the array stack: one comparison and two writes for push, a write and a read for pop, one read for peek. The linked stack’s counts differ (push calls malloc, checks it and writes two fields; pop reads two fields, writes v and calls free), but they are just as fixed. None of them looks at more than the top, so a stack of a million items is as fast as a stack of three.

Memory is another matter. The array stack takes its 68 bytes whatever it holds. The linked one takes one block per item, 32 bytes each on this platform with the C library’s spacing, so three items take 96 bytes.

Running it yourself

gcc -std=c11 -Wall -Wextra -pedantic undo.c -o undo
./undo

It prints now 55, last saved 40, then undo: 40, undo: 25 and undo: 10. linked-stack.c builds the same way and prints 40 25 10.

Worked example

By hand. On an empty array stack: push 3, push 7, pop, push 2, push 9, pop, pop.

OperationReturnsitems[0..2]top
push 33, 0, 01
push 73, 7, 02
pop73, 7, 01
push 23, 2, 02
push 93, 2, 93
pop93, 2, 92
pop23, 2, 91

The pops return 7, 9 and 2. The stack ends holding only 3, with top 1, while items[1] still holds 2 and items[2] still holds 9: the push of 2 wrote over the 7 that had been popped.

In the clip. After line 37 runs, top is 3, and peek returns 40. After all three pops, items[0] still holds 10. And for a stack with push 1, 2, 3 and then two pops, the top item is 1, and top is 1 too.

Full. With CAP 16, sixteen pushes in a row fill items[0] to items[15] and make top 16; the seventeenth is refused, and the stack is unchanged.

Empty. Here is a pop on an empty stack:

int main(void)
{
    struct stack history = { .top = 0 };
    int value = pop(&history);
    printf("undo: %d\n", value);
    return 0;
}

pop makes top −1 and reads items[-1], an int just before the array, which is not part of it. C does not stop this. The program prints whatever those bytes hold. Built with -fsanitize=undefined, it reports the bad index, then carries on and prints whatever it read. The report:

underflow.c:13:20: runtime error: index -1 out of bounds for type 'int [16]'

The fix belongs to the caller: check that top is more than 0 before popping, as main does on line 40.

Where it’s used

The call stack. When a function calls another, the new frame goes on top of the caller’s, and when it returns, its frame comes off first. The frames of The call stack (1.3) are a stack in this exact sense: the code the compiler generates pushes a frame at each call and pops it at the return.

Undo. Editors keep the changes you make on a stack and undo the most recent first; many keep a second stack for redo.

The back button. A browser pushes each page you leave; back pops the last one.

Brackets and expressions. Compilers and calculators check that brackets match and work out expressions with a stack: Matching brackets (8.2), Infix to postfix (8.3) and Evaluating postfix (8.4). Depth-first search (19.3) explores a maze with one.

Reference card

Code or ideaMeaning
push, pop, peek, is emptythe only operations: last in, first out
topthe number of items, and the next free cell
s->items[s->top] = v; s->top++;push: write, then move up
s->top--; return s->items[s->top];pop: move down, then read
s->items[s->top - 1]peek
top == CAP / top == 0full (overflow on push) / empty (underflow on pop)
push_front, remove the headthe same stack on nodes
every operationa fixed few steps, whatever the stack holds

Platform: C11, gcc on x86-64 Linux: int 4 bytes, pointers 8, little-endian. Addresses in the pictures are short stand-ins, by one rule for the whole course; sizes are the real ones.

End of the lesson

Where to go next.

New lessons by email

Are you 13 or over?

Accounts and email updates are for people aged 13 or over. We keep only your answer, never your date of birth.

Phasorium

Account

Sign inWith Google, GitHub or your email

Subjects

Site

Look