我花了两天时间在一个大型Fortran项目中调试一个看似荒谬的段错误。当我将代码移动到我自己的计算机时,问题就出现了,并且在一些代码中出现了段错误,这些代码在其他几个系统上运行良好多年。我最终找到了段错误的来源,但是我决定将它发布在这里令人惊讶地意外(并且依赖于编译器)。
考虑以下MWE:
program dafuq
implicit none
integer :: a=1
integer, parameter :: b=2
call foo(a,b)
end program dafuq
subroutine foo(a,b)
implicit none
integer, intent(inout) :: a, b
a=b !OK
b=a !causes segfault
end subroutine foo
我可以访问两个HPC群集,它与我的笔记本电脑一起让我可以检查这些(有时候有些旧)编译器:
事实证明,所有四个编译器都使用上面的代码产生段错误,因为变量b
被声明为parameter
。因此,在子例程中更改其值是违规的。我的问题是,只有最新的gfortran在编译期间显示警告(即使使用-Wall),如果省略子例程中的intent
规范,那也会消失。我怀疑使用const
变量在C ++中使用相同的设置会引发一个巨大的红旗。
现在,为了使其更加模糊,请考虑以下代码,使用数组而不是标量:
program dafuq_array
implicit none
integer :: a(2)=(/1,1/)
integer, parameter :: b(2)=(/2,2/)
call foo(a,b)
end program dafuq_array
subroutine foo(a,b)
implicit none
integer, intent(inout) :: a(2), b(2)
a=b !OK
b=a !might cause segfault
end subroutine foo
现在,在这种情况下,最新的gfortran产生了一个段错误,而其他三个编译器都没有! (实际上这就是为什么我之前没有遇到过这个问题的原因:列表中最新的gfortran就是我自己计算机上的那个。)在所有情况下,我基本上都没有使用编译开关,即ifort -o mwe mwe.f
和gfortran也一样。
即使我找到了段错误的原因并且我理解它,仍然有一些事情让我感到烦恼(没有双关语意)。
答案 0 :(得分:4)
一般情况下,只有Fortran函数参数位于模块内时才会进行类型检查。例如,如果将子例程放在模块中:
module m
public
contains
subroutine foo(a,b)
implicit none
integer, intent(inout) :: a,b
a = b
b = a
end subroutine
end module
program p
use m
implicit none
integer :: a
integer, parameter :: b = 2
a = 1
call foo(a,b)
end program
编译会给出错误:
gfortran 4.6.4
:
test.f90:35.15:
call foo(a,b)
1
Error: Non-variable expression in variable definition context (actual argument to INTENT = OUT/INOUT) at (1)
ifort 13.0.1
:
test.f90(35): error #6638: An actual argument is an expression or constant; this is not valid since the associated dummy argument has the explicit INTENT(OUT) or INTENT(INOUT) attribute. [2]
call foo(a,b)
---------------^
compilation aborted for test.f90 (code 1)
如果您无法在代码中添加模块,您还可以考虑启用自动接口和警告(ifort中的-gen-interfaces -warn all
)以启用不在模块中的函数的参数检查。
答案 1 :(得分:2)
希望发生错误并不是错误的(尽管你不能指望这种情况)并且GNU Fortran 5.1.0会为你的第二个测试用例提供警告。
dafuq.f90:6:13:
call foo(a,b)
1
Warning: Named constant ‘b’ in variable definition context (actual argument to INTENT = OUT/INOUT) at (1)
另请注意,虽然有些编译器工作正常,但有些编译器没有编译,有些段错误,有些编译工作的数组不能使用标量,这些都是合理的结果。一旦违反标准,您的代码就不再是Fortran,未定义的行为意味着任何结果都是正确的。