> *"Any fool can write code that a computer can understand. Good programmers write code that humans can
Prerequisites
- 2
- 3
- 4
- 5
- 6
- 8
Learning Objectives
- Read fixed-form FORTRAN 77 source correctly, accounting for the column rules (labels in 1–5, continuation in 6, comment `C` in 1) that free-form Fortran abandoned.
- Explain what a `COMMON` block and `BLOCK DATA` unit do, why they are a global untyped memory overlay, and how a mismatched declaration silently corrupts data.
- Recognize `EQUIVALENCE` as deliberate memory aliasing, state the concrete hazards it creates, and name its modern replacements.
- Decode the legacy control-flow zoo — `GO TO`, computed `GOTO`, arithmetic `IF`, statement functions, and `ENTRY` — and map each onto the structured construct that replaced it.
- Interpret implicit typing (the `I`–`N` integer rule), `DATA` initialization, and Hollerith constants when you meet them in the wild.
- Read an unfamiliar ~150-line FORTRAN 77 program — the `PLATE` Jacobi relaxation kernel — without panic, reconstruct what it computes, and predict its output.
In This Chapter
- Overview
- Learning Paths
- 17.1 Fixed-Form Source: Why Column Seven Matters
- 17.2 COMMON Blocks and BLOCK DATA: Global State by Memory Overlay
- 17.3 EQUIVALENCE: Deliberate Memory Aliasing and Its Dangers
- 17.4 The Control-Flow Zoo: GO TO, Computed GOTO, Arithmetic IF, Statement Functions, ENTRY
- 17.5 Implicit Typing, DATA, and the Hollerith Fossil
- 17.6 Reading Old Code Without Panicking: A Guided Tour of PLATE
- Project Checkpoint
- Summary
- Spaced Review
- What's Next
Chapter 17: Reading FORTRAN 77 — Fixed-Form, COMMON Blocks, EQUIVALENCE, and the Archaeology of Old Code
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." — Martin Fowler, Refactoring: Improving the Design of Existing Code (1999)
Overview
Sooner or later a file with a .f extension lands on your desk. It was written before you were born, it
computes something the organization depends on and cannot afford to get wrong, and — this is the part that
makes people panic — it looks nothing like the Fortran you have learned so far. Its lines begin in the
seventh column. Its variables are named T, N, IJK, and are declared nowhere. Whole blocks of memory
are shared between routines by a mechanism called COMMON that no one has explained to you, two different
arrays occupy the same bytes on purpose, and the flow of control is stitched together with numbered
GO TOs that leap up and down the page. You could be forgiven for concluding that this is a different
language.
It is not. It is your language, forty years younger — FORTRAN 77, the classic dialect that most of the world's validated scientific code is still written in. This chapter opens Part IV, the part of the book that turns you from someone who can write modern Fortran into someone who can also read the old kind and modernize it — a skill that, as the Part IV introduction argues, will earn your keep in almost any organization sitting on a mountain of legacy code. And the first move in modernizing old code is never to change it. It is to read it: to reconstruct, calmly and systematically, what it does and why, before you touch a single line. That reading is what this chapter teaches.
We approach FORTRAN 77 the way an archaeologist approaches a dig — not with contempt for the people who came before, but with curiosity about the constraints they worked under and respect for what they built. Every strange feature you are about to meet was a reasonable answer to a real 1970s problem: no dynamic memory, punch cards with eighty columns, compilers that fit in a few kilobytes. Understanding why the old code looks the way it does is most of what it takes to read it fluently.
In this chapter, you will learn to:
- Read fixed-form source correctly — the column rules for labels, continuation, and comments that
govern every line of a
.ffile, and that trip up every newcomer exactly once. - Understand
COMMONandBLOCK DATA: how FORTRAN 77 shared state between routines by overlaying memory, and why the compiler could not protect you from a mismatch. - See
EQUIVALENCEfor what it is — two names deliberately pointing at the same bytes — and know the hazards that make it the single most dangerous construct in the language. - Decode the legacy control-flow zoo: plain
GO TO, the computedGOTO, the arithmeticIF, the statement function, andENTRY, each paired with the modern construct that retired it. - Handle implicit typing (the rule that a name's first letter sets its type),
DATAstatements, and the fossil that is the Hollerith constant. - Put it all together by reading a real ~150-line FORTRAN 77 program end to end — the
PLATEheat-plate relaxation kernel that you will modernize, step by step, in the next two chapters.
Learning Paths
How to read this chapter by track. - 🔧 Legacy ("I inherited old code") — this entire chapter is written for you; read every section closely, and treat §17.6 and the Project Checkpoint as the reason you bought the book. This is your home turf. - 📖 Standard — read straight through. Even if you never write a
COMMONblock, you will read one, and knowing the whole language — including its past — is what "knowing Fortran" means. - 🔬 Scientist ("my Python is too slow") — you can defer this part until you actually meet a.ffile, but skim §17.2 and §17.6 now: the numerical kernel you will one day inherit looks exactly like thePLATEcode toured here. - ⚡ HPC ("I need parallel code") — the tuned kernels at the heart of many HPC codes are legacy FORTRAN 77. Read §17.1–17.3 so the fixed-form,COMMON-heavy sources you will profile in Part VII hold no surprises.
Throughout, the recurring theme of this part sits underneath everything: legacy code is not a burden, it is an inheritance. The strangeness is not incompetence; it is history. Read it as history and it becomes legible.
17.1 Fixed-Form Source: Why Column Seven Matters
The first shock of an old .f file is visual: the code does not start at the left margin. It starts one
tab-stop in, and if you retype it flush left it stops compiling. This is not a style convention you may
ignore. It is fixed-form source, the layout FORTRAN inherited from the punch card, and the columns
carry meaning.
Definition (fixed-form source). The source layout of FORTRAN 77 and earlier, in which each line is divided into fixed character columns with reserved meanings: columns 1–5 hold an optional statement label (a number), column 6 is the continuation marker, columns 7–72 hold the statement itself, and columns 73–80 are ignored (historically the punch card's sequence number). A
Cor*in column 1 makes the entire line a comment. This is the counterpart to the free-form source introduced with Fortran 90 (and used everywhere else in this book), where statements may begin in any column. A.f(or.for) file is fixed-form; a.f90file is free-form.
Picture the eighty columns of a punch card, because that is literally where these rules come from:
Column: 1 2 3 4 5 6 7 ... 72 73 ... 80
|----- label -----| | continuation |----- statement -----| |sequence|
1 2 3 4 5 6 7 ...................72 73......80
C a C or * in column 1 makes the whole line a comment
X = 1.0 <- statement starts in column 7 (six leading blanks)
100 Y = X + 1.0 <- label 100 sits in columns 1-5; statement in 7+
& + 2.0 <- nonblank in column 6 continues the PREVIOUS line
Read that ruler carefully, because four rules fall out of it and they explain almost every "why won't this compile" moment you will have with old code.
Rule 1 — statements live in columns 7 through 72. The six leading blanks you see on every ordinary line are not decoration; columns 1–6 are reserved, so the statement body must begin at column 7. That is the "one tab-stop in" you noticed.
Rule 2 — a label goes in columns 1–5. A statement label is a number (1 to 99999) that names a line so
that a GO TO, a DO, or a FORMAT can refer to it. It sits left of the statement, in the label field.
Labels need not be ordered or contiguous; they are just names that happen to be numbers.
Rule 3 — column 6 is the continuation column. If a statement is too long for one line (remember,
column 72 is the wall), you break it and put any nonblank character in column 6 of the next line to say
"this continues the statement above." The choice of character is free — old codes use &, +, ., or
the digits 1, 2, 3 to number a long continuation. A blank or a zero in column 6 means "this is a new
statement," which is the normal case.
Rule 4 — a C or * in column 1 is a comment. There is no ! end-of-line comment in strict FORTRAN
77 (that came with Fortran 90); a comment is a whole line flagged in column 1. You will see rivers of
C-comments in old code, often in a banner style.
Here is a small, complete, authentic fixed-form program. Note the leading blanks, the label 10, and
that everything stays inside column 72:
C AREA -- circle area, in genuine fixed-form FORTRAN 77.
PROGRAM AREA
PI = 3.14159
R = 2.0
A = PI * R * R
WRITE (*,10) A
10 FORMAT (1X, 'AREA = ', F8.4)
STOP
END
$ gfortran -std=legacy area.f -o area && ./area
AREA = 12.5664
The -std=legacy flag is how you tell gfortran "this is old code; accept its obsolete features and read
it as fixed-form." (The compiler also switches to fixed-form automatically from the .f extension.) We
hand-check the output: $\pi r^2 = 3.14159 \times 4 = 12.56636$, which F8.4 prints as 12.5664,
right-justified in eight columns, after the one leading space from 1X. Nothing about the computation
is strange; only the layout is.
📜 From History. Why columns at all? Because FORTRAN was designed to be punched onto cards, one statement per card, and the card reader delivered each card as eighty fixed columns. Columns 73–80 were left for a sequence number so that if you dropped your card deck on the floor, you could sort it back into order. That is not a metaphor — it is why column 72 is the end of the statement to this day. When you meet a bizarre constraint in old code, ask what physical object it was shaped by; the answer is usually illuminating and occasionally poignant.
🔧 Modern vs Legacy: The same program, free-form, is what you have been writing since Chapter 2:
fortran ! Modern, free-form: no column rules, no labels, an end-of-line comment with ! program area implicit none real :: pi = 3.14159, r = 2.0 print '(a, f8.4)', 'area = ', pi * r * r end program areaThe statement starts where you like, the comment rides on the same line with
!, and there is no label and noFORMATline to jump to. Free-form did not add power — the two programs compute the same thing — it removed a set of tripwires. Modern Fortran is a modern language, and this is the first and most visible piece of evidence.⚠️ Common Pitfall: the invisible column-6 character. The nastiest fixed-form bug is a stray character that lands in column 6 by accident (a misplaced tab, a comment marker one column off), turning a line you meant as a fresh statement into a continuation of the line above — or vice versa. If an old file gives a baffling syntax error, count columns with your editor's ruler before you suspect anything cleverer. Nine times in ten it is a column, not a concept.
🔄 Check Your Understanding
- In fixed-form source, what is in columns 1–5, what is in column 6, and where does the statement body begin?
- A line reads
* DIFFUSION TERMwith the*in column 6 (not column 1). Is it a comment? What is it? - Why can a strict FORTRAN 77 statement never extend past column 72?
Answers
1. Columns 1–5 hold an optional statement *label* (a number); column 6 is the *continuation* marker (nonblank = "continue the previous line"); the statement body occupies columns 7–72. 2. It is **not** a comment — a comment needs the `C` or `*` in **column 1**. With the `*` in column 6, this line is a *continuation* of the statement above it, and `DIFFUSION TERM` will be spliced onto that statement, almost certainly causing a syntax error. This is exactly the column-6 trap. 3. Because the source came from eighty-column punch cards, with columns 73–80 reserved for a sequence number; the standard fixed the statement field at columns 7–72, and anything past 72 is ignored.17.2 COMMON Blocks and BLOCK DATA: Global State by Memory Overlay
FORTRAN 77 had no modules (Chapter 8),
no derived types, and — crucially — no way for two subroutines to share a variable by naming it. If
STEP and OUTPUT both needed to see the same temperature array, the language offered exactly one tool:
the COMMON block, which let them share the same memory rather than the same name.
Definition (
COMMON). ACOMMONblock is a named (or unnamed "blank") region of memory that several program units may each declare and thereby share. WrittenCOMMON /name/ list-of-variables, it establishes a run of storage; every program unit that declares aCOMMONblock of the same name is given access to that same storage, associating its listed variables with the block by position, not by name. It is FORTRAN 77's mechanism for global, shared state — the ancestor of the module variable, and far more dangerous.
The phrase to burn in is by position, not by name. When two routines both say COMMON /GRID/ ..., the
compiler lays their variables over the same bytes in the order listed. The names need not match. The
types need not match. Nothing is checked.
C Routine A's view of the shared block.
SUBROUTINE STEP
COMMON /STATE/ TEMP, FLUX, NCELL
REAL TEMP, FLUX
INTEGER NCELL
C ... uses TEMP, FLUX, NCELL ...
END
C Routine B's view of the SAME bytes -- different names, and no one checks.
SUBROUTINE OUTPUT
COMMON /STATE/ A, B, M
REAL A, B
INTEGER M
C ... A is TEMP, B is FLUX, M is NCELL -- if you got the order right.
END
Both routines refer to the same three words of memory. In STEP the first word is called TEMP; in
OUTPUT the very same word is called A. As long as every routine lists the block in the same order with
the same types, this works — it is how every large FORTRAN 77 program shared its data. But the safety net
is entirely in the programmer's head.
🐛 Find the Bug. Two routines declare the same
COMMONblock. What goes wrong, and why does the compiler stay silent?```fortran SUBROUTINE SOLVE COMMON /BLK/ X, Y, N ! REAL, REAL, INTEGER ... END
SUBROUTINE PRINTIT COMMON /BLK/ X, N, Y ! REAL, INTEGER, REAL <-- reordered! ... END```
Diagnosis
The two declarations disagree about the order of the block. InSOLVEthe second word is the realYand the third is the integerN; inPRINTITthe second word is read as the integerNand the third as the realY. SoPRINTITreads the bits of a real number and interprets them as an integer, and vice versa — producing nonsense, with no crash and no warning. FORTRAN 77 associatesCOMMONby position, and the compiler compiles each routine separately, so it never sees the two declarations side by side to notice they conflict. This is not a contrived bug; it is the single most common way largeCOMMON-based codes were silently corrupted, and it is why keeping the declarations in sync spawned theINCLUDEhabit below.
Because the same block had to be declared identically in file after file, programmers kept the declaration
in one file and pasted it in everywhere with an INCLUDE line (INCLUDE 'state.inc'), a raw textual
insertion. The result was FORTRAN 77's actual mechanism for building large programs: one global pool of
untyped memory, held together by copied-and-pasted text.
BLOCK DATA: initializing the global pool
There is one thing a plain COMMON declaration cannot do: give its variables initial values. A DATA
statement (§17.5) may not initialize a COMMON variable inside an ordinary subroutine — the standard
forbids it, because it would be ambiguous which routine "owns" the initialization. FORTRAN 77 solved this
with a dedicated, bodiless program unit whose only job is to hold those initial values.
C BLOCK DATA: the only place a named COMMON block may be initialized.
BLOCK DATA SETCON
COMMON /PARAMS/ TOL, MAXIT
DATA TOL /1.0E-5/, MAXIT /1000/
END
A BLOCK DATA unit has no PROGRAM, SUBROUTINE, or FUNCTION header and contains no executable
statements — only COMMON, type, and DATA declarations. It runs no code; it simply tells the linker
"stamp these initial values into the /PARAMS/ block before the program starts." You will see one, off in
a corner of an old code, doing exactly this and nothing else.
🔗 Connection. You already met the cure for all of this in Chapter 8. The module replaces the
COMMONblock with one typed, compiler-checked declaration that every unituses by name; the module variable replaces the shared word of memory; a moduleparameteror an initializer replaces theBLOCK DATAunit. WhereCOMMONassociates by position and checks nothing,useassociates by name and checks everything. ConvertingCOMMONto a module is step three of the eight-step modernization recipe in Chapter 18, and it is the step that stops a code from being able to corrupt itself.🔧 Modern vs Legacy: The
/PARAMS/block and itsBLOCK DATA, done the modern way, collapse into a handful of lines with no overlay and no separate initialization unit:
fortran ! Modern: a module owns the parameters, typed and initialized in one place. module params use, intrinsic :: iso_fortran_env, only: dp => real64 implicit none real(dp), parameter :: tol = 1.0e-5_dp integer, parameter :: maxit = 1000 end module paramsThere is no
BLOCK DATA, because the initialization lives with the declaration; there is no positional association, becauseuse params, only: tol, maxitimports the entities by name; and if a routine misusesmaxitas a real, the compiler says so. One authoritative definition, checked everywhere it is used.🚪 Threshold Concept —
COMMONis one global pool of untyped memory. The mental model that unlocks every old code is this: in FORTRAN 77, a large program is a single shared expanse of bytes that any routine may reach into and reinterpret according to whatever it happened to declare, with no compiler anywhere able to check that two routines agree about what lives there. Modern Fortran replaced that with components that each own their state behind a checked interface. Once you see aCOMMONblock as a window onto shared raw memory — not as a tidy list of variables — the behavior of legacy code stops being mysterious. Every strange aliasing bug, every "how did that variable change?", traces back to this one design.
17.3 EQUIVALENCE: Deliberate Memory Aliasing and Its Dangers
If COMMON shares memory between routines, EQUIVALENCE shares it within one — it tells the compiler
that two or more variables in the same routine occupy the same storage, on purpose.
Definition (
EQUIVALENCE). AnEQUIVALENCEstatement, writtenEQUIVALENCE (a, b), declares that the named variables (or array elements) share the same memory location: writing one changes the other, because they are the same bytes. It was used chiefly to save memory — a scarce resource in the 1970s — by letting arrays that were never live at the same time reuse one buffer, and to reinterpret a region of storage as a different shape or type (an array as a flat vector, say). It is FORTRAN 77's tool for deliberate aliasing — the very thing modern Fortran works hard to forbid, because forbidding it is what makes Fortran fast.
There are two flavors, and they map onto two very different intentions.
Memory reuse. Two large scratch arrays that are never needed simultaneously can be laid over the same buffer to halve the program's footprint:
C WORK and SCRATCH share one buffer -- only legal because the code
C never needs both alive at once. Get that wrong and you have a bug.
DIMENSION WORK(1000), SCRATCH(1000)
EQUIVALENCE (WORK(1), SCRATCH(1))
Reshaping and reinterpreting. An array can be aliased as a flat vector so it can be swept with a single loop, or (worse) a real can be overlaid on an integer to inspect its bits:
C Alias the 2-D field T as a 1-D vector TFLAT for a single-loop scan.
DIMENSION T(5,5), TFLAT(25)
EQUIVALENCE (T(1,1), TFLAT(1))
C Now TFLAT(K) IS T stored column-major: TFLAT(1)=T(1,1), TFLAT(2)=T(2,1)...
That second use is genuinely handy — and it depends on knowing Fortran's column-major storage order
(the lesson of Chapter 5): TFLAT(1) is T(1,1),
TFLAT(2) is T(2,1), and so the flat index marches down the first column, then the next. If you
mis-picture the layout, the alias silently reads the wrong cells.
Why is EQUIVALENCE the most dangerous construct in the language? Three reasons, each concrete:
- A write through one name changes the other, invisibly. Code that reads
SCRATCHafter some distant routine wroteWORKgets whateverWORKleft behind — a data dependency with no syntactic trace. The bug lives in the absence of a connection you can see. - Type overlays are non-portable and treacherous. Overlaying a
REALon anINTEGERto read the bits assumes a particular size and representation; move to a machine where aREALand anINTEGERdiffer in size and the alias corrupts memory. - It defeats the optimizer. The compiler's freedom to reorder loads and stores rests on knowing that
distinct names are distinct memory.
EQUIVALENCEbreaks that promise by hand, so aliased code cannot be optimized as aggressively — you pay in speed for the bytes you saved.
🔧 Modern vs Legacy: Every legitimate use of
EQUIVALENCEhas a safe, typed modern replacement, and you will tabulate them in Chapter 19:
Legacy intent ( EQUIVALENCE)Modern replacement Reuse one buffer for two scratch arrays Two allocatablearrays —allocateanddeallocatearound useAlias a 2-D array as a flat vector reshape, or an array-pointer withcontiguousReinterpret a REAL's bits as anINTEGERthe intrinsic transfer(source, mold)— explicit and typedThe
transferintrinsic deserves the spotlight: whereEQUIVALENCEsilently overlays bytes and hopes,transfer(x, 0)says in the code "reinterpret the bits ofxas the type of0," checked and portable. The intent that was implicit and dangerous becomes explicit and safe.⚠️ Common Pitfall — do not "fix"
EQUIVALENCEby deleting it. When you meet anEQUIVALENCEin old code, resist the urge to strip it out on sight. First find out which intent it serves — memory reuse, reshaping, or bit reinterpretation — because each maps to a different modern construct, and choosing the wrong one changes the program's behavior. Reading correctly precedes rewriting correctly; that is the whole ethic of this part.
🔄 Check Your Understanding
- In one sentence, what does
EQUIVALENCE (A, B)assert aboutAandB? - Given
DIMENSION T(5,5), TFLAT(25)withEQUIVALENCE (T(1,1), TFLAT(1)), which element ofTisTFLAT(7)? (Fortran is column-major.) - Which modern intrinsic replaces the "reinterpret these bits as another type" use of
EQUIVALENCE?
Answers
1. That `A` and `B` occupy the *same* memory — they are two names for one storage location, so writing one writes the other. 2. `TFLAT(7)` is `T(2,2)`. Column-major means the flat index runs down column 1 first (`TFLAT(1..5)` = `T(1,1)..T(5,1)`), then down column 2 (`TFLAT(6..10)` = `T(1,2)..T(5,2)`), so index 7 is the second element of column 2, `T(2,2)`. 3. `transfer(source, mold)` — it reinterprets the bits of `source` as the type of `mold`, explicitly and portably.17.4 The Control-Flow Zoo: GO TO, Computed GOTO, Arithmetic IF, Statement Functions, ENTRY
Modern Fortran's block constructs — if … end if, do … end do, select case — did not exist in early
FORTRAN. Control flow was built almost entirely from labels and jumps, and reading old code means
recognizing the handful of jump-based idioms and mentally translating each into the structured construct
that replaced it (the ones you learned in
Chapter 4).
The plain GO TO. The workhorse. GO TO 100 transfers control to the statement labeled 100,
anywhere in the routine. A loop was a GO TO back to the top with an IF to break out; a two-way branch
was an IF guarding a GO TO around the else-part. Read a cluster of GO TOs by finding the labels they
target and asking "is this jumping back (a loop) or forward (an escape or a branch)?"
C Legacy: a convergence loop built from a label and a GO TO.
ITER = 0
100 CONTINUE
ITER = ITER + 1
C ... do a sweep, compute DMAX ...
IF (DMAX .LT. TOL) GO TO 200
IF (ITER .LT. MAXIT) GO TO 100
200 CONTINUE
That is a do while wearing a disguise. The modern equivalent says the same thing without a single label:
! Modern: the same loop, structured. No labels, no GO TO.
iter = 0
do
iter = iter + 1
! ... do a sweep, compute dmax ...
if (dmax < tol) exit
if (iter >= maxit) exit
end do
The computed GOTO. A multi-way branch driven by an integer index.
Definition (computed
GOTO). A control statement of the formGO TO (L1, L2, L3), Kthat transfers control to the label in the list selected by the integerK: toL1ifKis 1,L2ifKis 2, and so on. IfKis less than 1 or greater than the number of labels, control falls through to the next statement. It is FORTRAN 77's integer-indexed multi-way branch — the ancestor ofselect case.
C Legacy: dispatch on an integer boundary-condition code.
GO TO (10, 20, 30), IBC
10 CONTINUE
C ... fixed-temperature (Dirichlet) boundary ...
GO TO 99
20 CONTINUE
C ... insulated (Neumann) boundary ...
GO TO 99
30 CONTINUE
C ... periodic boundary ...
99 CONTINUE
The modern reading is a select case, which is safer in every way — it labels its cases meaningfully, it
cannot fall through by accident, and it handles the out-of-range value explicitly:
! Modern: the same dispatch, self-documenting and bounds-safe.
select case (ibc)
case (1) ! fixed-temperature (Dirichlet)
! ...
case (2) ! insulated (Neumann)
! ...
case (3) ! periodic
! ...
case default
error stop 'unknown boundary code'
end select
The arithmetic IF. The oldest branch of all, and the strangest to modern eyes: a three-way jump on
the sign of an expression.
C Legacy: IF (expr) neg, zero, pos -- jump on the sign of expr.
IF (DMAX - TOL) 200, 100, 100
C negative (DMAX<TOL) -> label 200 (converged);
C zero or positive -> label 100 (keep iterating).
IF (e) L1, L2, L3 jumps to L1 when e is negative, L2 when e is zero, and L3 when e is
positive. It reads as a puzzle until you decode it as "branch on sign(e)," and then it is just a clumsy
select case on three outcomes. The arithmetic IF was declared obsolescent by the standard long ago;
modern code writes an ordinary if (dmax < tol) then … else … end if.
The statement function. A one-line function defined inline, in the declaration section of a routine.
Definition (statement function). A statement function is a single-statement function definition written among a routine's declarations, of the form
name(args) = expression. It is local to the routine that defines it, its arguments are dummy names, and it is expanded like a formula wherever it is called. It was the lightweight way to name a small recurring expression — the ancestor of the modern internal procedure — and it is now obsolescent.
C Legacy: a statement function for the 4-neighbour Jacobi average
C (this is the exact one in the PLATE kernel of Section 17.6).
AVG(TL,TR,TB,TA) = 0.25D0 * (TL + TR + TB + TA)
C ... later, in the sweep ...
TNEW(I,J) = AVG(T(I-1,J), T(I+1,J), T(I,J-1), T(I,J+1))
A statement function must sit after the declarations and before the first executable statement, and it
can be only one expression long. When you meet one, read it as a named formula. The modern replacement is
an internal procedure (a containsed function, from
Chapter 6) — which may span many lines, carries
intent and a checked interface, and can be pure:
! Modern: the statement function promoted to a pure internal function.
contains
pure function avg(w, e, s, n) result(m)
real(dp), intent(in) :: w, e, s, n
real(dp) :: m
m = 0.25_dp * (w + e + s + n)
end function avg
ENTRY. The rarest fossil, and worth recognizing precisely because it confuses people. An ENTRY
statement gives a subprogram a second name and entry point, sharing the routine's body and local
variables. A single SUBROUTINE might have three ENTRY points, so that CALL INIT, CALL STEP, and
CALL FINISH all landed in different parts of one tangled routine that shared state through its locals.
C Legacy: one routine, multiple entry points sharing local state.
SUBROUTINE SOLVER
SAVE COUNT
COUNT = 0
RETURN
ENTRY BUMP
COUNT = COUNT + 1
RETURN
END
The modern reading is almost always "this should have been a module with several procedures sharing a
private module variable" — exactly the step_counter pattern from
Chapter 8. ENTRY is obsolescent; you
will rarely write it, but you must be able to recognize it, because a routine with multiple entry points
does not behave like the single-entry procedures you are used to.
🐍 Python Comparison. Every construct in this section is a jump — a
gotoin spirit — and Python, tellingly, has nogotoat all, on the same principle Fortran itself adopted in 1990: structured constructs (if,for,while,match) express every sane control pattern more clearly than labels and jumps do. Reading old FORTRAN is partly an exercise in recovering the structured intent that the jumps obscure. When you find yourself drawing arrows betweenGO TOs and their labels to see the loops, you are reverse-engineering thedoloop the author could not yet write.🔗 Connection. Chapter 4 built the modern replacements for every construct here, and its history boxes pointed forward to this moment: the
do … exitthat replaces theGO TOloop, theselect casethat replaces the computedGOTOand the arithmeticIF. Reading legacy control flow is mostly the inverse map — recognizing the old idiom and naming the new construct it becomes.
🔄 Check Your Understanding
GO TO (30, 10, 20), K— to which label does control go whenKis 2? What happens whenKis 5?- Rewrite the arithmetic
IFstatementIF (X - Y) 5, 6, 6as a modernifconstruct. - Where in a routine must a statement function be defined, and what modern construct replaces it?
Answers
1. When `K` is 2 it goes to the *second* label in the list, `10`. When `K` is 5 — out of range for a three-label list — control *falls through* to the statement after the computed `GOTO`. 2. `if (x < y) then` → (the `5` branch) `else` → (the combined `6` branch, since both zero and positive go to `6`) `end if`. In words: one branch for `x < y`, the other for `x >= y`. 3. A statement function must appear after the declaration statements and before the first executable statement of its routine. It is replaced by an **internal procedure** — a function defined after `contains`, with `intent` on its arguments and a checked interface.17.5 Implicit Typing, DATA, and the Hollerith Fossil
Look again at the AREA program in §17.1 and you will notice something that would fail to compile in every
other chapter of this book: not one variable is declared. PI, R, and A appear from nowhere and are
real numbers. This is implicit typing, and it is the default you have been shielded from by
implicit none since Chapter 2.
Definition (implicit typing). The FORTRAN convention by which an undeclared variable's type is determined by the first letter of its name: names beginning with
I,J,K,L,M, orNareINTEGER; names beginning with any other letter (A–H,O–Z) areREAL. The rule is often summarized "IthroughNare integers" — a mnemonic reinforced by the first two letters of INteger. A variable simply comes into existence, typed by its initial, the first time it is used. Modern Fortran disables this withimplicit none(Chapter 2), forcing an explicit declaration for every variable.
The rule is not arbitrary — it matches mathematical habit, where i, j, k, m, n are the classic
loop counters and array sizes, and everything else is a real quantity. In a short, careful routine it even
reads cleanly. But it is a trap with two sharp edges.
The first edge is the silent typo. If you write TEMP in one place and TEPM in another, implicit
typing does not complain that TEPM is undeclared — it creates a brand-new real variable called TEPM,
initialized to garbage, and your temperature quietly fails to propagate. There is no error, only a wrong
answer. This is the single strongest argument for implicit none, and it is why the modern language made
the shield available.
The second edge is accidental integer arithmetic. Because I–N names are integers, a quantity you
meant as a real — a step count reused as a divisor, say — can be silently integer, and integer division
truncates (Chapter 3). A name
like N holding what should be a fraction gives 1/N = 0, not 0.01, with no warning.
C Legacy hazard: with implicit typing, N is INTEGER (starts with N),
C so 1/N is INTEGER DIVISION -> 0, not 0.01. Nothing warns you.
N = 100
DT = 1.0 / N
C DT is 1.0/100 done as real (1.0 is real), so this one is OK -- but
C STEP = 1 / N would be integer 1/100 = 0. The danger is real and quiet.
⚠️ Common Pitfall — the implicit
SAVE, hiding nearby. Implicit typing has a cousin that catches people even in modern code. In a FORTRAN 77 routine, a local variable given an initial value in its declaration was implicitlySAVEd — it kept its value between calls. Old code leaned on this; a routine would initialize a counter "once" and rely on it persisting. When you read (or modernize) such a routine, that persistence is part of its behavior, and dropping it silently changes the result. We dissect the modern version of this trap — the accidental implicitsaveinside a procedure — in Chapter 13; here, simply flag every "initialized once" local as stateful.
DATA statements. FORTRAN 77 initialized variables not in the declaration (that came with Fortran 90)
but with a separate DATA statement, which pairs a list of variables with a list of constants.
C DATA: initialize variables with a parallel list of constant values.
DATA TTOP, TBOT /0.0, 100.0/
DATA (EDGE(I), I=1,3) /3*0.0/ ! implied-DO; "3*0.0" = three 0.0's
Two idioms in that snippet repay knowing. The values between slashes correspond position-by-position to
the variables before them. The 3*0.0 is a repeat count — shorthand for 0.0, 0.0, 0.0 — and the
(EDGE(I), I=1,3) is an implied-DO, a compact loop that lists EDGE(1), EDGE(2), EDGE(3). Modern
Fortran folds all of this into the declaration: real :: ttop = 0.0, tbot = 100.0 and
real :: edge(3) = 0.0.
Hollerith constants. Finally, the oldest fossil you will encounter — the way FORTRAN represented text
before it had a character type at all (the CHARACTER type arrived only with FORTRAN 77 itself, so
pre-77 code, and much early-77 code, predates it).
A Hollerith constant is written
nHtext— a countn, the letterH, then exactlyncharacters — naming a string of text by its length.5HHELLOis the five charactersHELLO. Named for Herman Hollerith, whose punch-card tabulator seeded IBM, it stored text in numeric orINTEGERvariables and appeared mainly inFORMATstatements andDATAlists.
C Legacy: a FORMAT using a Hollerith constant for its label text.
10 FORMAT (1X, 13HTEMPERATURE =, F8.2)
C Modern equivalent uses a quoted character string:
C print '(a, f8.2)', 'temperature =', t
You will almost never need to write Hollerith, but you must be able to read it: when you see 13H
followed by exactly thirteen characters wedged into a FORMAT, that is a text label, nothing more.
Miscount the characters and the following comma vanishes into the string — a classic way old FORMATs
broke.
🔧 Modern vs Legacy: The whole of this section, in one comparison. Legacy: undeclared variables typed by their first letter, initialized by a distant
DATAstatement, with text smuggled through Hollerith counts. Modern:implicit none, every variable declared with an explicit type and kind, initialized where it is declared, text in quotedcharacterstrings. The modern version is longer by a few declarations and safer by an entire category of bug. That trade — a little more typing for a lot more checking — is the through-line of modernization, and the reason modern Fortran is a modern language.
17.6 Reading Old Code Without Panicking: A Guided Tour of PLATE
Everything so far has been vocabulary. Now we read a whole program — the way you will read the real thing —
using a method you can apply to any legacy source. Meet PLATE, a compact FORTRAN 77 kernel (about a
hundred lines, in four program units) that solves for the steady-state temperature of a square metal
plate. It is the code you will modernize in
Chapter 18 and tabulate in
Chapter 19; read it well here and those chapters become
edits, not excavations.
First, the physics in one breath, because reading numerical code is far easier when you know what it is trying to compute. A time-dependent temperature field obeys the heat equation $\frac{\partial u}{\partial t} = \alpha \nabla^2 u$ (the equation behind this book's whole project). When the plate reaches steady state, nothing changes with time, so $\frac{\partial u}{\partial t} = 0$ and the equation collapses to Laplace's equation, $\nabla^2 u = 0$. On a grid, the five-point stencil turns that into a beautifully simple rule: each interior temperature equals the average of its four neighbours,
$$ T_{i,j} = \tfrac{1}{4}\left( T_{i-1,j} + T_{i+1,j} + T_{i,j-1} + T_{i,j+1} \right). $$
Jacobi relaxation enforces that rule by brute repetition: replace every interior point with the average of its neighbours from the previous sweep, over and over, until the field stops changing. (The full finite-difference story — and why "from the previous sweep" matters — is Chapter 24's; here we only need to recognize the pattern in the code.)
Here is the whole program — about a hundred lines across four program units. Do not read it line by line
yet; first just look at its shape. (This is the exact code you will modernize in Chapter 18; it is in
this chapter's code/plate-legacy.f.)
C=====================================================================
C PLATE -- steady-state temperature of a square plate.
C 2-D Laplace equation, solved by Jacobi relaxation.
C FORTRAN 77, fixed-form. *** LEGACY CODE -- do not imitate. ***
C Compile: gfortran -std=legacy plate-legacy.f -o plate-legacy
C=====================================================================
PROGRAM PLATE
IMPLICIT DOUBLE PRECISION (A-H,O-Z)
PARAMETER (NMAX = 21)
COMMON /GRID/ T(NMAX,NMAX), N
C Grid size (a 4x4 test plate; interior is 2x2).
N = 4
CALL SETBC
CALL RELAX
CALL OUTPT
STOP
END
C
SUBROUTINE SETBC
C Boundary conditions: top edge hot, other three edges cold.
IMPLICIT DOUBLE PRECISION (A-H,O-Z)
PARAMETER (NMAX = 21)
COMMON /GRID/ T(NMAX,NMAX), N
DATA THOT, TCOLD / 100.0D0, 0.0D0 /
DO 20 J = 1, N
DO 10 I = 1, N
T(I,J) = 0.0D0
10 CONTINUE
20 CONTINUE
DO 30 I = 1, N
T(I,1) = TCOLD
T(I,N) = TCOLD
30 CONTINUE
DO 40 J = 1, N
T(N,J) = TCOLD
40 CONTINUE
DO 50 J = 1, N
T(1,J) = THOT
50 CONTINUE
RETURN
END
C
SUBROUTINE RELAX
C Jacobi relaxation with a GOTO convergence loop.
IMPLICIT DOUBLE PRECISION (A-H,O-Z)
PARAMETER (NMAX = 21)
COMMON /GRID/ T(NMAX,NMAX), N
DOUBLE PRECISION TNEW(NMAX,NMAX)
DATA TOL, MAXIT / 1.0D-6, 1000 /
C Statement function: four-neighbour average.
AVG(TL,TR,TB,TA) = 0.25D0 * (TL + TR + TB + TA)
ITER = 0
60 CONTINUE
ITER = ITER + 1
DMAX = 0.0D0
C One Jacobi sweep: TNEW from the OLD T (interior only).
DO 80 J = 2, N-1
DO 70 I = 2, N-1
TNEW(I,J) = AVG(T(I-1,J), T(I+1,J), T(I,J-1), T(I,J+1))
DIFF = ABS(TNEW(I,J) - T(I,J))
IF (DIFF .GT. DMAX) DMAX = DIFF
70 CONTINUE
80 CONTINUE
DO 100 J = 2, N-1
DO 90 I = 2, N-1
T(I,J) = TNEW(I,J)
90 CONTINUE
100 CONTINUE
IF (DMAX .GT. TOL .AND. ITER .LT. MAXIT) GO TO 60
WRITE (6,'(A,I5,A)') ' converged in ', ITER, ' iterations'
RETURN
END
C
SUBROUTINE OUTPT
C Print the interior temperatures, one grid row per line.
IMPLICIT DOUBLE PRECISION (A-H,O-Z)
PARAMETER (NMAX = 21)
COMMON /GRID/ T(NMAX,NMAX), N
DO 120 I = 2, N-1
WRITE (6,'(20F9.4)') (T(I,J), J = 2, N-1)
120 CONTINUE
RETURN
END
The five-step reading method
Now the method — the same one whether the code is 150 lines or 150,000.
Step 1: read the comments and the shape, not the logic. Before decoding any statement, harvest what the
author told you. The banner says "steady-state temperature… Jacobi relaxation." The routine names —
SETBC, RELAX, OUTPT — announce a three-phase structure: set the boundary conditions, relax to steady
state, output. In thirty seconds, without reading one executable line, you know this program imposes edge
temperatures on a plate, relaxes the interior, and prints the result. That is most of the battle.
Step 2: find the shared state. In a COMMON-based code, the data model lives in the COMMON blocks.
Here there is just one, /GRID/ T(NMAX,NMAX), N — the temperature field T and the working grid size N
— and it is declared identically in all four program units. That consistency is what makes the sharing
safe, and it is the first thing you would check if you suspected a bug. Two details reward a second look.
NMAX is a PARAMETER fixing the array's storage size (21) while N is the working size (4) — the
classic FORTRAN 77 "over-dimension a fixed array" pattern, since there were no allocatable arrays. And
IMPLICIT DOUBLE PRECISION (A-H,O-Z) at the top of every unit means all those undeclared reals (T,
THOT, TOL, DMAX, …) are double precision — a common way old codes got 64-bit arithmetic without
declaring a kind. Note also what is not here: no BLOCK DATA, because the constants TOL, MAXIT,
THOT, TCOLD are local variables initialized by DATA inside their routines — BLOCK DATA is only
needed to initialize COMMON.
Step 3: identify the loops and the exit. In RELAX, the label 60 with the GO TO 60 near the bottom
is a loop, and its single exit is the line IF (DMAX .GT. TOL .AND. ITER .LT. MAXIT) GO TO 60: it jumps
back only while not yet converged and iterations remain, so control falls through to the WRITE when
either condition fails. Mentally, that is do … if (dmax <= tol .or. iter >= maxit) exit … end do. The
labeled DO 80 / 70 CONTINUE and DO 100 / 90 CONTINUE nests are ordinary counted loops: the first is the
sweep over the interior (2 to N-1, skipping the fixed edges), the second copies the new values back.
Step 4: decode the kernel. The statement function AVG names the four-neighbour average, and the line
TNEW(I,J) = AVG(T(I-1,J), T(I+1,J), T(I,J-1), T(I,J+1)) is exactly the Laplace stencil above — the new
field TNEW computed from the old field T, which is what makes it Jacobi and not in-place Gauss–Seidel.
The separate TNEW array, copied back into T after the whole sweep, is the tell. The DIFF/DMAX lines
track the largest change in the sweep, the convergence measure. You have now found the mathematical heart of
the program, and it is four lines long.
Step 5: trace one concrete step to confirm. Never trust a reading you have not tested against a number.
SETBC sets the top row (T(1,J)) hot at 100, the other three edges cold at 0, and zeros the interior.
On this 4×4 grid the interior is just the four cells (2,2), (2,3), (3,2), (3,3). Trace the first sweep by
hand: cell (2,2) averages its old neighbours T(1,2)=100 (hot edge), T(3,2)=0, T(2,1)=0, T(2,3)=0,
giving $\tfrac14(100) = 25$; cell (3,2), one row further from the heat, averages four zeros and stays 0.
So after sweep 1 the largest change is 25 — a number you can verify with a calculator.
Where does it converge? By symmetry the two upper interior cells are equal (call the value $a$) and the two
lower cells equal ($b$). Steady state means each equals the average of its neighbours:
$a = \tfrac14(100 + b + 0 + a)$ and $b = \tfrac14(a + 0 + 0 + b)$, i.e. $3a - b = 100$ and $a = 3b$.
Substituting gives $8b = 100$, so $b = 12.5$ and $a = 37.5$ — every value a dyadic fraction, exact in double
precision. OUTPT prints only the interior (2 to N-1), so you see a 2×2 block, and the loop reports the
sweep count on the way:
$ gfortran -std=legacy plate-legacy.f -o plate-legacy && ./plate-legacy
converged in 25 iterations
37.5000 37.5000
12.5000 12.5000
That output is not a lucky guess — it is the steady state you just derived by hand, which is precisely why
this small, symmetric problem was chosen for a teaching kernel: a reader can confirm the archaeology against
an exact answer. (Why 25 sweeps? From an all-zero interior the max change is 25 after sweep 1 and then
halves every sweep — a spectral radius of exactly $\tfrac12$ on this grid — so it first drops below the
$10^{-6}$ tolerance on sweep 25.) When you inherit a real code with no such luxury, Step 5 becomes "find or
build a case whose answer you know" — the seed of the regression test you will write in
Chapter 18.
💡 Intuition: Reading legacy code is not linear reading; it is triage. Comments and structure first, data model second, control flow third, the numerical kernel fourth, a concrete trace last. Panic comes from trying to understand line 1 before line 2; calm comes from understanding the shape before any line at all. You did not need to know FORTRAN 77 to find the four-line heart of
PLATE— you needed a method.
🔄 Check Your Understanding
- Which array in
RELAXis the give-away that the method is Jacobi rather than in-place Gauss–Seidel, and why? - In
RELAX, which single statement forms the exit of the convergence loop, and which construct would replace the whole60 … GO TO 60structure in modern Fortran? OUTPTloopsIandJfrom2toN-1. On the 4×4 grid, how many numbers does it print, and why does it skip the first and last rows and columns?
Answers
1. `TNEW` — the *separate new-field* array. Jacobi computes every interior point from the neighbours' **old** values in `T`, writing results into `TNEW`, then copies `TNEW` back into `T` after the whole sweep; Gauss–Seidel would update `T` in place, using new values as soon as they appear, and would need no `TNEW`. The `T(I,J) = TNEW(I,J)` copy-back loop confirms it is Jacobi. 2. The line `IF (DMAX .GT. TOL .AND. ITER .LT. MAXIT) GO TO 60` is the loop: it repeats *while* not converged and iterations remain, and falls through when either fails. Modern Fortran replaces the `60 CONTINUE … GO TO 60` structure with a bare `do … end do` and an `if (dmax <= tol .or. iter >= maxit) exit`. 3. It prints the `2 × 2 = 4` interior values (a 2×2 block). It skips the first and last rows and columns because those are the *fixed boundary* temperatures set by `SETBC` — they are inputs, not part of the computed solution, so only the relaxed interior is reported.Project Checkpoint
Your running heat solver takes a side quest here, one that spans Chapters 17–19. You have just met the
FORTRAN 77 PLATE kernel; over the next two chapters you will modernize it into clean Fortran and tabulate
every move you make. This chapter's checkpoint is the one that makes the rest possible: read PLATE
until you own it.
Concretely, do three things and commit them beside your heat-solver/ project in a new folder,
heat-solver/legacy/:
-
Save and compile the kernel. Type the
PLATElisting from §17.6 intoplate-legacy.f(or take it from this chapter'scode/directory) and build it withgfortran -std=legacy plate-legacy.f -o plate-legacy. Confirm it printsconverged in 25 iterationsand the interior37.5000 / 12.5000. Getting a legacy build working — the-std=legacyflag, the fixed-form.fextension — is itself a skill you will reuse on every old code you inherit. -
Annotate it. In a copy of the file, add a modern
!-style comment beside each legacy construct naming what it is and what will replace it:IMPLICIT DOUBLE PRECISION→ "implicit none+real(dp)declarations";COMMON /GRID/ T, N→ "agridmodule (or an assumed-shape argument)"; the60 … GO TO 60loop → "do … exit";AVG(...) =→ "internalpurefunction";DATA THOT, TCOLD→ "declaration initializers"; the over-dimensionedT(NMAX,NMAX)→ "anallocatablearray sized toN". (Notice what is absent:PLATEuses noEQUIVALENCE— a reminder that the modernization recipe is a menu, not a fixed march; you apply the steps a given code actually needs.) This annotated file is your migration map for Chapter 18. -
Pin the reference output. Run the kernel and save its output to
legacy/expected.txt. That file is the regression reference: in Chapter 18 you will modernize the code one step at a time and, after each step, check that its output still matchesexpected.txt. Because every value here is a dyadic fraction (37.5,12.5) computed with no rounding, this migration can be checked bit-for-bit — a case where "still correct" is completely unambiguous.
That is the whole checkpoint: no new solver code, but a legacy kernel you can read, build, and trust, plus the annotated map and reference output that turn Chapter 18 from a rewrite into a safe, checkable refactor. This is the ethic of the part made practical — never rewrite what you can refactor, and never refactor what you have not first read and pinned down.
Summary
Reading FORTRAN 77 is a bounded skill: a fixed vocabulary of old constructs, each with a known modern counterpart, plus a method for approaching an unfamiliar file calmly.
| Legacy construct | What it is | Modern counterpart |
|---|---|---|
| fixed-form source | columns 1–5 label, 6 continuation, 7–72 statement, C/* comment |
free-form; ! comments |
COMMON /blk/ |
shared memory, associated by position, unchecked | a module with typed variables (Ch. 8) |
BLOCK DATA |
the only unit that may initialize named COMMON |
a module parameter or initializer |
INCLUDE 'x.inc' |
raw textual paste to keep COMMON in sync |
use module, only: … |
EQUIVALENCE |
two names share the same bytes (aliasing) | allocatable, reshape, transfer |
GO TO label |
unconditional jump | do/exit/cycle, if |
computed GOTO |
GO TO (l1,l2,…), k — jump by integer index |
select case |
arithmetic IF |
IF (e) l1,l2,l3 — jump on sign(e) |
if … else if … end if |
| statement function | one-line inline function in the declarations | internal pure function (Ch. 6) |
ENTRY |
a second entry point sharing a routine's body/state | separate module procedures |
| implicit typing | first letter sets the type; I–N are INTEGER |
implicit none + explicit declarations (Ch. 2) |
DATA statement |
initialize via parallel variable/value lists | declaration initializer |
Hollerith nHtext |
text named by length, pre-CHARACTER |
quoted character strings |
The two rules worth memorizing. (1) In fixed-form, columns matter: label 1–5, continuation 6,
statement 7–72, comment C in column 1 — count columns before you suspect anything deeper. (2) A
COMMON block is shared raw memory associated by position, so a reordered or retyped declaration
corrupts data silently; the module fixed this by associating by name and checking every access.
The reading method (any legacy file). Comments and structure first → the COMMON data model second →
loops and their exits third → the numerical kernel fourth → one hand-traced concrete step last. Understand
the shape before any single line.
Compile flag introduced: -std=legacy — accept obsolete features and read fixed-form; the way you
build an inherited .f file.
Project piece added: not solver code, but the read, built, annotated, and output-pinned PLATE kernel —
the legacy side quest you will modernize in Chapter 18.
Spaced Review
Two threads reach back into the foundations, because reading legacy control flow and legacy state is the mirror image of the modern constructs you already know — Chapter 4 (control flow) and Chapter 8 (modules).
-
(Ch. 4) The
PLATEkernel's convergence loop is100 CONTINUE … IF (DMAX .LT. TOL) GO TO 200 … GO TO 100. Write the equivalent modern loop usingdo,exit, and anif, and say why the modern version needs no statement labels.
Answer
`do; iter = iter + 1; … ; if (dmax < tol) exit; if (iter >= maxit) exit; end do`. The modern `do … end do` construct *is* the loop — `end do` marks where to jump back to and `exit` marks where to leave — so there is nothing for a label to name. Labels existed only because the old `GO TO` had to name a target line; structured constructs make the target implicit. -
(Ch. 4) A legacy routine dispatches with
GO TO (10, 20, 30), MODE. Which modern construct replaces a computedGOTO, and what does it add that the computedGOTOlacked?
Answer
`select case (mode)` with `case (1)`, `case (2)`, `case (3)`, and a `case default`. It adds a named, self-documenting branch per value, a `default` for out-of-range values (the computed `GOTO` silently falls through), and no possibility of accidental fall-through — the safety points from Chapter 4's `select case` section. -
(Ch. 8)
PLATEshares its grid throughCOMMON /GRID/ T(NMAX,NMAX), N, declared identically in all four routines. How does a module make this both safer and shorter, and what replaces the requirement to repeat the declaration everywhere?
Answer
A module declares `t` and `n` *once*, with explicit types the compiler checks; every routine gains access with a single `use grid` (or `use grid, only: …`), associating the entities **by name**, not by position. There is no repeated `COMMON` line to keep in sync across the four units, so the whole class of "reordered/retyped declaration corrupts data" bugs disappears — the payoff of Chapter 8's module concept. -
(Ch. 8) In FORTRAN 77 a named
COMMONblock could only be initialized in aBLOCK DATAunit. Where does that initialization go in modern Fortran, and why is no separate unit needed?
Answer
It goes right where the entity is declared in the module — as a `parameter` (for constants like `tol` and `maxit`) or a declaration initializer. No separate unit is needed because a module variable's declaration and its initial value live together and are owned unambiguously by the module, removing the ambiguity that forced FORTRAN 77 to invent `BLOCK DATA`.
What's Next
You can now read FORTRAN 77 — the columns, the COMMON blocks, the aliasing, the jumps, the implicit
types — and you have a real kernel, PLATE, that you have read down to its four-line Jacobi heart and
pinned to a known-correct output. That reading was the hard part; what follows is the payoff.
Chapter 18 takes PLATE through an eight-step
modernization recipe — implicit none, free-form, COMMON to module, intent, assumed-shape arrays,
structured control, retiring EQUIVALENCE (where a code has it), and adding error handling — transforming
it into clean modern Fortran without changing the answer it computes, checked at every step against the
reference output you saved here. Legacy code is not a burden; it is an inheritance — and you are about to
improve the engineering while preserving the science. Let's modernize it.