ENTRY statements are a form of global GOTO statement and, much as I
detest and discourage GOTOs, they do have an occasional application,
in particular for error handling. I'm not encouraging their use but
they do seem to have a place in the language.
No, ENTRY statements are not a form of a global GOTO statement. You
misunderstand their use entirely, and any success you've had using them
that way is almost certainly nonportable and/or accidental.
> Also, wouldn't this be considered recursion, since a routine subordinate
> to main (effectively) calls main?
Recursion is already possible (and legal?) in standard FORTRAN by simply
having two subroutines which are different only in name that call each
other. I have no idea what this does to the stack, though.
No, recursion is not legal in standard FORTRAN. You can't make it so by
duplicating subroutines and renaming one of them.
OK, here is a way to understand what ENTRY means, by way of recoding a
sample use of ENTRY into Fortran code that doesn't use it, but still
preserves all the semantics and meaning:
SUBROUTINE X(ARG1, ARG2)
COMMON /FOO/ A, B, C
INTEGER INVOKE
SAVE INVOKE
DATA INVOKE/0/
C
INVOKE = INVOKE + 1
...do real stuff #1...
RETURN
C
ENTRY Y(ARG2, ARG3)
INVOKE = INVOKE + 1
...do real stuff #2...
RETURN
C
ENTRY Z(INVOQ)
INVOQ = INVOKE
END
Note that X and Y share an argument, ARG2, so it can be referenced,
defined, or undefined when X or Y is called -- but not Z. Meanwhile,
ARG1 can't be touched in the code path used by a call to Y, nor can
ARG3 be touched by Z's code path. Although I've shown "real stuff" #s
1 and 2 as separate things, they can GOTO back and forth between each
other (to share code) as long as these rules are followed.
Z just returns in its first argument the total number of times X and Y have
been called prior to Z being called.
Note for recursion enthusiasts: once X, Y, or Z is called, the running
program may not call _any_ of X, Y, or Z directly or indirectly until that
first X, Y, or Z returns to its caller. This means that "real stuff #1"
cannot CALL Z(J), nor can it CALL FROB if FROB does CALL Z(J).
Now, here is the above code recoded without ENTRY in an almost entirely
standard way:
SUBROUTINE unique-name(WHICH, ARG1, ARG2, ARG3, INVOQ)
INTEGER WHICH
COMMON /FOO/ A, B, C
INTEGER INVOKE
SAVE INVOKE
DATA INVOKE/0/
C
GOTO (10, 20, 30) WHICH
C
10 INVOKE = INVOKE + 1
...do real stuff #1...
RETURN
C
20 INVOKE = INVOKE + 1
...do real stuff #2...
RETURN
C
30 INVOQ = INVOKE
END
SUBROUTINE X(ARG1, ARG2)
CALL unique-name(1, ARG1, ARG2, 0., 0)
END
SUBROUTINE Y(ARG2, ARG3)
CALL unique-name(2, 0., ARG2, ARG3, 0)
END
SUBROUTINE Z(INVOQ)
CALL unique-name(3, 0., 0., 0., INVOQ)
END
The only nonstandard thing is "unique-name", which the translation must
substitute with a name that is unique across the entire program (easy
for compilers, usually -- GNU Fortran just makes a name unique across
the source file and doesn't make it global, which achieves the same thing).
It might look wrong for X and Y to pass 0 for INVOQ, since INVOQ is modified
by the unique-name procedure, but it isn't -- the variable is only modified
in the case where Z calls unique-name, else the _original_ example was as
nonconforming as this recoding.
f2c handles ENTRY similar to the above method, though since it has the
luxury of translating into C instead of Fortran, it can do some things more
simply (e.g. pass NULL instead of 0. for missing arguments, especially nice
when the missing argument is a large array and, for pure standards
conformance, I think that'd mean a space-wasting unused array would have
to be passed to make the code valid Fortran, while NULL is suitable for
valid C).
Recursion is another story entirely. If you've been doing something
like
SUBROUTINE X(ARG1, ARG2, ARG3)
...
ENTRY XERROR
PRINT *,'Error in X'
ARG1 = 0.
END
and calling XERROR when an error is detected in X or by any of the
procedures it calls, you are attempting recursion, and you're also asking
from trouble in other ways (even if X isn't active).
The recursion problem with the above is that the compiler might well
have made lots of internal (invisible) data structures static, meaning
that even the PRINT statement might not work properly on all systems
(even though it might on yours).
The other problem is that XERROR doesn't take ARG1 as an argument, so the
assignment is non-conforming. If your machine happens to, when X is
active at the point XERROR is invoked, actually set X's ARG1 to 0. as a
result of the assignment, you're lucky or incredibly knowledgable about writing
very nonportable code for your particular machine. But you're not using
ENTRY correctly from a standard-conformance point of view.
Instead, you have to do something like:
SUBROUTINE X(ARG1, ARG2, ARG3)
...
999 PRINT *,'Error in X'
ARG1 = 0.
END
Then, anyplace in the ..., GOTO 999 to do the abort, and anyplace you would
invoke a function or subroutine in the ... that might want to do the abort
directly, recode the function as a subroutine and pass *999 as an alternate-
return argument, to be used by the called subroutine or any of the procedures
it calls (i.e. apply this technique recursively :-). See my earlier post
about ENTRY for a clearer description.
Also realize that this is not a conforming program in ANSI FORTRAN 77
(or in Fortran 90 for that matter):
PROGRAM FOO
CALL BAR
END
SUBROUTINE BAR
LOGICAL FIRST
SAVE FIRST
DATA FIRST/.TRUE./
C
IF (FIRST) THEN
FIRST = .FALSE.
CALL BLETCH
END IF
C
PRINT *,'BAR'
END
SUBROUTINE BLETCH
PRINT *,'BLETCH'
CALL BAR
END
If you're lucky, the above program might print
BLETCH
BAR
BAR
but it is also perfectly standard-conforming for your system to delete
all your files as well, since you're program is not standard-conforming
due to its attempt to recurse. :-)
Also note that even the following is not permitted by the standard:
PRINT *,FOO(ICHAR('2'))
END
REAL FUNCTION FOO(I)
CHARACTER*20 IFILE
IFILE = CHAR(I) // '.3'
READ (10, IFILE) FOO
10 FORMAT (F3.1)
END
(Assuming I've got the format and other such sundry details right....)
The above is not _overt_ recursion, but is nevertheless disallowed by
the standard because it has _implicit_ recursion of the Fortran I/O subsystem.
In particular, FOO is invoked after the start of I/O, and then FOO itself
reinvokes the I/O subsystem to do the internal-file READ to translate
"2.3" into a REAL value in FOO.
You might say, "well why didn't they require pre-evaluation of the arguments
to the I/O statement?" Good question. First, I'm not sure offhand if
this would be valid:
WRITE (*,*,ERR=20) (FOO(I), FOO(I), J=1,100)
20 END
REAL FUNCTION FOO(I)
FOO = FLOAT (I)
I = I + 1
END
If it is valid (which I think it might be, actually), it wouldn't work
if preevaluation was required (I should not always end up with the value
201 or whatever it gets, if there's an error writing to *).
But, more specifically, how would this work?
READ *, I, FOO(I)
END
Here, only after the value I is read, and while the I/O is still active,
do we know what to pass to FOO for preevaluation.
Believe me, this kind of thing gives all sorts of problems to those of
us who implement Fortran compiler systems, especially on weird
architectures or under other such constraints!
I don't remember whether Fortran 90 lifts the restriction on recursive
invocation of the I/O subsystem, offhand. Sure would be nice, since it
seems possible, and it's hard for programmers to keep themselves from
violating the rule (especially given how it is so common to insert
PRINT statements to debug code).
--