gfortran开启优化时intent(out)变量引发执行Bug的技术问询
Fortran代码优化异常与标准合规问题解析
问题背景
以下Fortran代码的预期输出值应接近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
对应的Makefile如下:
# 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
问题现象
- 使用ifort或
gfortran -O0编译时,代码输出接近2/3的正确结果; - 使用
gfortran -O1编译时,结果恒为1,且编译无任何警告; - 取消内循环开头
write(*,*) c1的注释、将proc子程序的intent(out)改为intent(inout),或禁用-O1下的特定优化选项(如编译buggy_gfortran-O1_correct),代码可输出正确结果,但此时会出现c1可能未初始化的警告,且警告位置随优化选项变化而改变。
核心语义澄清
根据Fortran 95标准12.4.1.1节注12.17:INTENT(OUT)意味着调用过程后,参数的值完全由该过程的执行结果决定;若需要保留参数调用前的值,必须使用INTENT(INOUT)。用户最初的误解(认为intent(out)参数未被修改时会保留原值)不符合标准规范。
疑问解答
1. gfortran开启-O1及以上优化时结果错误的原因
gfortran在-O1及更高优化级别下,会严格依据Fortran标准对intent(out)的语义做激进优化:
- 编译器认定
intent(out)参数在子程序返回时必须被完全赋值,因此会假设proc一定会修改c1的值; - 基于这个假设,编译器会执行常量传播、死代码消除、函数内联等优化,错误改写代码逻辑:例如认为
proc调用后c1必然为.true.(因为未赋值的intent(out)参数属于未定义行为,编译器可做任意假设),导致每次循环都执行k = k + 1,最终结果恒为1; - 禁用
-fno-tree-ccp、-fno-tree-ch等特定优化选项时,编译器无法应用这些基于语义假设的激进优化,代码会按照用户错误的预期逻辑执行,从而输出正确结果。
2. 为何ifort和gfortran未发出预期警告
- gfortran:默认
-O1优化流程中,编译器基于intent(out)的语义假设,认为proc一定会给c1赋值,因此不会触发未初始化警告;只有禁用部分优化选项后,编译器才会检测到c1存在未被赋值的路径,进而发出警告; - ifort(13版本):旧版本编译器对
intent(out)的语义检查和优化策略更宽松,没有严格执行标准中“intent(out)参数必须被完全覆盖”的要求,因此既不会触发警告,也不会做激进优化破坏原有逻辑,但这属于编译器的兼容行为,并不符合Fortran标准。
内容的提问来源于Stack Exchange,提问作者Michel Fioc
相关产品推荐
相关产品推荐

