Skip to main content
Compilation Toolchain
Compilation Toolchain
IOT

Interactive Lecture Plan

Instructor material — not student-facing

This document is a session-by-session teaching guide for the instructor. It contains live demo scripts, timing notes, and internal references. Students should use the main lecture document instead.

This document is the session-by-session plan for the 6 hours of interactive lectures. Each session is 1.5h. All demonstrations are done live in a terminal, students follow along.

The 6 hours of practicals (TPs) are described separately.

Info

Context: large lecture hall (60+ students), most without laptops. Active learning is driven by Wooclap quizzes on smartphones. Each Wooclap block takes 2–3 min: launch the question, wait for answers, reveal results, debrief. Aim for 2–3 questions per session.


Setup for the Instructor

Required Tools

ToolPurpose
gcc / clangC compilation
makeBuild automation
ar, nm, ldd/otoolLibrary inspection
gitVersion control
clang-formatCode formatting
clang-tidyStatic analysis
gdb / lldbDebugger
valgrind or -fsanitize=addressMemory checking

Wooclap Preparation

Create a Wooclap event before the course and pre-load all questions below so you can launch them instantly without typing during the session. Suggested event name: “Compilation Toolchain — [date]”.

#SessionQuestion typeTopic
Q11.2PollPreprocessor role
Q21.3MCQSymbol types in nm output
Q31.4MCQLinker vs preprocessor error
Q42.3MatchingAutomatic variables
Q52.4Multiple selectWhat gets rebuilt?
Q63.2PollStatic library after deletion
Q73.3PollStatic vs dynamic binary size
Q84.3MCQCorrect conflict resolution
Q94.5MCQNASA rule violated by uninitialized variable
Q104.7NumberNumber of issues clang-tidy finds in ugly.c

Terminal Setup

  • Use a terminal with a large font (24pt+) — e.g. Alacritty, kitty, or any terminal with Ctrl++ zoom
  • Split panes with tmux to show code + terminal side by side:
tmux new-session -s lecture
# Ctrl-b % → vertical split (editor left, shell right)
# Ctrl-b " → horizontal split if needed
  • Use a minimal Vim/Neovim or nano for editing (students should not be distracted by IDE features)
  • Prepare a ~/lecture/ workspace with starter files for each session (see below)

File Preparation

Create the following directory structure before the course:

~/lecture/
├── session1/           # Compilation basics
│   ├── hello.c
│   ├── util.h
│   ├── util.c
│   ├── main.c
│   ├── a.h / b.h       # multiple inclusion demo
│   └── c.h / d.h       # cyclic inclusion demo
├── session2/           # Makefile
│   ├── src/
│   │   ├── main.c
│   │   ├── inout.c
│   │   └── math_utils.c
│   ├── include/
│   │   ├── inout.h
│   │   └── math_utils.h
│   └── Makefile.v0     # empty, will be built live
├── session3/           # Libraries + Git basics
│   ├── lib/
│   │   ├── mylib.h
│   │   └── mylib.c
│   ├── app/
│   │   └── main.c
│   └── Makefile
└── session4/           # Git advanced + Code quality
    ├── ugly.c          # poorly formatted code
    ├── buggy.c         # memory errors for sanitizer demo
    ├── .clang-format
    └── .clang-tidy

Session 1 — Compilation Basics (1.5h)

Learning Objectives

  • Understand the full compilation pipeline (preprocessing → AST → IR → assembly → binary → linking)
  • Be able to run each step individually with gcc/clang
  • Diagnose common errors (missing includes, undefined symbols)

1.1 — Motivation (10 min)

Format: Discussion with students

Show the “Program vs. Software” table from the existing slides. Ask the class:

“Who has worked on a project with more than 3 files?” “How did you compile it? Did you type gcc commands by hand?”

Introduce the 4 questions: simultaneous work, versioning, debugging, automation.

1.2 — Preprocessing (15 min)

Format: Live terminal demo

Info

Wooclap Q1 — Poll (2 min)

Launch before showing any terminal output.

What does the C preprocessor do with a #include "util.h" directive?

  • A. It checks that the file exists and records the dependency
  • B. It physically copies the content of util.h into the source file
  • C. It compiles util.h separately and links the result
  • D. It does nothing — #include is handled by the linker

Expected: B. Debrief: show gcc -E main.c | less and point out how the header content appears verbatim. The size jump from wc -l reinforces this concretely.

# Start with a simple example
cat util.h
cat main.c

# Show what the preprocessor does
gcc -E main.c | less

# Point out: #include is literally copy-paste, #define is text replacement

1.3 — From Source to Binary (30 min)

Format: Step-by-step live demo

Walk through each stage on the same main.c + util.c example:

# 1. Preprocessing
gcc -E main.c -o main.i
wc -l main.c main.i           # show how much the preprocessor adds

# 2. AST (optional — show briefly, don't dwell)
clang -Xclang -ast-dump -fsyntax-only main.c 2>/dev/null | head -30

# 3. Intermediate representation
clang -S -emit-llvm main.c -o main.ll
cat main.ll                     # highlight: ideal machine, no register limits

# 4. Assembly
gcc -S main.c -o main.s
cat main.s                      # highlight: real registers (rax, rdi, rsi)

# 5. Object file
gcc -c main.c -o main.o
hexdump -C main.o | head -20   # it's just bytes now
nm main.o                       # show symbols: T = defined, U = undefined

# 6. Linking
gcc -c util.c -o util.o
nm util.o                       # compare: _compare is T here, U in main.o
gcc main.o util.o -o program
nm program                      # all symbols resolved
./program ; echo $?             # run it!
Info

Wooclap Q2 — MCQ (2 min)

Launch after showing nm main.o, before running nm util.o.

In the output of nm main.o, the symbol compare appears with the letter U. What does this mean?

  • A. The symbol is Undefined — main.o needs it but doesn’t provide it
  • B. The symbol is Used — it is called at least once in the file
  • C. The symbol is Unlinked — it was stripped during optimisation
  • D. U is an error code — the symbol is corrupt

Expected: A. Debrief: contrast with nm util.o where compare appears as T (Text section = defined). Linking resolves the U by finding the matching T.

Key teaching moment: After showing nm, ask:

“What does U _compare mean in main.o? Why does linking fix it?“

1.4 — Common Errors (15 min)

Format: Live error reproduction

Error 1 — Missing include / undeclared identifier:

# Remove #include "util.h" from main.c, try to compile
gcc -c main.c
# → "implicit declaration" or "undeclared identifier"
# Fix: add the include back

Error 2 — Undefined symbol (linker error):

# Try to build main.o alone into an executable
gcc main.o
# → "Undefined symbols: _compare"
# Fix: link with util.o
Info

Wooclap Q3 — MCQ (3 min)

Show this error on the projector, launch before explaining it.

/usr/bin/ld: main.o: undefined reference to symbol 'compare'

At which compilation stage does this error occur?

  • A. Preprocessing — the #include directive was not resolved
  • B. Compilation — the compiler could not parse the source
  • C. Linking — the symbol was declared but never defined in any object file
  • D. Runtime — the symbol could not be found in the dynamic library

Expected: C. Debrief: contrast with a preprocessor error (implicit declaration of function) — a preprocessor/compiler error mentions a file and line number, while a linker error mentions an object file and a symbol name. This distinction is the key diagnostic skill for the course.

1.5 — Include Guards (15 min)

Format: Live demo of the problem + solution

# Show multiple inclusion error
cat a.h        # defines g_variable
cat b.h        # includes a.h
cat main.c     # includes both a.h and b.h
gcc main.c     # → redefinition error

# Show the preprocessed output to explain WHY
gcc -E main.c  # g_variable defined twice!

# Fix with header guards
cat a_fixed.h  # with #ifndef/#define/#endif
gcc main_fixed.c  # compiles!

Show both styles: #ifndef guards and #pragma once.

1.6 — Quick Recap (5 min)

Draw the pipeline on the board (or whiteboard app):

.c → [preprocessor] → .i → [compiler] → .s → [assembler] → .o → [linker] → executable
       gcc -E              gcc -S             as/gcc -c          gcc

Ask: “If you see undefined reference to X, which step failed?”


Session 2 — Makefile (1.5h)

Learning Objectives

  • Understand Make’s dependency graph model
  • Write Makefiles from scratch, progressing from basic to generic
  • Use automatic variables, inference rules, and dependency generation

2.1 — Why Make? (10 min)

Format: Live demo of the pain

cd session2/

# Compile manually
gcc -W -Wall -Wextra -c src/main.c -Iinclude
gcc -W -Wall -Wextra -c src/inout.c -Iinclude
gcc -W -Wall -Wextra -c src/math_utils.c -Iinclude
gcc main.o inout.o math_utils.o -o project -lm

# Now change one file
vim src/inout.c   # small change
# Do we rebuild everything? Just inout.o? How do we know?

Reference the Quake 3 example: 328 .c files = 329+ commands minimum.

2.2 — First Makefile (20 min)

Format: Build a Makefile from scratch, live

vim Makefile

Build it step by step, explaining each concept:

Step 1 — Hardcoded rules:

project: main.o inout.o math_utils.o
    gcc main.o inout.o math_utils.o -o project -lm

main.o: src/main.c include/inout.h include/math_utils.h
    gcc -W -Wall -Wextra -Iinclude -c src/main.c

inout.o: src/inout.c include/inout.h
    gcc -W -Wall -Wextra -Iinclude -c src/inout.c

math_utils.o: src/math_utils.c include/math_utils.h
    gcc -W -Wall -Wextra -Iinclude -c src/math_utils.c

clean:
    rm -f *.o project

Run it, show incremental builds:

make
# touch one file, rebuild
touch src/inout.c
make            # only inout.o and project are rebuilt!
make clean

Key teaching moment: explain the dependency graph. Draw it on the board.

2.3 — Variables and Automatic Variables (15 min)

Format: Refactor the Makefile live

Step 2 — Add variables:

TARGET = project
CC = gcc
CFLAGS = -W -Wall -Wextra -Iinclude
LDFLAGS = -lm

Step 3 — Use automatic variables:

main.o: src/main.c include/inout.h include/math_utils.h
    $(CC) $(CFLAGS) -c $<
Info

Wooclap Q4 — Matching (3 min)

Launch after writing the rule with automatic variables on screen.

Match each automatic variable to its meaning in a Makefile rule.

VariableMeaning
$@?
$<?
$^?

Choices: The target name / The first prerequisite / All prerequisites (space-separated)

Expected: $@ → target, $< → first prerequisite, $^ → all prerequisites. Debrief: rewrite one of the rules live with and without automatic variables to show the equivalence. Common mistake: students confuse $< and $^ — the mnemonic “less-than = first/smallest” can help.

Show the variable reference table: $@, $<, $^, $*.

2.4 — Inference Rules (15 min)

Format: Continue refactoring live

Step 4 — Pattern rules:

%.o: src/%.c
    $(CC) $(CFLAGS) -c $<

Show: this replaces all three .o rules!

Problem: we lost header dependencies. Before demonstrating, launch:

Info

Wooclap Q5 — Multiple select (2 min)

Show the pattern-rule Makefile on the projector. Launch before running touch.

We run touch include/inout.h then make. Which files will be rebuilt?

  • main.o
  • inout.o
  • math_utils.o
  • project (the final executable)
  • Nothing — Make does not track headers

Expected: Nothing — with pattern rules and no -MMD, Make has no way to know that inout.o depends on inout.h. Debrief: this is the exact bug -MMD and -include $(DEPS) fixes. The contrast with the correct behaviour (section 2.5) will make the fix memorable.

# Change something in inout.h
touch include/inout.h
make            # nothing rebuilds! Bad.

2.5 — Automatic Dependencies (15 min)

Format: Live demo

# Show what -MMD does
gcc -MMD -Iinclude -c src/main.c
cat main.d
# Output: main.o: src/main.c include/inout.h include/math_utils.h

Step 5 — Add -MMD and -include:

CFLAGS = -W -Wall -Wextra -Iinclude -MMD
DEPS = $(OBJ:.o=.d)

-include $(DEPS)

Now touch include/inout.h && make correctly rebuilds dependent files.

2.6 — Generic Makefile (10 min)

Format: Final version, built live

TARGET  = project
CC      = gcc
CFLAGS  = -W -Wall -Wextra -Iinclude -MMD
LDFLAGS = -lm
SRC     = $(wildcard src/*.c)
OBJ     = $(SRC:src/%.c=%.o)
DEPS    = $(OBJ:.o=.d)

default: $(TARGET)

%.o: src/%.c
    $(CC) $(CFLAGS) -c $<

$(TARGET): $(OBJ)
    $(CC) $(LDFLAGS) $^ -o $@

-include $(DEPS)

.PHONY: clean
clean:
    rm -f *.o *.d $(TARGET)

Add a new .c file live and show it’s automatically picked up.

2.7 — Debug/Release Builds (5 min)

Quick demo of conditional variables:

DEBUG ?= yes
ifeq ($(DEBUG),yes)
    CFLAGS += -g -DDEBUG
else
    CFLAGS += -O3
endif
make                    # debug build
make DEBUG=no           # release build

Session 3 — Libraries (1.5h)

Learning Objectives

  • Understand static vs. dynamic libraries
  • Build and use both types
  • Inspect library contents and dependencies

3.1 — What Are Libraries? (10 min)

Format: Discussion + terminal demo

# Every C program uses libraries — even "Hello World"
cat > /tmp/hello.c << 'EOF'
#include <stdio.h>
int main() { printf("Hello\n"); return 0; }
EOF
gcc /tmp/hello.c -o /tmp/hello

# Show linked libraries
ldd /tmp/hello        # Linux
# otool -L /tmp/hello # macOS

“Where does printf come from? You didn’t write it. You didn’t compile it.”

Key concept: a library is precompiled code + headers.

3.2 — Static Libraries (25 min)

Format: Build one from scratch

cd session3/

# Look at our library source
cat lib/mylib.h
cat lib/mylib.c

# Step 1: Compile to object file
gcc -W -Wall -c lib/mylib.c -o lib/mylib.o

# Step 2: Create static library
ar rcs lib/libmylib.a lib/mylib.o

# Inspect it
ar -t lib/libmylib.a        # list contents
nm lib/libmylib.a           # show symbols

# Step 3: Use it
gcc -W -Wall app/main.c -Ilib -Llib -lmylib -o app/program
./app/program

Key teaching moments:

  • ar = archiver (bundles .o files)
  • -L = where to look, -l = what to link (strip lib prefix and .a suffix)
  • The library is embedded in the executable
Info

Wooclap Q6 — Poll (2 min)

Launch before running rm.

We delete libmylib.a after linking app/program statically. What happens when we run ./app/program?

  • A. It crashes immediately — the library is missing
  • B. It runs fine — the library code was copied into the executable at link time
  • C. It runs fine — the OS finds the library automatically in /usr/lib
  • D. It refuses to start — the dynamic linker reports a missing dependency

Expected: B. Debrief: the whole point of a static library — independence from the environment at runtime. The executable is self-contained.

# Prove it: delete the library, program still works
rm lib/libmylib.a
./app/program      # still runs!

3.3 — Dynamic Libraries (25 min)

Format: Build one from scratch

# Step 1: Compile with Position Independent Code
gcc -W -Wall -fPIC -c lib/mylib.c -o lib/mylib.o

# Step 2: Create shared library
gcc -shared -o lib/libmylib.so lib/mylib.o    # Linux
# gcc -shared -o lib/libmylib.dylib lib/mylib.o  # macOS

# Step 3: Link against it
gcc -W -Wall app/main.c -Ilib -Llib -lmylib -o app/program

# Try to run...
./app/program
# Error! Library not found at runtime

# Fix: tell the loader where to find it
LD_LIBRARY_PATH=./lib ./app/program   # Linux
# DYLD_LIBRARY_PATH=./lib ./app/program  # macOS

Key teaching moment: difference between compile-time linking and runtime loading.

# Show the dependency
ldd app/program     # libmylib.so => not found

# Now delete the library
rm lib/libmylib.so
./app/program       # fails! Unlike static.

3.4 — Static vs. Dynamic Comparison (10 min)

Format: Side-by-side comparison in terminal

# Build both versions
gcc app/main.c -Ilib -Llib -lmylib_static -o app/static_prog   # static
gcc app/main.c -Ilib -Llib -lmylib        -o app/dynamic_prog  # dynamic

# Compare sizes
ls -la app/static_prog app/dynamic_prog

# Compare dependencies
ldd app/static_prog
ldd app/dynamic_prog
Info

Wooclap Q7 — Poll (2 min)

Launch before running ls -la.

We build two versions of the same program: one linked statically against libmylib.a, one dynamically against libmylib.so. Which executable file is larger on disk?

  • A. The statically linked binary is larger
  • B. The dynamically linked binary is larger
  • C. They are the same size

Expected: A. Debrief: show the actual ls -l output. The static binary embeds the compiled library code; the dynamic binary only stores a reference (the soname). Relate to the RAM sharing benefit of dynamic libraries: 100 processes linking the same .so share one copy in memory.

Recap the tradeoffs (embedded in existing slides):

  • Static: no dependencies, larger binary, needs rebuild if lib changes
  • Dynamic: smaller binary, shared in RAM, but runtime dependency

3.5 — Library Versioning (10 min)

Format: Discussion with terminal examples

# Real-world example
ls -la /usr/lib/x86_64-linux-gnu/libz*
# libz.a
# libz.so -> libz.so.1.2.11
# libz.so.1 -> libz.so.1.2.11
# libz.so.1.2.11

# Symlink chain: libz.so → libz.so.1 → libz.so.1.2.11
# At compile time: linker follows libz.so
# At runtime: loader follows libz.so.1 (soname)

Explain M.m.p versioning: major (breaking), minor (compatible), patch (bugfix).

3.6 — Integrating Libraries in Makefile (10 min)

Format: Update the session 2 Makefile

# Add library build targets
lib/libmylib.a: lib/mylib.o
    ar rcs $@ $^

lib/mylib.o: lib/mylib.c lib/mylib.h
    $(CC) $(CFLAGS) -c $< -o $@

$(TARGET): $(OBJ) lib/libmylib.a
    $(CC) $(LDFLAGS) $(OBJ) -Llib -lmylib -o $@

Session 4 — Git Advanced + Code Quality (1.5h)

Learning Objectives

  • Work with branches, resolve merge conflicts
  • Understand the pull/merge request workflow
  • Use clang-format, clang-tidy and sanitizers to improve code quality

4.1 — Git Quick Recap (5 min)

Format: Live demo

# Assume students know: init, add, commit, push, pull
git init demo && cd demo
echo "int main() { return 0; }" > main.c
git add main.c && git commit -m "Initial commit"
git log --oneline

4.2 — Branches (20 min)

Format: Live terminal demo

# Create and switch to a feature branch
git checkout -b feature/add-utils

# Make changes
cat > utils.c << 'EOF'
int add(int a, int b) { return a + b; }
EOF
git add utils.c && git commit -m "Add utils module"

# Show the branch graph
git log --oneline --graph --all

# Switch back to main
git checkout master
ls       # utils.c is gone!

# Make a change on master too
echo "// updated" >> main.c
git add main.c && git commit -m "Update main"

# Show divergence
git log --oneline --graph --all

Key teaching moment: branches are just pointers to commits. Draw the DAG on the board.

4.3 — Merging and Conflict Resolution (25 min)

Format: Deliberately create a conflict, resolve it live

# Setup: both branches modify the same line in main.c
git checkout master
echo 'int main() { return 0; }' > main.c
git add main.c && git commit -m "Reset main"

git checkout -b feature/return-one
sed -i 's/return 0/return 1/' main.c
git add main.c && git commit -m "Return 1"

git checkout master
sed -i 's/return 0/return 42/' main.c
git add main.c && git commit -m "Return 42"

# Now merge — conflict!
git merge feature/return-one
# CONFLICT!
Info

Wooclap Q8 — MCQ (3 min)

Show the conflicted file on the projector. Launch before editing.

<<<<<<< HEAD
int main() { return 42; }
=======
int main() { return 1; }
>>>>>>> feature/return-one

You want to keep the value from the feature branch. What should the resolved file contain?

  • A. int main() { return 42; } ← keep HEAD
  • B. int main() { return 1; } ← keep the feature branch
  • C. int main() { return 42; return 1; } ← keep both lines
  • D. The conflict markers must stay — Git resolves them automatically on commit

Expected: B. Debrief: emphasise that all conflict markers (<<<<, ====, >>>>) must be removed and the file must compile. Option D is a very common misconception. Option C is a fun wrong answer to discuss.

Walk through resolution step by step:

# 1. See the conflict markers
cat main.c
# <<<<<<< HEAD
# int main() { return 42; }
# =======
# int main() { return 1; }
# >>>>>>> feature/return-one

# 2. Edit to resolve (pick one, combine, or write something new)
vim main.c

# 3. Mark as resolved
git add main.c
git commit    # default merge commit message

# 4. Verify
git log --oneline --graph --all

Interactive exercise (5 min): Give students a conflict scenario on paper. Ask them to write the resolved file.

4.4 — Pull / Merge Requests (15 min)

Format: GitLab UI demo (screen share or screenshots)

Show the workflow:

  1. Create a branch locally, push it:

    git checkout -b feature/new-feature
    # make changes, commit
    git push -u origin feature/new-feature
  2. Open a Merge Request in GitLab:

    • Title, description, assignee, reviewer
    • Show the diff view
    • Show inline comments / code review
  3. CI pipeline runs on the MR

  4. Reviewer approves → merge → branch deleted

Key teaching moment: MRs are about code review and quality gates, not just merging.

Show a real example from a project if possible.

4.5 — Why Code Quality Matters: NASA’s “Power of 10” (5 min)

Format: Brief discussion before diving into tools

“Before we look at the tools, let’s talk about why code quality matters in your field.”

Introduce NASA JPL’s “Power of 10” rules (Gerard Holzmann, 2006) — 10 rules for writing reliable C in safety-critical embedded systems (Mars rovers, flight software).

Show 4 rules on the board (the most accessible for students):

#RuleWhy it matters
1No goto, setjmp, or longjmpKeeps control flow predictable and analyzable
2All loops must have a fixed upper boundPrevents infinite hangs — critical on hardware with no OS to kill your process
6Declare variables at the smallest possible scopeReduces bugs from stale or reused state
7Check the return value of all non-void functionsA failed malloc or fopen that goes unchecked can crash a satellite

“These aren’t academic guidelines — they’re enforced on code that runs 225 million km from the nearest debugger.”

Info

Wooclap Q9 — MCQ (2 min)

Show this code snippet on the projector.

int x;
printf("Uninitialized: %d\n", x);

Which NASA “Power of 10” rule does this code violate?

  • A. Rule 1 — no goto or setjmp (control flow)
  • B. Rule 2 — all loops must have a fixed upper bound
  • C. Rule 6 — declare variables at the smallest possible scope and initialise them
  • D. Rule 7 — check the return value of all non-void functions

Expected: C. Debrief: using an uninitialised variable is precisely the kind of latent bug that causes non-deterministic failures — exactly what Rule 6 (scope + initialisation discipline) prevents. clang-tidy reports this as cert-dcl03-c / uninitialized read.

Bridge to the tools: explain that clang-tidy and sanitizers can enforce many of these rules automatically. That’s what we’ll set up next.

4.6 — Code Formatting with clang-format (10 min)

Format: Live demo

cd session4/

# Show ugly code
cat ugly.c

# Format it
clang-format ugly.c          # prints to stdout
clang-format -i ugly.c       # in-place

# Show the config file
cat .clang-format

# Create one interactively
clang-format -style=llvm -dump-config > .clang-format

Show how to integrate with editors (brief mention) and how to add to Makefile:

format:
    clang-format -i src/*.c include/*.h

4.7 — Static Analysis with clang-tidy (10 min)

Format: Live demo

# Run on a file with issues
clang-tidy --quiet -checks='*' ugly.c --

# Show specific check categories:
# - bugprone-*: likely bugs
# - cert-*: CERT secure coding
# - readability-*: code clarity
# - modernize-*: modern C practices

# Use a config file
cat .clang-tidy

# Fix automatically
clang-tidy --fix ugly.c --
Info

Wooclap Q10 — Number (2 min)

Show ugly.c on the projector (full file). Launch before running clang-tidy.

How many warnings do you think clang-tidy will report on ugly.c?

(Students enter a number — the closest guess wins.)

Expected: varies (can be 20–100+ depending on checks enabled). Debrief: reveal the actual count, then contrast with gcc -Wall (typically 4–6 warnings). The point is not the exact number but the order-of-magnitude difference — clang-tidy catches whole categories of issues that the compiler ignores by design.

Show the comparison from the slides: gcc = 0 warnings, gcc -Wall = 6, clang-tidy = 88.

4.8 — Memory Checking (5 min)

Format: Quick live demo (covered more in TP)

# Compile with sanitizer
clang -W -Wall -g -fsanitize=address buggy.c -o buggy
./buggy
# → AddressSanitizer: heap-use-after-free
# Shows exact line number!

Course Summary

Time Distribution

SessionTopicDurationStyle
1Compilation pipeline1.5h80% live terminal, 20% discussion
2Makefile from scratch1.5h90% live coding, 10% explanation
3Static & dynamic libraries1.5h70% live terminal, 30% discussion
4Git advanced + code quality1.5h60% live terminal, 25% UI demo, 15% discussion

Mapping to Practicals (6h)

TPTopicBuilds on Session
TP1 (2h)Makefile: write from scratch for a multi-file project, use variables, inference rules, automatic dependenciesSessions 1–2
TP2 (2h)Libraries: build static + dynamic libs, multi-file archives, objdump inspection, library versioning with sonameSession 3
TP3 (2h)Advanced Git: branching, conflict resolution, stash, rebase, MR workflow; Code quality: clang-format, clang-tidy, AddressSanitizer, ValgrindSession 4

Key Pedagogical Principles

  1. Terminal first: Every concept is demonstrated live, not on slides
  2. Error-driven: Show errors first, then explain why they happen, then fix them
  3. Incremental complexity: Start simple, refactor toward the “right” solution
  4. Students predict: Before running a command, ask “what do you think will happen?”
  5. Real-world motivation: Reference Quake 3, system libraries, actual CI pipelines

Appendix — Demo Files

All files referenced in the sessions above. Copy them into the ~/lecture/ directory structure before the course.

Session 1 — Compilation Basics

session1/hello.c

#include <stdio.h>

int main(void)
{
    printf("Hello, toolchain!\n");
    return 0;
}

session1/util.h

#ifndef UTIL_H
#define UTIL_H

int compare(int a, int b);

#endif

session1/util.c

#include "util.h"

int compare(int a, int b)
{
    if (a == b) return 0;
    if (a < b)  return 1;
    return -1;
}

session1/main.c

#include "util.h"

#define N 20
#define M 35

int main(void)
{
    int ret_value = compare(N, M);
    return ret_value;
}

session1/a.h — Multiple inclusion demo (broken)

/* a.h — NO header guard on purpose */
const int g_variable = 50;

session1/b.h — Multiple inclusion demo (broken)

/* b.h — includes a.h */
#include "a.h"

int func(void)
{
    return g_variable;
}

session1/main_broken.c — Triggers redefinition error

#include "a.h"
#include "b.h"

int main(void)
{
    int var = g_variable;
    return var + func();
}

session1/a_fixed.h — With header guard

#ifndef A_H
#define A_H

const int g_variable = 50;

#endif

session1/b_fixed.h — With header guard

#ifndef B_H
#define B_H

#include "a_fixed.h"

int func(void)
{
    return g_variable;
}

#endif

session1/main_fixed.c — Compiles correctly

#include "a_fixed.h"
#include "b_fixed.h"

int main(void)
{
    int var = g_variable;
    return var + func();
}

session1/c.h — Cyclic inclusion demo (broken)

/* c.h — includes d.h, creating a cycle */
#include "d.h"
int g_var = 10;

int temp_c(void)
{
    return temp_d() + g_var;
}

session1/d.h — Cyclic inclusion demo (broken)

/* d.h — includes c.h, creating a cycle */
#include "c.h"

int temp_d(void)
{
    return temp_c() + g_var;
}

Session 2 — Makefile

session2/include/inout.h

#ifndef INOUT_H
#define INOUT_H

#define MAX_LINE 256

/* Read an array of integers from a text file (one per line).
   Returns the number of values read, or -1 on error. */
int  read_array(const char *filename, int *array, int max_size);

/* Write an array of integers to a text file (one per line).
   Returns 0 on success, -1 on error. */
int  write_array(const char *filename, const int *array, int size);

/* Print an array to stdout. */
void print_array(const int *array, int size);

#endif

session2/include/math_utils.h

#ifndef MATH_UTILS_H
#define MATH_UTILS_H

/* Sort an integer array in-place (selection sort). */
void sort_array(int *array, int size);

/* Return the minimum value in the array. */
int  array_min(const int *array, int size);

/* Return the maximum value in the array. */
int  array_max(const int *array, int size);

/* Return the sum of all elements. */
long array_sum(const int *array, int size);

#endif

session2/src/inout.c

#include <stdio.h>
#include <stdlib.h>
#include "inout.h"

int read_array(const char *filename, int *array, int max_size)
{
    FILE *fp = fopen(filename, "r");
    if (fp == NULL)
        return -1;

    int count = 0;
    char line[MAX_LINE];

    while (count < max_size && fgets(line, sizeof(line), fp) != NULL)
    {
        array[count] = atoi(line);
        count++;
    }

    fclose(fp);
    return count;
}

int write_array(const char *filename, const int *array, int size)
{
    FILE *fp = fopen(filename, "w");
    if (fp == NULL)
        return -1;

    for (int i = 0; i < size; i++)
        fprintf(fp, "%d\n", array[i]);

    fclose(fp);
    return 0;
}

void print_array(const int *array, int size)
{
    printf("[");
    for (int i = 0; i < size; i++)
    {
        if (i > 0) printf(", ");
        printf("%d", array[i]);
    }
    printf("]\n");
}

session2/src/math_utils.c

#include "math_utils.h"
#include <limits.h>

void sort_array(int *array, int size)
{
    for (int i = 0; i < size - 1; i++)
    {
        int min_idx = i;
        for (int j = i + 1; j < size; j++)
        {
            if (array[j] < array[min_idx])
                min_idx = j;
        }
        if (min_idx != i)
        {
            int tmp        = array[i];
            array[i]       = array[min_idx];
            array[min_idx] = tmp;
        }
    }
}

int array_min(const int *array, int size)
{
    int min = INT_MAX;
    for (int i = 0; i < size; i++)
    {
        if (array[i] < min)
            min = array[i];
    }
    return min;
}

int array_max(const int *array, int size)
{
    int max = INT_MIN;
    for (int i = 0; i < size; i++)
    {
        if (array[i] > max)
            max = array[i];
    }
    return max;
}

long array_sum(const int *array, int size)
{
    long sum = 0;
    for (int i = 0; i < size; i++)
        sum += array[i];
    return sum;
}

session2/src/main.c

#include <stdio.h>
#include "inout.h"
#include "math_utils.h"

#define MAX_VALUES 100

int main(void)
{
    int values[MAX_VALUES];

    int n = read_array("data.txt", values, MAX_VALUES);
    if (n < 0)
    {
        fprintf(stderr, "Error: could not read data.txt\n");
        return 1;
    }

    printf("Read %d values: ", n);
    print_array(values, n);

    printf("Min = %d\n", array_min(values, n));
    printf("Max = %d\n", array_max(values, n));
    printf("Sum = %ld\n", array_sum(values, n));

    sort_array(values, n);
    printf("Sorted:        ");
    print_array(values, n);

    write_array("sorted.txt", values, n);
    printf("Written to sorted.txt\n");

    return 0;
}

session2/data.txt

42
17
8
99
23
56
3
71

session2/Makefile.v0 — Empty starting point

# Makefile for session 2 — build this from scratch during the lecture
#
# Targets to implement:
#   default  — build the project executable
#   clean    — remove build artifacts
#
# Source files:
#   src/main.c        (uses inout.h, math_utils.h)
#   src/inout.c       (uses inout.h)
#   src/math_utils.c  (uses math_utils.h)

Session 3 — Libraries

session3/lib/mylib.h

#ifndef MYLIB_H
#define MYLIB_H

/* A small vector math library for demonstration purposes. */

typedef struct
{
    double x;
    double y;
} Vec2;

/* Create a vector. */
Vec2   vec2_create(double x, double y);

/* Add two vectors. */
Vec2   vec2_add(Vec2 a, Vec2 b);

/* Subtract b from a. */
Vec2   vec2_sub(Vec2 a, Vec2 b);

/* Dot product of two vectors. */
double vec2_dot(Vec2 a, Vec2 b);

/* Length (magnitude) of a vector. */
double vec2_length(Vec2 v);

/* Normalize a vector (return unit vector). */
Vec2   vec2_normalize(Vec2 v);

/* Print a vector to stdout. */
void   vec2_print(Vec2 v);

#endif

session3/lib/mylib.c

#include <stdio.h>
#include <math.h>
#include "mylib.h"

Vec2 vec2_create(double x, double y)
{
    Vec2 v = {x, y};
    return v;
}

Vec2 vec2_add(Vec2 a, Vec2 b)
{
    return vec2_create(a.x + b.x, a.y + b.y);
}

Vec2 vec2_sub(Vec2 a, Vec2 b)
{
    return vec2_create(a.x - b.x, a.y - b.y);
}

double vec2_dot(Vec2 a, Vec2 b)
{
    return a.x * b.x + a.y * b.y;
}

double vec2_length(Vec2 v)
{
    return sqrt(v.x * v.x + v.y * v.y);
}

Vec2 vec2_normalize(Vec2 v)
{
    double len = vec2_length(v);
    if (len == 0.0)
        return vec2_create(0.0, 0.0);
    return vec2_create(v.x / len, v.y / len);
}

void vec2_print(Vec2 v)
{
    printf("(%.2f, %.2f)\n", v.x, v.y);
}

session3/app/main.c

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

int main(void)
{
    Vec2 a = vec2_create(3.0, 4.0);
    Vec2 b = vec2_create(1.0, 2.0);

    printf("a = ");
    vec2_print(a);

    printf("b = ");
    vec2_print(b);

    Vec2 sum = vec2_add(a, b);
    printf("a + b = ");
    vec2_print(sum);

    printf("a . b = %.2f\n", vec2_dot(a, b));
    printf("|a|   = %.2f\n", vec2_length(a));

    Vec2 n = vec2_normalize(a);
    printf("norm(a) = ");
    vec2_print(n);
    printf("|norm(a)| = %.2f\n", vec2_length(n));

    return 0;
}

session3/Makefile

# Makefile for session 3 — library demo
# Students will extend this during the lecture

CC      = gcc
CFLAGS  = -W -Wall -Wextra -Ilib
LDFLAGS = -lm

TARGET  = app/program

default: $(TARGET)

$(TARGET): app/main.c
	$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)

.PHONY: clean
clean:
	rm -f $(TARGET) lib/*.o lib/*.a lib/*.so lib/*.dylib app/*.o

Session 4 — Git Advanced + Code Quality

session4/ugly.c

A deliberately poorly formatted file with multiple issues for clang-format and clang-tidy to catch.

#include <stdio.h>
#include<stdlib.h>
#include <string.h>

#define TAILLE 100

// compute the average
double    moyenne(int* tab,int n){
double s=0;int i;
    for(i=0;i<n;i++){
  s=s+tab[i];
        }
return s/n;
}

//find max
int maximum(int* t  ,  int    n)
{
int max=t[0];
for(int i=1;i<n;i++){
if(t[i]>max)max=t[i];
}
return max;
}

// identical to maximum but for minimum — violates DRY
int minimum(int* t,int n)
{
int min=t[0];
for(int i=1;i<n;i++){
if(t[i]<min)min=t[i];
}
return min;
}

char* concat(char* a,char* b){
char* result=malloc(strlen(a)+strlen(b)+1);
strcpy(result,a);
strcat(result,b);
return result;
}

void print_tab(int tab[],int n)
{
    int i;
printf("[");
for(i=0;i<n;i++){
if(i>0)printf(", ");
printf("%d",tab[i]);}
printf("]\n");
}

int main()
{
int tab[TAILLE];
int i;
for(i=0;i<10;i++)tab[i]=rand()%100;

printf("Array: ");
print_tab(tab,10);

printf("Average: %.2f\n",moyenne(tab,10));
printf("Max: %d\n",maximum(tab,10));
printf("Min: %d\n",minimum(tab,10));

char* msg=concat("Hello ","World!");
printf("%s\n",msg);
// memory leak: msg is never freed

int x;
printf("Uninitialized: %d\n",x);

return 0;
}

Issues for students to find:

  • Inconsistent spacing, braces, indentation everywhere
  • #include<stdlib.h> missing space
  • main() should be main(void) in C
  • concat() has a memory leak (never freed)
  • x is used uninitialized
  • min/max functions are near-duplicates (DRY violation)
  • Missing const on read-only pointer parameters

session4/buggy.c

Demonstrates memory errors caught by AddressSanitizer and Valgrind.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Bug 1: heap-use-after-free */
void use_after_free(void)
{
    char *buffer = malloc(16);
    strcpy(buffer, "hello");
    free(buffer);
    printf("After free: %s\n", buffer);  /* BUG: use after free */
}

/* Bug 2: heap-buffer-overflow */
void buffer_overflow(void)
{
    int *arr = malloc(5 * sizeof(int));
    for (int i = 0; i <= 5; i++)         /* BUG: off-by-one, writes arr[5] */
        arr[i] = i * 10;
    printf("arr[4] = %d\n", arr[4]);
    free(arr);
}

/* Bug 3: memory leak */
void leak(void)
{
    char *data = malloc(1024);
    strcpy(data, "this is leaked");
    printf("Leaked data: %s\n", data);
    /* BUG: no free(data) */
}

/* Bug 4: double free */
void double_free(void)
{
    int *p = malloc(sizeof(int));
    *p = 42;
    free(p);
    /* free(p);  — uncomment to demo double free (crashes immediately) */
}

/* Bug 5: stack-buffer-overflow */
void stack_overflow(void)
{
    int arr[10];
    for (int i = 0; i <= 10; i++)        /* BUG: writes arr[10] */
        arr[i] = i;
    printf("arr[9] = %d\n", arr[9]);
}

int main(void)
{
    printf("=== Bug 1: use-after-free ===\n");
    use_after_free();

    /* Uncomment one at a time — ASan stops at the first error */
    /*
    printf("=== Bug 2: heap-buffer-overflow ===\n");
    buffer_overflow();

    printf("=== Bug 3: memory leak ===\n");
    leak();

    printf("=== Bug 4: double free ===\n");
    double_free();

    printf("=== Bug 5: stack-buffer-overflow ===\n");
    stack_overflow();
    */

    return 0;
}

Usage during lecture:

# Each bug can be tested individually by uncommenting in main()
clang -W -Wall -g -fsanitize=address buggy.c -o buggy
./buggy
# ASan reports the exact line + stack trace

# For leak detection specifically:
clang -W -Wall -g -fsanitize=address -fsanitize=leak buggy.c -o buggy
./buggy

session4/.clang-format

---
Language: C
BasedOnStyle: LLVM
IndentWidth: 4
ColumnLimit: 100
BreakBeforeBraces: Allman
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: true
PointerAlignment: Right
SpaceBeforeParens: ControlStatements
...

session4/.clang-tidy

---
Checks: >
    -*,
    bugprone-*,
    cert-*,
    clang-analyzer-*,
    misc-*,
    performance-*,
    readability-braces-around-statements,
    readability-identifier-naming,
    readability-implicit-bool-conversion,
    readability-misleading-indentation
WarningsAsErrors: ''
HeaderFilterRegex: '.*'
CheckOptions:
    - key: readability-identifier-naming.FunctionCase
      value: lower_case
    - key: readability-identifier-naming.VariableCase
      value: lower_case
    - key: readability-identifier-naming.MacroDefinitionCase
      value: UPPER_CASE
...

Setup Script

You can use this script to create the full directory structure on your machine:

#!/usr/bin/env bash
set -euo pipefail

BASE=~/lecture

echo "Creating lecture demo files in $BASE..."
mkdir -p "$BASE"/{session1,session2/{src,include},session3/{lib,app},session4}

echo "Done. Now copy each file from the appendix into the corresponding location."
echo "Structure:"
find "$BASE" -type d | sort