One of the most fundamental aspects of Fortran programming is passing arguments to procedures. It is also one of the most misunderstood aspects. In this space today I'll try to make things clearer.
First, some terminology. In Fortran, there are "actual arguments" and "dummy arguments". An actual argument is what you put inside the parentheses in a call to a procedure. This can be a variable, a named (PARAMETER) constant, a literal (such as 3), a procedure or an expression. "variable" here means more than just a variable name. It could be an array reference, a derived type component reference, a substring reference or a combination of any or all of these. If you can put it on the left side of an assignment, it's a variable.
A dummy argument is the corresponding element in the argument list of the called procedure. Some languages call these "formal arguments". When you call a procedure, each dummy argument becomes "associated" with its corresponding actual argument, if any. (OPTIONAL dummy arguments do not get associated with omitted actual arguments.) The rules for argument association are complex and have many special cases, and that's what I'll spend most of this post going over.
On a fundamental level, argument association is pretty simple. Although most people would tell you that Fortran uses "pass by reference", meaning that the address of the actual argument is used for the dummy argument, that's not quite true. The standard says that argument association is "usually similar to call by reference" and then adds "The actual mechanism by which this happens is determined by the processor." [Fortran 2003 Note 12.22]
There are some cases where what actually gets passed is not the original argument. The most common case for this is when a non-contiguous array pointer or a array slice is passed to an argument for which the compiler does not see an explicit interface specifying that the corresponding dummy argument is an assumed-shape array. Because the called routine is expecting a contiguous array, the compiler needs to make a copy of the actual argument, pass the copy, and then copy back any changes. The compiler can warn you about this at run-time if you use the option /check:arg_temp_created (Windows) or -check arg_temp_created (Linux/Mac OS). The compiler will generate a run-time test to see if the argument is actually contiguous and skip the copy if it is.
This would probably be a good time to mention INTENT. If the compiler sees that the dummy argument is INTENT(OUT), it doesn't need to make the copy going in, and if it is INTENT(IN), it can skip the "copy out". Explicit interfaces and explicit INTENT are always a good idea.
Another case where a copy is made is if the argument has the VALUE attribute. By this I mean the Fortran 2003 VALUE attribute, not the one on a !DEC$ ATTRIBUTES directive. In this case, the copy is made by the called routine on entry into a temporary, and any changes discarded on exit.
A lot of Fortran programs, especially older ones, rely on sequence association. What is it? An actual argument that is an array element, an array expression or a character scalar (of default or C_CHAR kind) represents a sequence of elements. This sequence starts with the first element or character passed and continues to the end of the array or last character. The corresponding dummy argument, which need not have the same number of dimensions (rank) as the actual argument, or the same character length, is associated with this sequence.
Let's take a simple example common to older code.
subroutine sub (b)
When sub is called, b(1) will be associated with a(3), b(2) with a(4) and so on up through b(8) being associated with a(10). Usually, the programmer would declare b as assumed-size with a dimension of (*), in which case the implied extent of b is 8 because that's the number of elements remaining in array a.
What happens if you declare b to be fewer or more than 8 elements? Fewer is fine, but more is not. The compiler may or may not detect this error.
There is a significant restriction regarding sequence association which more and more people are running into, especially as they "update" old code to include modern Fortran features such as pointers. You don't get sequence association if the actual argument is assumed-shape or is an array with a "vector subscript". Why? Because the argument may not be contiguous and thus there can't be an association with a sequence of elements.
Here's where this gets most people into trouble... In general, a Fortran rule says that "if the actual argument is scalar, the corresponding dummy argument shall be scalar, unless the actual argument is of type default character, of type character with the C character kind, or is an element or substring of an element of an array that is not an assumed-shape or pointer array." [Fortran 2003 184.108.40.206]
If in the example above, array a was an assumed-shape array (might be a POINTER or ALLOCATABLE or a dummy argument itself) and you passed a(3), the compiler would give you error #6585 quoting the text from the standard shown in the previous paragraph. The workaround for an assumed-shape array would be to pass an array slice, but you can't then depend on sequence association and the dummy argument has to be no larger than the slice you passed.
You'll also get this error (as error #8299) if you are passing a scalar that isn't an array element, such as a constant 1 or a scalar variable, to an array dummy argument. In some cases you can use an array constructor to turn the scalar into an array, such as , but you will lose sequence association benefits.
Now, what's the deal with that exception for character types when the dummy is an array of default character kind or C kind? This lets you pass a normal character value or variable to a dummy argument declared as an array of single characters, as this is the official "interoperable" mapping for C arguments declared as "char*". Note that you'll have to supply any trailing NUL yourself - Fortran won't do it for you.
Rules, Rules, Rules
Here are just a few of the other language rules that must be followed when passing arguments. You can read them all in section 220.127.116.11 of the Fortran 2003 standard.
- The types of the actual and dummy argument must be "type compatible", Typically this means the same type, though things start to get fuzzy when the Fortran 2003 feature of "polymorphism" comes into play (coming soon to Intel Fortran.)
- If the actual and dummy are character scalars, the length parameter of the actual must be greater than or equal to the length parameter of the dummy, if it is not passed-length.
- If the dummy is a pointer, the actual must be too. Same goes for allocatable.
The compiler can help a lot with making sure the rules are followed if you are using explicit interfaces (modules, contained procedures or INTERFACE blocks.) Otherwise, use the /warn:interface and /gen-interface (Windows), -warn interface and -gen-interface (Linux, Mac OS) options to have the compiler do interface checking for all your Fortran routines. On Windows, these options are enabled by default in new Visual Studio projects.
My advice is to always use explicit interfaces and to use proper INTENT attributes as well.
That's all for this round. If you have comments on this post, please add them below. If you are looking for tech support, please post in our user forums.