Chapter 13 — Key Takeaways (Error Handling, Debugging, and Defensive Programming)

A one-page reference. The chapter's core: ask whether each operation worked, halt deliberately when it did not, arm the compiler to catch what you missed, and check your own beliefs.

Error handling: ask, then decide

Construct What it does
allocate(a(n), stat=s, errmsg=msg) Guard allocation: s = 0 on success, nonzero (processor-dependent) on failure; msg gets the text. Without stat, a failure aborts.
deallocate(a, stat=s) Same guard on release.
open(..., iostat=ios, iomsg=msg) The I/O version (Ch. 7). Same shape — this is what stat generalizes.
if (s /= 0) … The only portable test. Never compare stat/iostat to a magic number.

Halting: stop vs error stop

stop error stop [code]
Termination normal error
Default exit code 0 (success) nonzero (gfortran 1)
With code N exit N exit N
Parallel (coarrays) orderly immediate, all images
Meaning to the shell "all well" "something failed — notice me"
  • Exit codes are read with echo $?, and taken mod 256 on Unix — keep codes in 1–255.
  • Use distinct codes per failure class (e.g. 2 bad config, 3 allocation, 4 out-of-range) so scripts branch on $? without parsing text.
  • Diagnostics go to error_unit (standard error), not standard output.

Compile flags introduced (see Appendix C)

Flag Effect When
-fcheck=all Runtime checks: bounds, allocation, pointers Development — remove for production
-fbacktrace Stack trace on a crash (pair with -g) Cheap; keep on
-ffpe-trap=invalid,zero,overflow Halt at the op that makes NaN/Inf Development (and often production)
-finit-real=snan Fill uninitialized reals with signaling NaN Development — trips -ffpe-trap=invalid
-fsanitize=address / -fsanitize=undefined Fast in-binary memory / UB checks Development
-ftrapv Trap signed integer overflow instead of wrapping Development

Never trap underflow/inexact — they fire constantly in correct code.

Debuggers: which tool for which fault

Symptom Tool Moves
"Why is this value wrong?" gdb break file:line · run · print v · next/step · bt · continue
Leak / read of freed memory / dangling valgrind (--leak-check=full) or -fsanitize=address run under it; slow (~10–30×), use small cases
Out-of-bounds, unallocated, bad pointer -fcheck=all just recompile; names the line
NaN/Inf born mid-run -ffpe-trap + -fbacktrace crashes at the raising line

Defensive programming

  • Assertion — a hand-written assert(cond, msg) that error stops when cond is false. For the "impossible" (a bug in your logic), not for expected errors (those use stat/iostat).
subroutine assert(condition, message)
  logical,          intent(in) :: condition
  character(len=*), intent(in) :: message
  if (.not. condition) then
     write(error_unit, '(a)') 'ASSERTION FAILED: ' // message
     error stop 1
  end if
end subroutine assert
  • Precondition: check arguments/config at the boundary, before computing.
  • Postcondition: check the result is sane (e.g. no NaN).
  • Make checks removable: logical, parameter :: checking = .true. + if (checking) call assert(...); flip to .false. and the optimizer deletes them. (Same idea as C's NDEBUG, Python's -O.)
  • NaN test: if (x /= x) is .true. only for NaN (or use ieee_is_nan, Ch. 20).

The usual suspects (name → fix)

Suspect Fix
Uninitialized variable (holds garbage, not 0) Initialize before use; -Wall, -finit-real=snan
Out-of-bounds index (1-based! Ch. 5) -fcheck=bounds
Integer overflow (int32 wraps past ~2.1e9) Use integer(int64) for counts/products
Precision loss No == on reals (use a tolerance); kind suffixes 1.0_dp; watch integer division 1/2 = 0 (Ch. 3)
Implicit save integer :: n = 0 in a procedure is set once and persists — assign in an executable statement for a per-call reset; write save explicitly if you want persistence

Numbers and rules worth memorizing

  • huge(0_int32) = 2,147,483,647; + 1 wraps to −2,147,483,648 (two's-complement).
  • stat/iostat: 0 = success, nonzero = failure; end-of-file is negative (iostat_end).
  • Exit code: 0 = success, nonzero = failure, mod 256.
  • An initializer in a declaration ⇒ implicit save — the single most surprising Fortran trap.

The ethic

Fail early, fail loud, fail with a message that names what was expected and what was seen. A solver that stops with 'dt must be positive, got -0.5' is worth infinitely more than one that runs for a day and writes NaN.

Project piece added this chapter

validate_config(nx, ny, dt, alpha) — reject a non-positive grid, dt, or alpha at the boundary; a stat-guarded field allocation; distinct error stop codes for config (2) vs allocation (3) failure; build with -fcheck=all -g while developing (strip -fcheck for production, Ch. 30).

Key terms

error stop · stat/errmsg (allocation) · floating-point exception trap · assertion · defensive programming · (used from Ch. 7: iostat/iomsg; from Ch. 11: dangling pointer).