Skip to content

Physical and logical structures

Memory offers two layouts, cells side by side and nodes joined by pointers. A stack, a queue or a tree is a set of rules for using one, written down as an abstract data type.

Helps to know firstStacks

Helps to know first
This topic

First, the picture

Is a stack a kind of memory? Watch the same seven numbers become a stack, a queue and a tree without moving.

One array, three structures

Seven numbers in a row, read as a stack, a queue and a tree

As a stack: top is 7, the next free cell. A push would write there; a pop would take cell 6, 80.

Structure
0.00 / 6.60 s
Describe this picture

A picture with no code. Seven cells in a row, numbered 0 to 6 above, hold 50 30 70 20 40 60 80. The clip reads them three ways, about 10 seconds. As a stack: a dashed empty slot appears after cell 6, numbered 7, with the marker top under it, and cell 6 is tinted in the second colour with “a pop takes 80” above it. As a queue: the markers head under cell 0 and tail under the slot after cell 6; cell 0 is tinted, with “the next to leave is 50” above it. As a tree: each cell rises out of the row to its place in a tree, leaving a dashed outline behind, and lines join cell i to cells 2i + 1 and 2i + 2: 50 on top, 30 and 70 under it, then 20, 40, 60 and 80, each with its cell number beside it. Under the picture is one sentence on the current reading. After the clip, the control “Structure” (Stack, Queue, Tree) stops it on one reading. The transport plays and pauses, steps one reading back or forward, and scrubs; the choice and a paused step are kept in the page address as one-array.view and one-array.step.

The layout and the rules

The seven numbers never move. What makes them a stack, a queue or a tree is the rule you use to read them: where the next one goes in and which one comes out. A data structure has two parts, and it helps to keep them apart:

  • the physical structure: how the values sit in memory;
  • the logical structure: the rules for adding, finding and removing them.

Two layouts in memory

Memory gives you two ways to keep many values.

  • Contiguous: the values side by side in one block, an array. Cell i is found by arithmetic, one address plus i times the size of a cell.
  • Linked: each value in a node of its own, anywhere in the heap, holding the address of the next node. Linked lists, from chapter 7 on, are built this way.

Either layout can carry any of the logical structures below. Stacks built a stack both ways.

Four logical families

  • Linear: one value after another. A stack takes from the end it last added to; a queue takes from the other end.
  • Hierarchical: a tree, each value but the root with one parent, and any number of children.
  • Network: a graph, any value joined to any other.
  • Table: values found by a key, as hashing does.

Read as a tree, the seven cells follow one rule: cell i is the parent of cells 2i + 1 and 2i + 2. So 30, in cell 1, has the children in cells 3 and 4, 20 and 40. That tree happens to be the search tree of a later chapter, kept in an array with no pointers at all.

An abstract data type in C

An abstract data type (ADT) is a list of operations and what they mean, with nothing said about storage. A stack’s is: make an empty stack, push a value, pop the value pushed last, ask whether it is empty. The same ADT can sit on an array or on linked nodes; the operations do not fix the storage.

C has no keyword for this. The pattern is two files and a promise.

The header: what the user may call

#ifndef STACK_H
#define STACK_H

#include <stdbool.h>

struct stack;

struct stack *stack_new(void);
void stack_free(struct stack *s);
bool push(struct stack *s, int v);
int pop(struct stack *s);
bool is_empty(const struct stack *s);

#endif

stack.h declares the five operations and nothing else. The line struct stack; says that a type with that name exists, without saying what is in it: an incomplete type. A file that includes the header may hold a struct stack *, a pointer, because C gives every pointer to a struct the same size and representation, whatever the struct holds. It cannot make a struct stack or look inside one, because it does not know the type’s size or fields. The #ifndef lines stop the header being read twice in one file.

The source: how it is stored

#include <stdlib.h>
#include "stack.h"

#define CAP 16

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

struct stack *stack_new(void)
{
    struct stack *s = malloc(sizeof *s);
    if (s != NULL)
        s->top = 0;
    return s;
}

void stack_free(struct stack *s)
{
    free(s);
}

static bool is_full(const struct stack *s)
{
    return s->top == CAP;
}

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

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

bool is_empty(const struct stack *s)
{
    return s->top == 0;
}

stack.c completes the type, an array of 16 ints and a top, and defines the five functions. pop does not check for an empty stack: popping one is undefined behaviour (here it reads items[-1]; in the linked version below, it follows a NULL pointer). The caller must ask is_empty first. is_full is marked static: it can be called only from inside stack.c, so it is not part of the ADT, and another file may use the same name for something else.

The user’s file

#include <stdio.h>
#include "stack.h"

int main(void)
{
    struct stack *s = stack_new();
    if (s == NULL)
        return 1;
    for (int v = 1; v <= 3; v++)
        push(s, v * 10);
    while (!is_empty(s))
        printf("%d ", pop(s));
    printf("\n");
    stack_free(s);
    return 0;
}

main.c includes the header and uses only the five operations. It never learns that there is an array inside.

Compiling and linking

Each .c file is compiled on its own into an object file, machine code with the names it defines and the names it still needs. The linker then joins the object files and matches each needed name to its definition.

gcc -std=c11 -Wall -Wextra -c stack.c
gcc -std=c11 -Wall -Wextra -c main.c
gcc stack.o main.o -o stacktest
./stacktest

The first two commands make stack.o and main.o, and neither is a program yet: stack.o has no main, and main.o calls functions it does not define. The third links them. The program prints 30 20 10 : pushed 10, 20, 30, popped last first.

stack.h is never compiled by itself. The preprocessor copies it into each file that includes it, so the declarations are the same in both.

Worked example

Sort the structures. An editor’s undo history is a stack: the last change is undone first. The jobs waiting for a printer are a queue: first sent, first printed. Folders on a disk are a tree. Roads between towns are a graph. A phone book looked up by name is a table.

Look inside, and fail. A user writes peek-inside.c, which includes stack.h and tries s->top = 0;. It does not compile:

peek-inside.c:9:6: error: invalid use of undefined type 'struct stack'

main.c and peek-inside.c see only the incomplete type, so the fields do not exist for them. The array stays in stack.c’s hands.

Change the storage, keep the user. stack-linked.c puts the stack of Stacks on linked nodes behind the same five functions: struct stack now holds only a pointer to the top node, push allocates a node, and pop frees one. Link it in place of stack.c:

gcc -std=c11 -Wall -Wextra -c stack-linked.c
gcc stack-linked.o main.o -o stacktest

main.c is not edited and main.o is not even recompiled; it is only linked again. The program prints 30 20 10 as before.

What changes is the memory. The array version is one 68-byte block (16 ints and top) whether it holds 1 item or 16. The linked version is an 8-byte struct stack plus one 16-byte node per item, each a block of its own from malloc, and it has no limit of 16.

Three checks. Read as a tree, what are 30’s children? (20 and 40.) Which file must change for the stack to hold 32 items? (stack.c only: CAP.) Why can main.c hold a struct stack * but not a struct stack? (Its size is unknown there.)

Where it’s used

The C library. FILE, behind fopen, fprintf and fclose, is used only through pointers and functions; programs never reach into it.

Operating systems. Files, sockets and processes are handed to programs as handles, with operations on them and nothing else.

Shared code. A team that publishes a header can rewrite its source, faster or smaller, and every program that uses it only needs linking again.

Reference card

Code or ideaMeaning
contiguousvalues side by side in one block: an array
linkedvalues in nodes joined by addresses
linear, hierarchical, network, tablestack and queue, tree, graph, hashing
abstract data typeoperations and their meaning, not the storage
struct stack;incomplete type: pointers only, no fields
static functionvisible only inside its own file
gcc -c file.ccompile to an object file
gcc a.o b.o -o proglink the object files into a program

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