Fortran 90/95 遗留代码接口错误 (#6634)

Error with interfaces (#6634) with legacy code to Fortran 90/95

我使用一个用于机械分析的科学(学术)软件已经有一段时间了。现在,代码从八十年代 (Fortran 77) 开始,以 Fortran 90/95 的 mixed/hybrid 形式到达我这里。但是,由于需要添加 MKL 等强大工具,我决定从旧的 Intel Visual Fortran 11.072(使用 VS2008)迁移到 "recent" 14.0(在 ComposerXE 2013 中)。 F77 核心编译没有问题,但我在处理变量延迟定义的子例程中遇到一些接口问题。我没有烦扰大量的例程,而是让 MWE 能够重复这些问题。

小程序复制如下,大家可以自己动手:

  program main
  implicit none

  print *, 'Start of the program'
  call mainsub
  print *, 'End of the program'

  pause
  end program

有一个定义尺寸的模块 problem.f:

  module problem
  implicit none
  save

  integer, parameter :: size1 = 6
  integer, parameter :: size2 = 3
  integer, parameter :: size3 = 18

  end module problem

所以,有一个调用 "first level" 子程序的主子程序:

  SUBROUTINE mainsub
  use problem  ! here there are the dimensions defined
  implicit none

  ! Scalars (almost)
  REAL*8  :: sca01, sca02(size2), sca03

  ! Vectors
  REAL*8  :: arr01(size1)
  REAL*8  :: arr02(size1)      
  REAL*8  :: arr03(size3)
  REAL*8  :: arr04(size1)

  ! Matrices
  REAL*8  :: mat01(size1,size1)
  REAL*8  :: mat02(size3)

  ! trying to trick IFORT with interface (hiding dimension)
  print *, 'Calling sub11'
  CALL sub11(arr01)
  print *, 'Calling sub11 - end'
  pause

  print *, 'Calling sub12'
  CALL sub12(arr02,arr03,arr04)
  print *, 'Calling sub12 - end'
  pause

  print *, 'Calling sub13'
  CALL sub13(mat01,mat02)
  print *, 'Calling sub13 - end'
  pause

  print *, 'Calling sub14'
  CALL sub14(sca01,sca02,sca03)
  print *, 'Calling sub14 - end'
  pause

  contains 
      subroutine sub11(arr01)
      use problem
      implicit none

      REAL*8, DIMENSION(:) :: arr01
      print *, 'This is sub11, size arr01: ', SIZE(arr01), SHAPE(arr01)

      CALL sub21(arr01)

      end subroutine

  end subroutine

这些是 "first level" 子程序

  SUBROUTINE sub12(arr02, arr03, arr04)
  use problem
  implicit none

  REAL*8  :: arr02(*)
  REAL*8  :: arr03(size3)
  REAL*8  :: arr04(*)

  REAL*8 :: dummy(600)

  print *, 'sub 12'

  call sub22(arr02, dummy, arr04)

  END SUBROUTINE


  SUBROUTINE sub13(mat01,mat02)
  use problem
  implicit none

  REAL*8  :: mat01(size1,size1)
  REAL*8  :: mat02(size3,*)

  print *, 'sub 13'

  call sub23(mat01, mat02)

  END SUBROUTINE


  SUBROUTINE sub14(sca01,sca02,sca03)
  use problem
  implicit none

  REAL*8  :: sca01, sca02(*), sca03
  REAL*8 :: dummy(600)

  print *, 'sub 14'

  call sub24(sca01, dummy, sca03)

  END SUBROUTINE

最后是 "second level" 子例程:

  SUBROUTINE sub21(arr01)
  use problem
  implicit none

  REAL*8 :: arr01(size3,size1)

  print *, 'This is sub21, size arr01: ', SIZE(arr01)

  END SUBROUTINE


  SUBROUTINE sub22(arr02, arr03, arr04)
  use problem
  implicit none

  REAL*8  :: arr02(size3)
  REAL*8  :: arr03(size3)
  REAL*8  :: arr04(size2,size3)

  print *, 'sub22'
  print *, SIZE(arr02)
  print *, SIZE(arr03)
  print *, SIZE(arr04)

  END SUBROUTINE


  SUBROUTINE sub23(mat01,mat02)
  use problem
  implicit none

  REAL*8  :: mat01(size1,size2)
  REAL*8  :: mat02(size1,size2,size3)

  print *, 'sub 23'
  print *, SHAPE(mat01), SIZE(mat01)
  print *, SHAPE(mat02), SIZE(mat02)

  end subroutine


  SUBROUTINE sub24(sca01,sca02,sca03)
  use problem
  implicit none

  REAL*8  :: sca01, sca02(*), sca03

  print *, 'sub 24'
  print *, SHAPE(sca01), SHAPE(sca03)
  end subroutine

这段代码在我的 Intel Fortran 14 机器上编译正确。现在,让我们考虑可能出现的一系列情况。

常见变量不匹配 如果我将一个实际变量定义为 Real*8,而在子例程中相应的虚拟变量是 Real*4,或者混合使用 Real*8 --> Integer*8,编译器会识别出不匹配并给出错误。同样,如果我定义一个标量变量 Real sca01 并在子例程中定义它 Real sca01(*) 或 Real sca01(size1),编译器会再次识别一个是数组而另一个不是,因此它会抛出一个错误。 (错误 #6633:实际参数的类型与伪参数的类型不同。)

数组大小不匹配 如果您将一个数组定义为 arr02(size1) 并且在调用的子程序 arr02(size2) 中,只有当运行时检查错误处于活动状态并且整数 size1、size2 被声明为参数(如在模块 problem.f).

但是,我在两个定义中间放置了一个中间子程序,就像上面的 MWE 一样:

            sub11 -- sub21
          /
mainsub --- sub12 -- sub22
          \
            sub13 -- sub23

与 CONTAINS 语句(mainsubsub11)的接口检查实际变量和虚拟变量的大小和维度是否一致.但是,它不会检查对 sub21 的下一次调用是否会在从接口子例程中退出时丢失对大小的跟踪。

sub12中,通过使用假设的形状数组(*)定义,我可以根据需要更改形状和大小。当然,我有时会遇到分段错误,但即使我进行了所有运行时检查和恒定大小,也不会抛出任何错误或警告。

最后,对于 sub12,这个技巧也适用于多个维度,有时即使它不是形状假定数组,例如 mat02(这很奇怪。 ..).

因此,我有几个问题:

  1. 接口定义中带(*)和(:)的区别是什么?

  2. if ( * ) 就像数组大小的延迟定义,为什么它不适用于标量? (假设标量是数组 1x1) 为什么它不通过子程序检查大小?

  3. 在 Intel Fortran 11 中,许多数组大小检查未完成。现在,对于 Intel Fortran 14,我遇到了很多#6633、#6634 和#8284 错误。它改变了什么?

  4. 给定这个混合 Fortran77/90/95 全景图,我应该考虑保留哪些定义,哪些不应该保留? (显然在上面用到的里面并没有进入面向对象,因为是过程式程序)

  5. Fortran 中变量定义的哲学(主要实际原因)是什么?如果我可以 "trick" 带有程序程序的编译器(并且大小不变,没有可分配的),我想我错过了一些东西。

对于问题的长度,我深表歉意,但我是单独学习 Fortran 的,没有 CS 背景,我觉得我错过了问题的要点...

谢谢!

  1. 对于虚拟参数,dummy(*) 声明了一个假定大小的数组,dummy(:) 声明了一个假定的形状数组[并且为了完整性,dummy(some_expression) 声明了一个显式形状数组].

    对于假定大小的数组:

    • 实参必须是可以产生数组元素序列的东西,但实参的维数不需要与虚参的维数相同。

    • 在带有伪参数的过程中,实际参数的大小不会自动知道,并且您不能调用需要该大小的伪参数的操作(例如 SIZE(dummy) 不是允许。如果过程内部由于某种原因需要实参指定的数组元素序列的大小,则需要用户单独传递。

    • 在调用过程的作用域中不需要显式接口。 (假设大小是 Fortran 77 的一个概念,在该语言具有显式接口的概念之前。)

    对于假定的形状数组:

    • 实际参数必须与虚拟参数在等级上匹配。

    • 在过程中,实际参数的大小和形状通过虚拟对象自动可用。

    • 在调用该过程的任何作用域中都需要该过程的显式接口。

    显式大小数组类似于假定大小数组,但在过程中数组的大小是已知的(因为它是显式声明的!)。实参指定的数组元素序列的大小必须等于或大于显式指定的大小。

  2. 作为标量变量名称的实参(不是 CHARACTER 类型,而是默认或 C_CHAR 类型)不指定数组元素序列。作为一般原则,标量不是数组。

  3. 改进了编译器查找程序错误的能力。此外,一些与错误检查相关的编译器选项可能已更改默认值。 (注意,不同的编译器在这方面有不同的诊断能力。)

  4. 哪种类型的数组声明(除了这里讨论的那些之外还有其他的)最好取决于你想做什么,但作为新代码的指南,如果对假定形状的限制不要引起问题,然后对数组使用假定的形状。

  5. 不能保证编译器会捕获所有编程错误。不同的声明是为了不同的目的。