The following Fortran code should give a value close to 2/3.
! File "buggy.f90".
program buggy
implicit none
integer, parameter :: n = 1000
integer :: i, k
logical :: c1, c2
real, parameter :: q2 = 0.5
real :: p2
k = 0
do i = 1, n
c1 = .false.
c2 = .false.
do
! write(*,*) c1
if (c1 .or. c2) exit
call proc(c1)
if (c1) then
k = k + 1
else
call random_number(p2)
if (p2 >= q2) c2 = .true.
end if
end do
end do
write(*,*) "Result should be close to 2/3: ", real(k)/n
contains
subroutine proc(c1)
implicit none
logical, intent(out) :: c1
! logical, intent(inout) :: c1
real, parameter :: q1 = 0.5
real :: p1
call random_number(p1)
if (p1 >= q1) c1 = .true.
end subroutine proc
end program buggy
I obtain the correct value when I compile with ifort
or with gfortran -O0
(executables buggy_ifort
and buggy_gfortran-O0
in the Make
file below).
# Make file.
ifort = ifort13
gfortran = gfortran-11
all: buggy_ifort buggy_gfortran-O0 buggy_gfortran-O1 buggy_gfortran-O1_correct
buggy_ifort: buggy.f90
@ echo "\nCompiling \"buggy_ifort\"."
@ $(ifort) -warn declarations -check uninit -warn argument_checking -warn uninitialized -warn usage -implicitnone -warn uncalled -warn unused \
-std95 -warn all -o buggy_ifort buggy.f90
@ echo "\nDone."
buggy_gfortran-O0: buggy.f90
@ echo "\nCompiling \"buggy_gfortran-O0\"."
@ $(gfortran) -Wall -Wextra -pedantic -std=f95 -O0 -o buggy_gfortran-O0 buggy.f90
@ echo "\nDone."
buggy_gfortran-O1: buggy.f90
@ echo "\nCompiling \"buggy_gfortran-O1\"."
@ $(gfortran) -Wall -Wextra -pedantic -std=f95 -O1 -o buggy_gfortran-O1 buggy.f90
@ echo "\nDone."
buggy_gfortran-O1_correct: buggy.f90
@ echo "\nCompiling \"buggy_gfortran-O1_correct\"."
@ $(gfortran) -Wall -Wextra -pedantic -std=f95 -O1 \
-o buggy_gfortran-O1_correct buggy.f90 \
-fno-tree-ccp \
-fno-tree-ch \
-fno-tree-dominator-opts \
-fno-tree-fre
@ echo "\nDone."
# The output of buggy_gfortran-O1_correct is wrong if any of the -fno-tree-* above options is removed.
# The compilation of buggy_gfortran-O1_correct produces the following message:
# " buggy.f90:18:12:
#
# 18 | if (c1) then
# | ^
# Warning: ‘c1’ may be used uninitialized in this function [-Wmaybe-uninitialized] ".
# This message disappears if -fno-tree-fre is removed.
# It is replaced by
# " buggy.f90:16:19:
#
# 16 | if (c1 .or. c2) exit
# | ^
# Warning: ‘c1’ may be used uninitialized in this function [-Wmaybe-uninitialized] "
# if -fno-tree-ch is removed.
# If -fno-tree-ch is replaced by -fno-inline-functions-called-once, or if these two options
# are present, the output is correct but all the warning messages disappear.
clean:
@ rm buggy_ifort buggy_gfortran-O0 buggy_gfortran-O1 buggy_gfortran-O1_correct
However, with gfortran -O1
(executable buggy_gfortran-O1
), I always get the same wrong result: exactly 1. In all the cases, no warning is given.
Strangely, a correct value is produced with gfortran -O1
if the write(*,*) c1
statement is uncommented at the beginning of the inner loop, or if intent(out)
is replaced by intent(inout)
in the proc
subroutine. Disabling some of the optimizations made active by -O1
also provides the right result (executable buggy_gfortran-O1_correct
. See comments in the Make
file); in this case, however, I get a warning that the c1
variable may be used uninitialized!
The difference seems indeed to come from the value assigned to c1
. My understanding is that, with c1
declared as intent(out)
in the proc
subroutine, c1
should retain the value it had before the call to proc
if it is not modified in the subroutine.
So, if it is the standard interpretation, why does the execution fail with gfortran
and the -O1
(or above) general optimization, unless some specific optimizations are disabled?
And if it is not the standard interpretation, why does neither ifort
nor gfortran
issue a warning message given the compilation directives in the Make
file?
Note: This question is not exactly the same as Difference between intent(out) and intent(inout). The purpose is rather to know why the output is wrong when the code is compiled with optimization.
Regarding the effect of intent(out)
, Note 12.17 in Sect. 12.4.1.1 of the Fortran 95 standard indeed says that
INTENT(OUT) means that the value of the argument after invoking the procedure is entirely [my emphasis] the result of executing that procedure. If there is any possibility that an argument should retain its current value rather than being redefined, INTENT(INOUT) should be used rather INTENT(OUT), even if there is no explicit reference to the value of the dummy argument.
Still, even if the compiler is not required to issue a warning in that case, the apparition of warning messages seems quite erratic with gfortran
, depending on the optimizations used.
You quote the reason for the problem in the question itself
INTENT(OUT) means that the value of the argument after invoking the procedure is entirely [your emphasis] the result of executing that procedure. If there is any possibility that an argument should retain its current value rather than being redefined, INTENT(INOUT) should be used rather INTENT(OUT), even if there is no explicit reference to the value of the dummy argument.
You cannot reference the previous value of the argument with intet(out)
. It is against the standard. If you do so, the program is no longer (standard) Fortran. The standard does not specify what should happen, the result will be undefined. Other languages call this the "undefined behaviour" (UB).
The compiler is not required by the standard to diagnose such a bug from the programmer. There is no requirement for that in the standard. Unless the compiler itself promises that to be caught somewhere, it is not required to diagnose this.
Various compilers offer various error or undefined behaviour check flags and tools, but they are not guaranteed to catch everything.