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

  1. 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?
  2. The mismatched end program name 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?
  3. Suppose the original file had included implicit none from 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 none is not a style preference; it is a bug detector. The identical typo that silently returned 32 became 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.