Case Study 1: The Build That Won't Build
"The compiler is not your adversary. It is the first and cheapest colleague to review your code, and it never gets tired of doing so."
Executive Summary
Nothing in a compiled language is more common, or more discouraging to a newcomer, than a program that simply refuses to build. This case study takes a small, real-feeling Fortran utility that a colleague has handed you — a temperature converter that "used to work" — and repairs it from a dead stop to a clean, correct, reproducible build. The point is not the converter; it is the method. You will learn to read a compiler message and locate the failure in the compile–link–run cycle, to tell a compile-stage error from a link-stage error, and — most valuable of all — to recognize the moment when a program compiles and runs and is still wrong, and to make the compiler help you find out why. By the end you will have a diagnostic routine you can apply to any broken Fortran build you meet for the rest of your career.
Skills applied: verifying the toolchain (§2.1); reading and locating errors in the compile–link–run
cycle (§2.3); the early compiler flags and the development profile (§2.4); using implicit none to convert
a silent bug into a caught error (§2.6); the honesty discipline of computing the expected answer by hand
(§2.7, and the whole book's method).
Background
You have joined a research group, and a colleague sends you a single file, convert.f90, with a note: "This
converts Celsius to Fahrenheit — 100 degrees should give 212. It compiled on the old workstation but it
won't build for me anymore. Can you get it going?" Here is exactly what they sent, defects and all:
program convert
celsius = 100.0
fahrenheit = celcius * 9.0 / 5.0 + 32.0
print *, 'F =', fahrenheit
end program covert
It is four lines of logic and it is going to take us four distinct problems to make it right — which is realistic. Broken builds rarely have one cause; they have a stack of them, and you peel them off one at a time. Resist the urge to rewrite the whole thing from scratch (you will only reintroduce the bugs you can't see). Instead, work the cycle.
Phase 1 — Is There Even a Compiler?
Before blaming the code, confirm the tool. Your colleague's "it won't build for me" is a strong hint that the problem might be their environment, not the source. The first command at any unfamiliar machine is always the same:
$ gfortran --version
gfortran: command not found
There it is — for this teammate, there is no compiler on the PATH at all, which is why nothing builds.
This is not a code problem and no amount of staring at convert.f90 would have revealed it. Following §2.1
for their platform (say, sudo apt install gfortran on Ubuntu) fixes it, and now:
$ gfortran --version
GNU Fortran (Ubuntu 13.2.0-4ubuntu3) 13.2.0
A compiler, version 13 — comfortably past our baseline of 10. Now, and only now, does it make sense to look at the source.
The lesson of Phase 1: always separate "is the tool present and working?" from "is the code correct?" They are different questions with different fixes, and conflating them is how people waste an afternoon debugging source that was never the problem.
Phase 2 — The First Build: A Compile-Stage Error
Attempt the build with the development profile from §2.4, so the compiler tells you as much as it can:
$ gfortran -std=f2018 -Wall -g -fcheck=all convert.f90 -o convert
convert.f90:5:16:
5 | end program covert
| 1
Error: Expecting END PROGRAM statement for 'convert' at (1)
Read the message the way §2.3 taught you. It arrived during compilation — the source never became an
object file, so there is nothing to link and nothing to run yet. The compiler is telling you that the
program opened as program convert (line 1) but closed as end program covert (line 5), and the names do
not match. This is the compiler doing you a favor: the mismatched name is almost always a typo, and catching
it forces you to look. Fix line 5 to end program convert.
| Symptom | Stage | Cause | Fix |
|---|---|---|---|
Expecting END PROGRAM statement for 'convert' |
compile | opening and closing names differ (typo covert) |
make end program name match program name |
Phase 3 — It Compiles, It Runs, and It Is Wrong
Rebuild after fixing the name:
$ gfortran -std=f2018 -Wall -g -fcheck=all convert.f90 -o convert
$ ./convert
F = 32.000000
The build succeeds and the program runs. (List-directed print * chooses the exact spacing and digit count
itself, so the precise width of that number may differ on your compiler — what matters is the value, not
its layout.) A beginner celebrates here and moves on. You do not, because you know the answer already: your colleague told you 100 degrees Celsius should give 212 Fahrenheit, and a
moment's hand arithmetic confirms it — $100 \times 9 / 5 + 32 = 180 + 32 = 212$. The program printed 32.
It compiles, it runs, and it is wrong — the single most dangerous state a program can be in, because
nothing complained.
Where did 32 come from? Look at the arithmetic: the only way to get 32.0 out of that formula is for
celcius to be 0, because $0 \times 9 / 5 + 32 = 32$. So the variable feeding the formula is zero, not
one hundred — even though line 2 clearly sets celsius = 100.0. Look very closely at the names. Line 2
assigns to cel**s**ius; line 3 reads from cel**c**ius. They are different words. The s/c transposition
means line 3 is reading a different variable entirely — one that was never assigned, and so holds zero.
This is precisely the disaster §2.6 warned about, and it slipped through for exactly the reason given there:
the original file had no implicit none, so the misspelled celcius was silently invented as a fresh,
uninitialized real variable instead of being flagged. The program is a working demonstration of why silent
implicit typing is a menace: a one-letter slip produced a plausible-looking wrong number with no error at
all.
Phase 4 — Recruit the Compiler to Find the Typo
You could fix the typo by eye, having found it. But the professional move is to make the compiler
incapable of hiding this class of bug ever again, by adding the two words that turn implicit typing off. Add
implicit none and the explicit declarations it forces:
program convert
use, intrinsic :: iso_fortran_env, only: dp => real64
implicit none
real(dp) :: celsius, fahrenheit
celsius = 100.0_dp
fahrenheit = celcius * 9.0_dp / 5.0_dp + 32.0_dp ! typo still here — watch
print '(a, f0.2)', 'F = ', fahrenheit
end program convert
Now rebuild, and watch the compiler catch the very bug that was silently poisoning the result:
$ gfortran -std=f2018 -Wall -g -fcheck=all convert.f90 -o convert
convert.f90:6:15:
6 | fahrenheit = celcius * 9.0_dp / 5.0_dp + 32.0_dp ! typo still here — watch
| 1
Error: Symbol 'celcius' at (1) has no IMPLICIT type
That is the whole argument for implicit none in a single screen. The identical typo that produced a
silent wrong answer in Phase 3 now produces a loud compile-time error pointing at the exact line and
column. Fix the spelling to celsius:
fahrenheit = celsius * 9.0_dp / 5.0_dp + 32.0_dp
Phase 5 — A Clean, Correct, Reproducible Build
Rebuild the corrected program and verify:
$ gfortran -std=f2018 -Wall -g -fcheck=all convert.f90 -o convert
$ ./convert
F = 212.00
Confirm it by hand, as always: $100.0 \times 9.0 / 5.0 = 180.0$, and $180.0 + 32.0 = 212.0$, printed by
f0.2 as 212.00 with no leading blank (because we also upgraded print * to print '(a, f0.2)' for
clean output). The build is now correct. Two final professional touches finish the job:
- Record the build. Write the exact command —
gfortran -std=f2018 -Wall -g -fcheck=all convert.f90 -o convert— into a note beside the file, so the next person (possibly you, in six months) does not have to reconstruct it. This is the seed of the reproducibility practice of Chapter 37. - Keep the release profile in mind. For a converter it does not matter, but the habit does: the
production build drops the run-time checks —
gfortran -std=f2018 -Wall -O2 convert.f90 -o convert— and is what you would time or ship.
| Phase | Problem | Stage it surfaced | Repair |
|---|---|---|---|
| 1 | no compiler on PATH |
environment | install gfortran (§2.1) |
| 2 | end program name mismatch |
compile | match the names |
| 3 | wrong answer (32, not 212) |
run (caught by hand-check) | trace it to a zero-valued variable |
| 4 | silent typo celcius |
compile, after adding implicit none |
fix the spelling |
| 5 | messy output, no record | polish | formatted print, record the command |
Discussion Questions
- In Phase 3 the program compiled, ran, and gave the wrong answer. Which of the chapter's tools finally caught the bug — the compiler, the run-time checks, or the human hand-check — and what does that tell you about the limits of each?
- The mismatched
end programname in Phase 2 was, in this case, harmless (a typo in a name that is never used again). Why might the language designers have chosen to make it an error anyway rather than ignoring it? - Suppose the original file had included
implicit nonefrom the start. Which of the four problems would have been caught immediately, and which would still have required a human to notice the wrong answer?
Your Turn: Extensions
- Option A. Introduce a third defect into the corrected program that produces a link-stage error rather than a compile-stage error (hint: call a subroutine that does not exist), build it, and read the message. Confirm from the wording that it failed at the link stage, not the compile stage.
- Option B. Rewrite the converter to go the other way — Fahrenheit to Celsius — and hand-compute the expected output for an input of 212 before you run it. Did you get 100? (Watch that you use real division throughout.)
- Option C. Take a short program from a colleague, a textbook, or online that lacks
implicit none, add it, and see whether the compiler flags anything. Report what you found; a surprising fraction of casual Fortran in the wild harbors exactly the bug of Phase 3.
Key Takeaways
- Debugging a broken build is a staged process: confirm the tool, then fix compile-stage errors, then fix link-stage errors, then — never skip this — check the answer against a value you computed by hand.
- The compiler locates errors precisely, but only within its remit: it checks grammar and declared types, not your intent. A program that compiles cleanly can still be wrong, and only a hand-computed expected answer catches that.
implicit noneis not a style preference; it is a bug detector. The identical typo that silently returned32became a compile-time error the instant implicit typing was switched off.- A build is not finished when it runs. It is finished when it runs, gives the right answer, and its exact compile command is written down where the next person can find it.