Lengthy argument lists in subroutines and user-defined functions can occur
as modularised programs grow ever larger, requiring more and more information
to be passed between program units. The COMMON
block, a piece
of shared memory in the computer, is another method for passing information
between program units. Data stored in a COMMON
block is not
passed between program units via argument lists, but through the
COMMON
statement near the beginning of each program unit.
There are two types of COMMON
blocks: blank and
named. A program may contain only one blank
COMMON
block but any number of named COMMON
blocks.
Every COMMON
block must be declared in every program unit in which
the information stored therein is needed. In addition, the unique
blank COMMON
block must be declared in the main program.
The blank COMMON
block is set up with the statement
COMMON variable-list
and the named COMMON
block is declared by
COMMON /name/ variable-list
where the name between the forward slashes is the name of the
named COMMON
block.
Every subroutine or user-defined function that uses data stored in the
COMMON
block, blank or named, must have a similar statement
to those above. The variable names do not need to match between program
units but it is vital that their types and the order in which they appear
in the list are identical.
Consider the following program fragment:
PROGRAM MAIN INTEGER A REAL F,R,X,Y COMMON R,A,F A = -14 R = 99.9 F = 0.2 CALL SUB(X,Y) … END SUBROUTINE SUB(P,Q) INTEGER I REAL A,B,P,Q COMMON A,I,B … END
In this example, a blank COMMON
block holds three values: a
REAL
number, an INTEGER
number, and another
REAL
number. Memory is shared in the COMMON
block in the following way:
Main Program | Common Memory Storage | Subroutine |
---|---|---|
R |
99.9 | A |
A |
-14 | I |
F |
0.2 | B |
Note that the variable names for each memory area differ between the main program and the subroutine, but that the number and type of variables are the same as is the order in which they are listed.
Named COMMON
blocks are used in much the same manner. Note
that a variable cannot appear in more than one named COMMON
block in a program unit.
Blank COMMON
blocks must be declared in the main program.
It is not necessary to declare named COMMON
blocks in the
main program unless they are used there.
Blank COMMON
blocks need not be the same length in
different program units. However, a named COMMON
block must
be exactly the same length wherever it appears. This means that
some knowledge about how the computer stores information is necessary.
That is, the programmer must know how much storage each variable or
array takes in order to ensure that the named COMMON
blocks
are the same length.
Variables in blank COMMON
blocks may be initialised with
READ
or assignment statements but not with a
DATA
statement. The same restrictions apply to named
COMMON
blocks with one important difference: named
COMMON
blocks may be initialised in a special nonexecutable
subroutine called a BLOCK DATA
subprogram.
When a subroutine or function is exited, local variables become
undefined. The same thing may happen with the variables stored in
named COMMON
blocks. Therefore, it is possible to
SAVE
an entire named COMMON
block (but not
individual variables in the block) in a procedure with the command
SAVE /named common block1/, /named common block2/, …, /named common blockn/
Variables in a blank COMMON
block never become undefined since
the it is declared in the main program. Similarly, if a named
COMMON
block is declared in the main program, then it is
unecessary to use the SAVE
command in other program units.
Note that you cannot use the SAVE
command on a blank
COMMON
block.
A BLOCK DATA
subprogram consists of the
BLOCK DATA
statement, any necessary type declarations, a
list of the named COMMON
blocks and their variables, and one or
more DATA
statements initialising one or more of the variables
appearing in the COMMON
blocks. Its sole purpose is to
initialise the values in named COMMON
blocks.
BLOCK DATA SETUP INTEGER A,B,C REAL I,J,K,L COMMON /AREA1/ A,B,C COMMON /AREA2/ I,J,K,L DATA A,B,C,I,J,K,L/0,1,2,10.0,-20.0,30.0,-40.0/ END
During compilation and linking, if one of the modules containing a procedure
is omitted, the linker will almost certainly return an error but this will
not happen if it's the BLOCK DATA
subprogram that's missing.
To avoid this possibility, the name of the BLOCK DATA
subprogram unit should be included in an EXTERNAL
statement in
some or all of the procedures which contain the named COMMON
blocks initialised by the BLOCK DATA
subprogram.
The general form of the statement is
EXTERNAL ename1, ename2, …, enamen
where ename is the name of an external function, subroutine,
BLOCK DATA
subprogram or dummy procedure in an argument list.
The use of COMMON
blocks is discouraged unless there are
extremely large amounts of data to be passed between program units. The
reason for this is simple: it weakens modularity. Variables in
COMMON
blocks are global in nature and when one
program unit alters a variable in this shared memory area, then it affects
all of the other program units which also use this shared area, often with
unexpected results. The debugging process becomes that much harder.
The EQUIVALENCE
statement causes two or more items (arrays or
variables) to occupy the same memory space. In the early days of
FORTRAN, when computer memory was measured in kilobytes, this was a valuable
technique to make the most efficient use of very limited memory. Today,
computers routinely have gigabytes of memory, but EQUIVALENCE
still has a use, as the case study below demonstrates.
The general form of the statement is
EQUIVALENCE (variable-list1), (variable-list2), …, (variable-listn)
where there are one or more variable-lists. The
variable-list contains two or more variables or arrays or mixture
of the two. The EQUIVALENCE
statement occurs near the
beginning of a program unit, along with type declarations and other
specification statements.
Some years ago, one of the authors wrote a FORTRAN 77 program to model the dynamics of planetary satellites by numerical integration of the equations of motion. The orbits were to be fitted to observational data, so it was necessary to integrate not only the coordinates of the satellites, but also the partial derivatives of the coordinates with respect to the initial position and velocity of each satellite, in order to calculate the variational equations.
Thus the (one-dimensional) array of quantities being integrated in the case of N satellites were:
x1, y1, z1, x2, y2, z2, …, xN, yN, zN, ∂x1/∂ξ1, ∂y1/∂ξ1, ∂z1/∂ξ1, …, ∂x1/∂η1, ∂y1/∂η1, ∂z1/∂η1, …, ∂zN/∂ζ'N
where ξ1 denotes the initial value of x1, and so forth.
In the main program, the number of coordinates and free parameters were defined
using the PARAMETER
statement as NCOORD
and
NPARAM
respectively. For a two-satellite system, there are 6
coordinates and 12 components of the initial position and velocity vectors,
hence:
PARAMETER(NCOORD=6, NPARAM=12)
Then the number of partial derivatives was defined as the product of those two quantities,
PARAMETER(NPTLS=NCOORD*NPARAM)
and the total number of equations to be integrated was the number of coordinates plus the number of partial derivatives,
PARAMETER(NEQNS=NCOORD+NPTLS)
The one-dimensional array of quantities being integrated was declared as
DOUBLE PRECISION P(NEQNS)
and the two-dimensional array of partial derivatives was declared as
DOUBLE PRECISION PTLS(NCOORD,NPARAM)
Finally, EQUIVALENCE
was used to make these two array share
the same memory:
EQUIVALENCE (PTLS,P(NCOORD+1))
In the case of two satellites, this results in the following memory layout:
P(1) |
= | x1 | ||
P(2) |
= | y1 | ||
P(3) |
= | z1 | ||
P(4) |
= | x2 | ||
P(5) |
= | y2 | ||
P(6) |
= | z2 | ||
P(7) |
= | PTLS(1,1) |
= | ∂x1/∂ξ1 |
P(8) |
= | PTLS(2,1) |
= | ∂y1/∂ξ1 |
P(9) |
= | PTLS(3,1) |
= | ∂x1/∂ξ1 |
P(10) |
= | PTLS(4,1) |
= | ∂x2/∂ξ1 |
P(11) |
= | PTLS(5,1) |
= | ∂y2/∂ξ1 |
P(12) |
= | PTLS(6,1) |
= | ∂z2/∂ξ1 |
P(13) |
= | PTLS(1,2) |
= | ∂x1/∂η1 |
P(14) |
= | PTLS(2,2) |
= | ∂y1/∂η1 |
P(15) |
= | PTLS(3,2) |
= | ∂z1/∂η1 |
… | … | … | ||
P(81) |
= | PTLS(6,12) |
= | ∂z2/∂ζ'2 |
Why do this? The integration routine required a one-dimensional array of equations, but in the rest of the program, it was preferable to represent the partial derivatives as a two-dimensional array. There were three options:
P
. This would greatly
complicate the program, as well as increasing the likelihood of bugs due to
miscalculation of the correct offset.P
into the array
PTLS
after every integration step. This would be slow and
wasteful, because the program performs many thousands of integration steps,
and the number of partial derivatives grows quadratically with the number of
satellites.EQUIVALENCE
to map the two-dimensional array of partial
derivatives directly onto the one-dimensional array of integrated
quantities.
The third option was the most elegant and the most efficient. It demonstrates
that EQUIVALENCE
still has a place in modern FORTRAN 77
programming.
There are a few restrictions on the use of the EQUIVALENCE
statement. Dummy arguments in external functions and subroutines cannot
appear in the variable-list nor can variables which are external
function names. It is also illegal to equivalence two or more elements of
the same array or do anything else which violates storage sequence rules.
It is possible to equivalence different numerical data types but you must
know exactly how these values are stored internally in order to do it
correctly and sensibly. The resulting code may not be portable to other
machines. Use with caution.