Doctor Fortran in "Too Much of a Good Thing?"

A lot of Fortran programmers take the "belt and suspenders" approach to coding, with explicit declarations of every attribute they want for a symbol.  In general, this is good practice, especially when combined with IMPLICIT NONE to force you to say what you mean.  But some programmers take this a bit too far and it gets them into trouble.  Let's look at some cases...

Department of Redundancy Department

This is an oldie but a goodie.  If one declaration is good, two should be better, right?  I've seen some programs come in with such things as:

INTEGER I
INTEGER I


Whatever possessed them to write this, I don't know.  In some cases, there isn't even the excuse of one of the declarations coming from an INCLUDE file.  So what's wrong with this?  The Fortran 2003 standard says:

C508 An entity shall not be explicitly given any attribute more than once in a scoping unit.

The C508 indicates that this is a "constraint" - a rule that must be followed in order for the program to be valid standard Fortran and which the compiler must have the ability to check.  Some compilers will allow this redundant declaration (when not asked to check standards conformance), but Intel Fortran does not.  We've had requests over the years to be more relaxed here, but we believe that such redundant declarations are more likely to be an unintentional error.  For example, we sometimes see declaratioins such as the following:

INTEGER X1,X2,X3,X3,X5

where it seems clear that the programmer meant to declare X4 but slipped. We'd rather alert the programmer to the error than have it go undetected and possibly cause problems later.

Up Periscope!

Fortran 90 added several more "scopes" - nested contexts where symbols can be found.  The "host scope" is the traditional Fortran 77 one which is within a given program unit (subroutine, function, program or BLOCK DATA construct).  "use association" comes into play when you have a USE of a module, and the module has its own scope. "host association" is when one scope is contained within another, such as a module procedure or a contained procedure.

Here again, it's possible to get into trouble by adding a declaration that doesn't need to be there. Consider the following case:

module MYMOD
implicit none
contains
integer function F1 (X)
integer, intent(IN) :: X
integer F2
F1 = F2(2*X)
end function F1

integer function F2 (X)
integer, intent(IN) :: X
F2 = 3 * X
end function F2
end module MYMOD

When this code is built into an application using version 11 of the Intel compiler, a link error is generated for the call to F2 from within F1; an error that did not happen with version 10.1.  Compiler bug?  No, a compiler bug fix - version 11 is correct.

The trouble-maker is the "redundant" declaration of F2 inside F1.  Without that, F2 already has an explicit interface from host association - it is a module procedure in the same module.  But if you then add a declaration of F2 inside F1, the language says that this is declaring a different, external procedure F2, which may or may not exist in the program.

The key part of the standard governing this is Fortran 2003 section 12.4.4, "Resolving named procedure references".  It is essentially an algorithm to follow to determine which F2 is meant when you have a reference to a procedure F2. If you follow the steps using the above example, you'll find that you end up at 12.4.4.3(3) "the reference is to an external procedure with that name".

This is not restricted to procedures, though.  Consider the following:

module MYMOD2
integer X
contains
subroutine SUB
integer X
X = 3
...

If you are expecting the module variable X to be updated when SUB is called, you'll be disappointed.  In general, local declarations of a name hide host-associated declarations of the same name.  But if you're expecting that behavior for use association as well, think again...

module MYMOD3
integer X
end module MYMOD3
subroutine SUB
use MYMOD3
integer X ! Error!
...


This redeclaration of X is an error; unlike for host association, you can't hide a use-associated name with a local declaration.  If you want to do that, then use an ONLY or renaming clause to prevent the same-named symbol from becoming visible.

We often see these sorts of problems in code that has been "converted from Fortran 77" by taking a set of procedures and putting them in a module.  Declarations which were necessary in the separate context are forbidden when host association comes into play.

One More Thing

Finally, consider this example:

module MYMOD4
integer X
end module MYMOD4
program MAIN
use MYMOD4
dimension X(10)
...

This is also an error.  Once you inherit a symbol through association, most of its attributes are "locked"; you are not allowed to add to them.  An exception is made for the ASYNCHRONOUS and VOLATILE attributes which can be added-on inside a procedure.

Say No More

Now you have learned that, while it is good practice to explicitly specify attributes, you can get into trouble by over-doing it.  I hope this has helpful to you.  Feel free to comment below if you have questions about this item, but if you are looking for support for a problem, please ask in the user forums instead.

 

 

For more complete information about compiler optimizations, see our Optimization Notice.

13 comments

Top
joseph-krahn's picture

You can have declaration warnings always enabled by setting $IFORTCFG to a file containing the option "-warn declarations".

Beware that it is still possible to accidentally use a host associated variable by mistake, which is not that hard to do for contained procedures using a simple variable name like 'I'. Some people like to convert the main code body into a contained procedure to isolate it's local variables from the other contained procedures.

There was some discussion on extending Fortran to avoid this problem IMPORT in a way that would require explicit imports of host-associated variables. It has the same merits as "IMPLICIT NONE" and "USE, ONLY", so it may one day become a standard language feature, but Fortran Standards development is a very a slow process.

No, /check has no effect on generated interfaces. Perhaps you meant /warn:all ?

Mike Prager's picture

On Windows, the option /check:all seems to include /gen-interface (and, I would assume, /warn:interface). Good features.

anonymous's picture

Good note about the version 11.1 change Steve, I've noticed the -gen-interfaces missing on some recent code and didn't know why.

New? No, it goes back to the 1970s and VAX Fortran. Most other Fortran compilers have a similar option.

I definitely agree that explicit declarations are best - I recommend against use of switches that change program semantics, such as -r8 you mentioned. -gen-interfaces is a diagnostic tool - it does not change the interpretation of the program and does not relieve you of the requirement of writing interfaces when the language says you must. (Note that as of version 11.1, you don't need -gen-interfaces if you say -warn interfaces.)

Izaak Beekman's picture

Is the -warn declarations option new? I seem to remember there being some other command line syntax to force IMPLICIT NONE.

I must admit, however, I find it stylistically dubious to assume that the code and variables will be modified at compile time. I think explicit declarations in the code are a much safer and a better practice. (Albeit don't over do it with multiple declarations!) For example I have seen a lot of codes which break if not compiled without -r8, and it can take some time to figure out that the author intended the code to be compiled a certain way. One possible exception to this in my opinion is the -gen-interfaces flag. This can be very useful for working with legacy codes. While I think best practice is to declare procedures in modules where they automatically get explicit interfaces, adding interfaces manually for external procedures not contained within modules is quite a verbose enterprise and dull too!

rreis's picture

cool, thanks! I think I'll man ifort with more care.

Compiler flag for IMPLICIT NONE? /warn:declarations (-warn declarations)

rreis's picture

Once I was stranded a full afternoon chasing something that could have been avoided with an explicit interface... and always implicit none (a compiler flag for it would be cool)

best,

Hi, Greg..

Yes, absolutely. My favorite is the combination of /gen-interface and /warn:interface (for Linux and Mac OS X that's "-gen-interface -warn interface". This is set by default for new Visual Studio projects on Windows, but I recommend it to everyone.

What /gen-interface does is tell the compiler to generate interface blocks for external subroutines (F77 style), creating a mini module for each one. /warn:interface tells the compiler that, when it sees a call to a procedure for which an explicit interface is not visible, to go look to see if there is a generated interface for that routine. If it finds one, it checks the call for mismatches as it would if there were an explicit interface. This will warn you of mismatches of argument number, type, failure to provide an explicit interface where one is required, and more. Note that a generated interface is NOT a substitute for providing an explicit interface where the language requires it (such as OPTIONAL arguments, etc.)

There's one caveat, though. This feature is dependent on the order of compilation. If you have multiple source files, the file(s) with the routines needs to be compiled before the file(s) with the calls in order for the checking to be done. If there are callers and callees in the same source file, that's ok. This is why sometimes doing a second build will show errors that the first didn't find.

Of course, this feature would not be necessary if everyone always used explicit interfaces - preferably as module procedures - but there's a lot of code out there that can benefit.

Other practices I recommend include:
- Always use free-form source
- Use KIND specifiers for constants. Especially if you have "significant" constants such as the value of pi, declare PARAMETER constants for these.
- Don't hardcode KIND values such as 4 and 8 - use SELECTED_INT_KIND and SELECTED_REAL_KIND to declare PARAMETER constants for kind values and then use the constants.

Steve

Pages

Add a Comment

Have a technical question? Visit our forums. Have site or software product issues? Contact support.