When Fortran was originally developed memory was at a premium. Variables and procedure names could have a maximum of 6 characters, and variables were often implicitly typed. This means that the first letter of the variable name determines its type.
integer
real
Programs like the following are acceptable Fortran:
program badbadnotgood
j = 4
key = 5 ! only the first letter determines the type
x = 3.142
print*, "j = ", j, "key = ", key, "x = ", x
end program badbadnotgood
You may even define your own implicit rules with the implicit
statement:
! all variables are real by default
implicit real (a-z)
or
! variables starting with x, y, z are complex
! variables starting with c, s are character with length of 4 bytes
! and all other letters have their default implicit type
implicit complex (x,y,z), character*4 (c,s)
Implicit typing is no longer considered best practice. It is very easy to make a mistake using implicit typing, as typos can go unnoticed, e.g.
program oops
real :: somelongandcomplicatedname
...
call expensive_subroutine(somelongandcomplEcatedname)
end program oops
This program will happily run and do the wrong thing.
To turn off implicit typing, the implicit none
statement can be used.
program much_better
implicit none
integer :: j = 4
real :: x = 3.142
print*, "j = ", j, "x = ", x
end program much_better
If we had used implicit none
in the program oops
above, the compiler would have noticed immediately, and produced an error.
Arithmetic if
statement allows one to use three branches depending on the result of an arithmetic expression
if (arith_expr) label1, label2, label3
This if
statement transfers control flow to one of the labels in a code. If the result of arith_expr
is negative label1
is involved, if the result is zero label2
is used, and if the result is positive last label3
is applied. Arithmetic if
requires all three labels but it allows the re-use of labels, therefore this statement can be simplified to a two branch if
.
Examples:
if (N * N - N / 2) 130, 140, 130
if (X) 100, 110, 120
Now this feature is obsolete with the same functionality being offered by the if
statement and if-else
construct. For example, the fragment
if (X) 100, 110, 120
100 print*, "Negative"
goto 200
110 print*, "Zero"
goto 200
120 print*, "Positive"
200 continue
may be written as the if-else
construct
if (X<0) then
print*, "Negative"
else if (X==0) then
print*, "Zero"
else
print*, "Positive"
end if
An if
statement replacement for
if (X) 100, 100, 200
100 print *, "Negative or zero"
200 continue
may be
if (X<=0) print*, "Negative or zero"
The non-block do
construct looks like
integer i
do 100, i=1, 5
100 print *, i
That is, where the labelled termination statement is not a continue
statement. There are various restrictions on the statement that can be used as the termination statement and the whole thing is generally very confusing.
Such a non-block construct can be rewritten in block form as
integer i
do 100 i=1,5
print *, i
100 continue
or better, using an end do
termination statement,
integer i
do i=1,5
print *, i
end do
Alternate return is a facility to control the flow of execution on return from a subroutine. It is often used as a form of error handling:
real x
call sub(x, 1, *100, *200)
print*, "Success:", x
stop
100 print*, "Negative input value"
stop
200 print*, "Input value too large"
stop
end
subroutine sub(x, i, *, *)
real, intent(out) :: x
integer, intent(in) :: i
if (i<0) return 1
if (i>10) return 2
x = i
end subroutine
The alternate return is marked by the arguments *
in the subroutine dummy argument list.
In the call
statement above *100
and *200
refer to the statements labelled 100
and 200
respectively.
In the subroutine itself the return
statements corresponding to alternate return have a number. This number is not a return value, but denotes the provided label to which execution is passed on return. In this case, return 1
passes execution to the statement labelled 100
and return 2
passes execution to the statement labelled 200
. An unadorned return
statement, or completion of subroutine execution without a return
statement, passess execution to immediately after the call statement.
The alternate return syntax is very different from other forms of argument association and the facility introduces flow control contrary to modern tastes. More pleasing flow control can be managed with return of an integer "status" code.
real x
integer status
call sub(x, 1, status)
select case (status)
case (0)
print*, "Success:", x
case (1)
print*, "Negative input value"
case (2)
print*, "Input value too large"
end select
end
subroutine sub(x, i, status)
real, intent(out) :: x
integer, intent(in) :: i
integer, intent(out) :: status
status = 0
if (i<0) then
status = 1
else if (i>10)
status = 2
else
x = i
end if
end subroutine
Fortran originally was designed for a fixed format form based on an 80 column punched card:
Yes: This is a line of the author's own code
These were created on a card punch machine, much like this:
Images are original photography by the author
The format, as shown on the illustrated sample card, had the first five columns reserved for statement labels. The first column was used to denote comments by a letter C. The sixth column was used to denote a statement continuation (by inserting any character other than a zero '0'). The last 8 columns were used for card identification and sequencing, which was pretty valuable if you dropped your deck of cards on the floor! The character coding for punched cards had only a limited set of characters and was upper case only. As a result, Fortran programs looked like this:
DIMENSION A(10) 00000001
C THIS IS A COMMENT STATEMENT TO EXPLAIN THIS EXAMPLE PROGRAM 00000002
WRITE (6,100) 00000003
100 FORMAT(169HTHIS IS A RATHER LONG STRING BEING OUTPUT WHICH GOES OVE00000004
1R MORE THAN ONE LINE, AND USES THE STATEMENT CONTINUATION MARKER IN00000005
2COLUMN 6, AND ALSO USES HOLLERITH STRING FORMAT) 00000006
STOP 00000007
END 00000008
The space character was also ignored everywhere, except inside a Hollerith character constant (as shown above). This meant that spaces could occur inside reserved words and constants, or completely missed out. This had the side effect of some rather misleading statements such as:
DO 1 I = 1.0
is an assignment to the variable DO1I
whereas:
DO1I = 1,0
is actually a DO
loop on the variable I
.
Modern Fortran does not now required this fixed form of input and permits free form using any columns. Comments are now indicated by a !
which can also be appended to a statement line. Spaces are now not permitted anywhere and must be used as separators, much as in most other languages. The above program could be written in modern Fortran as:
! This is a comment statement to explain this example program
Print *,"THIS IS A RATHER LONG STRING BEING OUTPUT WHICH no longer GOES OVER MORE THAN ONE LINE, AND does not need to USE THE STATEMENT CONTINUATION MARKER IN COLUMN 6, or the HOLLERITH STRING FORMAT"
Although the old-style continuation is no longer used, the above example illustrates that very long statements will still occur. Modern Fortran uses a &
symbol at the end and beginning of the continuation. For example, we could write the above in a more readable form:
! This is a comment statement to explain this example program
Print *,"THIS IS A RATHER LONG STRING BEING OUTPUT WHICH still &
&GOES OVER MORE THAN ONE LINE, AND does need to USE THE STATEMENT &
&CONTINUATION notation"
In the early forms of Fortran the only mechanism for creating global variable store visible from subroutines and functions is to use the COMMON
block mechanism. This permitted sequences of variables to be names and shared in common.
In addition to named common blocks there may also be a blank (unnamed) common block.
A blank common block could be declared like
common i, j
whereas the named block variables
could be declared like
common /variables/ i, j
As a complete example, we could imagine a heap store that is used by routines that can add and remove values:
PROGRAM STACKING
COMMON /HEAP/ ICOUNT, ISTACK(1023)
ICOUNT = 0
READ *, IVAL
CALL PUSH(IVAL)
CALL POP(IVAL)
END
SUBROUTINE PUSH(IVAL)
COMMON /HEAP/ ICOUNT, ISTACK(1023)
ICOUNT = ICOUNT + 1
ISTACK(ICOUNT) = IVAL
RETURN
END
SUBROUTINE POP(IVAL)
COMMON /HEAP/ ICOUNT, ISTACK(1023)
IVAL = ISTACK(ICOUNT)
ICOUNT = ICOUNT - 1
RETURN
END
Common statements may be used to implicitly declare the type of a variable and to specify the dimension
attribute. This behaviour alone is often a sufficient source of confusion. Further, the implied storage association and requirements for repeated definitions across program units makes the use of common blocks prone to error.
Finally, common blocks are very restricted in the objects they contain. For example, an array in a common block must be of explicit size; allocatable objects may not occur; derived types must not have default initialization.
In modern Fortran this sharing of variables can be handled by the use of modules. The above example can be written as:
module heap
implicit none
! In Fortran 2008 all module variables are implicitly saved
integer, save :: count = 0
integer, save :: stack(1023)
end module heap
program stacking
implicit none
integer val
read *, val
call push(val)
call pop(val)
contains
subroutine push(val)
use heap, only : count, stack
integer val
count = count + 1
stack(count) = val
end subroutine push
subroutine pop(val)
use heap, only : count, stack
integer val
val = stack(count)
count = count - 1
end subroutine pop
end program stacking
Named and blank common blocks have slightly different behaviours. Of note:
save
attribute; objects in named common blocks without the save
attribute may become undefined when the block is not in the scope of an active program unitThis latter point can be contrasted with the behaviour of module variables in modern code. All module variables in Fortran 2008 are implicitly saved and do not become undefined when the module goes out of scope. Before Fortran 2008 module variables, like variables in named common blocks, would also become undefined when the module went out of scope.
Assigned GOTO uses integer variable to which a statement label is assigned using the ASSIGN statement.
100 CONTINUE
...
ASSIGN 100 TO ILABEL
...
GOTO ILABEL
Assigned GOTO is obsolescent in Fortran 90 and deleted in Fortran 95 and later. It can be avoided in modern code by using procedures, internal procedures, procedure pointers and other features.
Computed GOTO allows branching of the program according to the value of an integer expression.
GOTO (label_1, label_2,... label_n) scalar-integer-expression
If scalar-integer-expression
is equal to 1 the program continues at statement label label_1
, if it is equal to 2 it goes to label_2
and so on. If it is less then 1
or larger than n
program continues on next line.
Example:
ivar = 2
...
GOTO (10, 20, 30, 40) ivar
will jump to statement label 20.
This form of goto
is obsolescent in Fortran 95 and later, being superseded by the select case
construct.
Before Fortran 95 it was possible to use assigned formats for input or output. Consider
integer i, fmt
read *, i
assign 100 to fmt
if (i<100000) assign 200 to fmt
print fmt, i
100 format ("This is a big number", I10)
200 format ("This is a small number", I6)
end
The assign
statement assigns a statement label to an integer variable. This integer variable is later used as the format specifier in the print
statement.
Such format specifier assignment was deleted in Fortran 95. Instead, more modern code can use some other form of execution flow control
integer i
read *, i
if (i<100000) then
print 100, i
else
print 200, i
end if
100 format ("This is a big number", I10)
200 format ("This is a small number", I6)
end
or a character variable may be used as the format specifier
character(29), target :: big_fmt='("This is a big number", I10)'
character(30), target :: small_fmt='("This is a small number", I6)'
character(:), pointer :: fmt
integer i
read *, i
fmt=>big_fmt
if (i<100000) fmt=>small_fmt
print fmt, i
end
Consider the program
implicit none
integer f, i
f(i)=i
print *, f(1)
end
Here f
is a statement function. It has integer result type, taking one integer dummy argument.1
Such a statement function exists within the scope in which it is defined. In particular, it has access to variables and named constants accessible in that scope.
However, statement functions are subject to many restrictions and are potentially confusing (looking at casual glance like an array element assignment statement). Important restrictions are:
The main benefits of statement functions are repeated by internal functions
implicit none
print *, f(1)
contains
integer function f(i)
integer i
f = i
end function
end
Internal functions are not subject to the restrictions mentioned above, although it is perhaps worth noting that an internal subprogram may not contain further internal subprogram (but it may contain a statement function).
Internal functions have their own scope but also have available host association.
1 In real old code examples, it wouldn't be unusual to see the dummy arguments of a statement function being implicitly typed, even if the result has explicit type.