Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

Finding mistakes a parser cannot see (semantic analysis)

After this lesson: I can find the mistakes a parser cannot.

50 min lesson11 commandsCompilers and computer architecture lab: gcc, flex, bison and RISC-V in one VM

A report that follows every rule

A stock report script reads: print total + step. The grammar is happy. Every token is fine.

But step only existed inside a loop that has already ended. A parser checks the shape of a sentence, not whether it makes sense. The next part of the compiler checks the meaning.

By the end of this lesson

Note

I can find the mistakes a parser cannot.

Key words

WordWhat it meansExample
Semantic analysischecking that a program makes sense
Declarationthe line that brings a name into beingint step
Scopethe part of a program where a name exists
Symbol tablethe compiler's list of names in scope
Typewhat kind of value something holdsint
Warninga message that does not stop the build

The cells students fill in are left blank in this preview.

The compiler keeps a list of names

As it walks the syntax tree, the compiler keeps a symbol table. Each { opens a new scope on top of the table. Each declaration adds a name to the top scope. Each } throws the top scope away.

To check a name, the compiler looks in the top scope, then the one below, and so on. A name found nowhere is undeclared. The Runs show where step lives in scope.c.

Go into the compiler folder

Command
cd ~/compiler

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Every line that names step

Command
grep -n "step" scope.c

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Worked example: the symbol table of scope.c

LineWhat happensNames in scope
4 and 5main declares unused and totalunused, total
6The loop opens a scope and declares iunused, total | i
7The loop body declares stepunused, total | i | step
9The loop ends: its scopes are thrown awayunused, total
10printf uses step: not foundunused, total

The cells students fill in are left blank in this preview.

Compile with no extra checks

Command
gcc -c scope.c

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Now ask for the common warnings

Command
gcc -Wall -c scope.c

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Every value has a type

The compiler also works out the type of every expression and checks it fits where it is used. square takes an int. The text "7" is not an int: in C it is the address of some characters, a char *.

The first Run makes types.c, a copy of hello.c with square("7"). The original is untouched. The second compiles it.

Make a copy with a wrong type

Command
sed 's/square(7)/square("7")/' hello.c > types.c

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Compile the copy

Command
gcc -Wall -c types.c

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Errors stop the build, warnings do not

The next copy, fmt.c, asks printf for two numbers and gives it one. gcc knows how printf reads its format, so it can count.

This time it gives a warning. The build goes on, and ls shows that fmt.o was written. After an error, no object file is written at all.

Make a copy with a missing number

Command
sed 's/is %d/is %d %d/' hello.c > fmt.c

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Compile it

Command
gcc -Wall -c fmt.c

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Was an object file written?

Command
ls fmt.o

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

A common mistake

Warning

Many people think a warning can be ignored because the program still builds. A warning is the compiler saying it found something that is legal but very likely wrong. fmt.c builds, then prints a number nobody passed. Read every warning, and treat it as a bug until you know why it is safe.

Python checks while the program runs

C checks names and types before the program runs: that is static checking. Python checks them only when the line runs, so the mistake shows up as an error at that moment.

Both Runs below start the program. Neither mistake is found until Python reaches it.

A name Python has never seen

Command
python3 -c "print(total)"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Two types that do not add

Command
python3 -c "print(1 + 'a')"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Going further

Extend your checker with a second list, unused. It reports every name that was declared and never used, with the line of its declaration.

Check it on PROGRAM with one extra line at the top, let spare = 1. Then say why gcc reports unused only when asked with -Wall, but always reports step.

About this lab and its source: Compilers and computer architecture lab: gcc, flex, bison and RISC-V in one VM

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.