Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.
29252 Discussions

Dumb code: how not to pass by reference

Darrell
Beginner
1,171 Views

I decided to share our "bug de jour". The compiler did exactly what we told it to. Why we were passing in the same variable twice and trying to modify it once is a mystery. How any compiler might protect us from our own stupidity is up for debate. Hopefully this is the right forum for everyone to get a good laugh at the bad code below.

After we tracked down the bug, I found a few quotes on Fortran pass by reference with google:

Fortran 77 uses the so-called call-by-reference paradigm. This means that instead of just passing the values of the function/subroutine arguments (call-by-value), the memory address of the arguments (pointers) are passed instead.

By default, Fortran passes all data by reference (except the hidden length argument of strings, which is a special case.)


[fxfortran]      program main
         i = 7
         print *,'before: ',i
         call foo(i,i) ! same variable 2x
         print *,'main: i= ',i
      end program main

      subroutine foo(i,j)
         intent (in) i
         intent (out) j

         print *,'foo: ',i,j
         j= 0
         print *,'done: ',i,j
      end subroutine foo
[/fxfortran]
OUTPUT

before: 7

foo: 7 7

done: 0 0

main: i= 0

The solution is obvious, dont pass in the same variable twice then zero one, right? Pass by value would put things on the stack but use up more memory and not allow in/out arguments. Don't change the inputs, pass the changed value back as a function.

0 Kudos
1 Solution
Hirchert__Kurt_W
New Contributor II
1,171 Views
The rule in question allows compilers to optimize on the assumption that the dummy arguments are independent of each other (i.e., that changing one will not change another). In this case, ifort didn't do any optimization based on that assumption, so nothing "strange" happened, but in more complicated subroutines, the results can be extremely confusing. (E.g., you might see results that suggest that changing J both changed I and didn't change I.)

The suggestion to use a single argument function instead of a subroutine with one INTENT(IN) and one INTENT(OUT) argument is a good one, but perhaps perhaps a subroutine like FOO is given to you and you don't have the source code to change it. In such a case, you can call FOO without violating the rule like this:

call foo((i),i) ! note the parentheses around the first argument

The parentheses around the first argument tell the compile to pass the value of I rather than the variable itself for the first argument. This doesn't use the same mechanism as call by value (in this case, a temporary containing the value is passed by reference), but the semantic effect is similar. The output from the PRINT statement at line 14 will show that the dummy argument I retains the value 7 even after the dummy argument J is changed, and the program would be legal.

-Kurt

View solution in original post

0 Kudos
6 Replies
mecej4
Honored Contributor III
1,171 Views
The code shown is in violation of Note 12.29 of the Fortran 2003 Standard: "If there is a partial or complete overlap between the actual arguments...the overlapped portions shall not be defined, redefined, or become undefined..." In the example code, i and j completely overlap. Therefore, neither is allowed to be changed, and j cannot have intent(out).
0 Kudos
jimdempseyatthecove
Honored Contributor III
1,171 Views
Part of the "problem" is lack of understanding of the grammar. "Shall" is an imperative, to say in a different way, it is an order. Some/many/most/all (?) compilers permit you to violate the "shall" clauses, but you do so at your own peril. The reasons for permitting this is due to the large volume of old code that violated this rule. Modern day compilers (with interface declarations) can catch this and eitherwarn or inhibit the coding error. You, as the programmer, mustaccept some level of responsibility for bad programming practices.

Jim Dempsey
0 Kudos
TimP
Honored Contributor III
1,171 Views
ifort (and several other Fortran compilers) always had one or more options to compile code which intentionally violates the standard on argument aliasing. Look up /assume:dummy_aliases.
Either that reference on Fortran 77 refers to specific implementations, or it's wrong. Several of us have worked with Fortran 77 compilers which did not use pass by reference. Anyway, that's a different issue from non-standard argument aliasing, which can break the code regardless of argument passing implementation. Often, when people say "my code works with Fortran 77" they don't mean that it complies with standards, in which case the range of unexpected problems should be limited. They mean that it exploits extensions, quirks, and missed diagnoses of some particular implementation.
0 Kudos
jimdempseyatthecove
Honored Contributor III
1,171 Views
I recall the days of early FORTRAN where you could do this

CALL FOO(123.456)
WRITE(U,FMT) 123.456
...
SUBROUTINE FOO(ARG)
REAL ARG
ARG = 654.321
END

and the WRITE(U,FMT) 123.456 would output "654.321"
This is to say you were permitted to shoot yourself in the foot by modifying literals.
Or use this hack to obfuscate yourcode.

Jim Dempsey
0 Kudos
jimdempseyatthecove
Honored Contributor III
1,171 Views
And by the way

If the compiler prohibited you from modifying the literal, you could make the claim "the compiler is broken - my program no longer runs".

The newer (FORTRAN IV and later) compilers would either inhibit this behavior or make temp variables of literals and PARAMETERS then pass reference to temp variable. IVF with optimizations can look into the code (I suppose) and avoid the temp creation if the literal is not modified.

Jim
0 Kudos
Hirchert__Kurt_W
New Contributor II
1,172 Views
The rule in question allows compilers to optimize on the assumption that the dummy arguments are independent of each other (i.e., that changing one will not change another). In this case, ifort didn't do any optimization based on that assumption, so nothing "strange" happened, but in more complicated subroutines, the results can be extremely confusing. (E.g., you might see results that suggest that changing J both changed I and didn't change I.)

The suggestion to use a single argument function instead of a subroutine with one INTENT(IN) and one INTENT(OUT) argument is a good one, but perhaps perhaps a subroutine like FOO is given to you and you don't have the source code to change it. In such a case, you can call FOO without violating the rule like this:

call foo((i),i) ! note the parentheses around the first argument

The parentheses around the first argument tell the compile to pass the value of I rather than the variable itself for the first argument. This doesn't use the same mechanism as call by value (in this case, a temporary containing the value is passed by reference), but the semantic effect is similar. The output from the PRINT statement at line 14 will show that the dummy argument I retains the value 7 even after the dummy argument J is changed, and the program would be legal.

-Kurt
0 Kudos
Reply