将“参数”传递给子程序导致分段错误

And*_*eak 0 fortran gfortran segmentation-fault intel-fortran

过去两天我在一个大型 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
Run Code Online (Sandbox Code Playgroud)

我可以访问两个 HPC 集群,它们与我的笔记本电脑一起允许我检查这些(有时有点旧)编译器:

  • 伊堡11.1
  • gfortran 4.1.2
  • gfortran 4.4.7
  • gfortran 4.8.4(Ubuntu 14.04 存储库中的最新版本)

事实证明,所有四个编译器都会对上述代码产生段错误,因为变量b被声明为parameter. 因此,在子例程中更改其值是违规的。intent我的问题是,只有最新的 gfortran 在编译期间显示警告(即使使用 -Wall),如果我省略子例程中的规范,该警告也会消失。我怀疑在 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
Run Code Online (Sandbox Code Playgroud)

现在,在这种情况下,最新的 gfortran 会产生段错误,而其他三个编译器则不会!(实际上,这就是为什么我之前没有遇到这个问题的原因:列表中最新的 gfortran 就是我自己计算机上的那个。) 在所有情况下,我基本上没有使用编译开关,即ifort -o mwe mwe.fgfortran 也是如此。

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

  1. 在这种情况下我期待编译错误/警告是错误的吗?或者至少是“无效内存引用”之外的运行时错误。
  2. 对于某些编译器来说,使用数组可以避免此错误是否有意义?
  3. 我是否正确,不同系统上遇到的不同行为是由于编译器的差异,还是可能更微妙地特定于系统?

Sco*_*les 5

一般来说,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
Run Code Online (Sandbox Code Playgroud)

编译出现错误:

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)
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

如果您无法将模块添加到代码中,您还可以考虑启用自动接口和警告(-gen-interfaces -warn all在 ifort 中),以启用对不在模块中的函数的参数检查。