将 "parameter" 传递给子例程时出现分段错误

Segmentation fault by passing "parameter" to subroutine

过去两天我一直在调试一个大型 Fortran 项目中看似荒谬的段错误。当我将代码移到我自​​己的计算机上时,问题就开始了,段错误出现在代码的一部分中,该代码在其他几个系统上已经运行多年。我最终找到了段错误的来源,但它出乎意料地出乎意料(并且依赖于编译器),所以我决定在这里 post 它。

考虑以下 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 规范,那也会消失。我怀疑在 C++ 中使用 const 变量的相同设置会引发巨大的危险信号。

现在,为了让它更晦涩,请考虑以下代码,使用数组而不是标量:

  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。

尽管我找到了段错误的原因并且我有点理解它,但仍有一些事情让我烦恼(没有双关语)。

  1. 在这种情况下我期望编译 error/warning 是不是错了?或者至少 运行 次错误超过 "invalid memory reference".
  2. 对于某些编译器,使用数组避免此错误是否有意义?
  3. 我说的对吗,在不同系统上遇到的不同行为是由于编译器的差异,还是更微妙的系统特定?

通常 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)以启用不在模块中的函数的参数检查。

希望出现错误并没有错(尽管在这种情况下您不能指望出现错误)并且 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 代码,未定义的行为意味着任何结果都是正确的。