Fortran 的 "final" 子程序是否足够可靠以供实际使用?

Are Fortran's "final" subroutines reliable enough for practical use?

现代 Fortran 包含各种面向对象的思想,包括 "destructors" 通过 FINAL 关键字的概念。

MODULE mobject
  TYPE :: tobject
    ! Data declarations
  CONTAINS
    FINAL :: finalize
  END TYPE
CONTAINS
  SUBROUTINE finalize(object)
    TYPE(tobject) object
    ...
  END SUBROUTINE
END MODULE

但是,这个功能靠谱吗?值得注意的是,我注意到关于何时以及是否会调用它的不一致,英特尔 Fortran 19 和 GFortan 7、8 之间存在重大差异:

我注意到 gfortran-7.4.0 和 gfortran-8.2.1.2 之间没有区别。

这些不一致对我提出了一些关于析构函数实际可用性的问题。这两种行为是否完全符合标准?这个标准不清楚吗?该标准是否可能包含导致非直觉行为的条款?

详细分析(代码见下)

这意味着,程序员必须显式检查实例是否已初始化。


测试代码

!! -- Makefile ---------------------------------------------------
!! Runs the code with various compilers.

SHELL = bash
FC = NO_COMPILER_SPECIFIED
COMPILERS = gfortran-7 gfortran-8 ifort
PR = @echo$(n)pr -m -t -w 100

define n


endef

all: 
    rm -rf *.mod *.bin
    $(foreach FC, $(COMPILERS), $(n)\
      rm -rf *.mod && \
      $(FC) destructor.f90 -o $(FC).bin && \
      chmod +x $(FC).bin)
    $(PR) $(foreach FC, $(COMPILERS), <(head -1 <($(FC) --version)))
    $(info)
    $(foreach N,0 1 2 3 4 5 6,$(n) \
      $(PR) $(foreach FC, $(COMPILERS), <(./$(FC).bin $(N))))



!! -- destructor.f90 ---------------------------------------------

module mobject
  implicit none
  private
  public tobject, newObject

  type :: tobject
     character(32) :: name = "<undef>"
   contains
     procedure :: init
     final :: finalize
  end type tobject

contains

  subroutine init(object, name)
    class(tobject), intent(inout) :: object
    character(*), intent(in) :: name
    print *, "+ ", name
    object%name = name
  end subroutine init

  function newObject(name)
    type(tobject) :: newObject
    character(*), intent(in) :: name
    call new%init(name)
  end function newObject

  subroutine finalize(object)
    type(tobject) :: object
    print *, "- ", object%name
  end subroutine finalize

end module mobject



module mrun
  use mobject
  implicit none
contains

  subroutine run1()
    type(tobject) :: o1_uninit, o2_field_assigned, o3_tobject, o4_new, o6_init
    type(tobject), allocatable :: o5_new_alloc, o7_init_alloc
    print *, ">>>>> run1"
    o2_field_assigned%name = "o2_field_assigned"
    o3_tobject = tobject("o3_tobject")
    o4_new = newObject("o4_new")
    o5_new_alloc = newObject("o5_new_alloc")
    call o6_init%init("o6_init")
    allocate(o7_init_alloc)
    call o7_init_alloc%init("o7_init_alloc")
    print *, "<<<<< run1"
  end subroutine run1

  subroutine run2Array()
    type(tobject) :: objects(4)
    print *, ">>>>> run2Array"
    objects(1)%name = "objects(1)_uninit"
    objects(2) = tobject("objects(2)_tobject")
    objects(3) = newObject("objects(3)_new")
    call objects(4)%init("objects(4)_init")
    print *, "<<<<< run2Array"
  end subroutine run2Array

  subroutine run3AllocArr()
    type(tobject), allocatable :: objects(:)
    print *, ">>>>> run3AllocArr"
    allocate(objects(4))
    objects(1)%name = "objects(1)_uninit"
    objects(2) = tobject("objects(2)_tobject")
    objects(3) = newObject("objects(3)_new")
    call objects(4)%init("objects(4)_init")
    print *, "<<<<< run3AllocArr"
  end subroutine run3AllocArr

  subroutine run4AllocArrAssgn()
    type(tobject), allocatable :: objects(:)
    print *, ">>>>> run4AllocArrAssgn"
    objects = [ &
         tobject("objects(1)_tobject"), &
         newObject("objects(2)_new") ]
    print *, "<<<<< run4AllocArrAssgn"
  end subroutine run4AllocArrAssgn

  subroutine run5MoveAlloc()
    type(tobject), allocatable :: o_alloc
    print *, ">>>>> run5MoveAlloc"
    o_alloc = getAlloc()
    print *, "<<<<< run5MoveAlloc"
  end subroutine run5MoveAlloc

  function getAlloc() result(object)
    type(tobject), allocatable :: object
    print *, ">>>>> getAlloc"
    allocate(object)
    object = newObject("o_alloc")
    print *, "<<<<< getAlloc"
  end function getAlloc

  subroutine run6MovePtr()
    type(tobject), pointer :: o_pointer
    print *, ">>>>> run6MovePtr"
    o_pointer => getPtr()
    deallocate(o_pointer)
    print *, "<<<<< run6MovePtr"
  end subroutine run6MovePtr

  function getPtr() result(object)
    type(tobject), pointer :: object
    print *, ">>>>> getPtr"
    allocate(object)
    object = newObject("o_pointer")
    print *, "<<<<< getPtr"
  end function getPtr

end module mrun



program main
  use mobject
  use mrun
  implicit none
  type(tobject) :: object
  character(1) :: argument

  print *, ">>>>> main"
  call get_command_argument(1, argument)
  select case (argument)
  case("1")
     call run1()
  case("2")
     call run2Array()
  case("3")
     call run3AllocArr()
  case("4")
     call run4AllocArrAssgn()
  case("5")
     call run5MoveAlloc()
  case("6")
     call run6MovePtr()
  case("0")
     print *, "####################";
     print *, ">>>>> runDirectlyInMain"
     object = newObject("object_in_main")
     print *, "<<<<< runDirectlyInMain"
  case default
     print *, "Incorrect commandline argument"
  end select
  print *, "<<<<< main"
end program main

测试代码的输出

>> make
rm -rf *.mod *.bin
rm -rf *.mod && gfortran-7 destructor.f90 -o gfortran-7.bin && chmod +x gfortran-7.bin  
rm -rf *.mod && gfortran-8 destructor.f90 -o gfortran-8.bin && chmod +x gfortran-8.bin  
rm -rf *.mod && ifort destructor.f90 -o ifort.bin && chmod +x ifort.bin

pr -m -t -w 100  <(head -1 <(gfortran-7 --version))  <(head -1 <(gfortran-8 --version))  <(head -1 <(ifort --version))
GNU Fortran (SUSE Linux) 7.4.0   GNU Fortran (SUSE Linux) 8.2.1 2 ifort (IFORT) 19.0.4.243 2019041

pr -m -t -w 100  <(./gfortran-7.bin 0)  <(./gfortran-8.bin 0)  <(./ifort.bin 0) 
 >>>>> main                       >>>>> main                       >>>>> main
 ####################             ####################             ####################
 >>>>> runDirectlyInMain          >>>>> runDirectlyInMain          >>>>> runDirectlyInMain
 + object_in_main                 + object_in_main                 + object_in_main
 <<<<< runDirectlyInMain          <<<<< runDirectlyInMain          - <undef>
 <<<<< main                       <<<<< main                       - object_in_main
                                                                   <<<<< runDirectlyInMain
                                                                   <<<<< main

pr -m -t -w 100  <(./gfortran-7.bin 1)  <(./gfortran-8.bin 1)  <(./ifort.bin 1) 
 >>>>> main                       >>>>> main                       >>>>> main
 >>>>> run1                       >>>>> run1                       >>>>> run1
 + o4_new                         + o4_new                         - <undef>
 + o5_new_alloc                   + o5_new_alloc                   + o4_new
 + o6_init                        + o6_init                        - <undef>
 + o7_init_alloc                  + o7_init_alloc                  - o4_new
 <<<<< run1                       <<<<< run1                       + o5_new_alloc
 - o7_init_alloc                  - o7_init_alloc                  - o5_new_alloc
 - o6_init                        - o6_init                        + o6_init
 - o5_new_alloc                   - o5_new_alloc                   + o7_init_alloc
 - o4_new                         - o4_new                         <<<<< run1
 - o3_tobject                     - o3_tobject                     - <undef>
 - o2_field_assigned              - o2_field_assigned              - o2_field_assigned
 <<<<< main                       <<<<< main                       - o3_tobject
                                                                   - o4_new
                                                                   - o6_init
                                                                   - o5_new_alloc
                                                                   - o7_init_alloc
                                                                   <<<<< main

pr -m -t -w 100  <(./gfortran-7.bin 2)  <(./gfortran-8.bin 2)  <(./ifort.bin 2) 
 >>>>> main                       >>>>> main                       >>>>> main
 >>>>> run2Array                  >>>>> run2Array                  >>>>> run2Array
 + objects(3)_new                 + objects(3)_new                 - <undef>
 + objects(4)_init                + objects(4)_init                + objects(3)_new
 <<<<< run2Array                  <<<<< run2Array                  - <undef>
 <<<<< main                       <<<<< main                       - objects(3)_new
                                                                   + objects(4)_init
                                                                   <<<<< run2Array
                                                                   <<<<< main

pr -m -t -w 100  <(./gfortran-7.bin 3)  <(./gfortran-8.bin 3)  <(./ifort.bin 3) 
 >>>>> main                       >>>>> main                       >>>>> main
 >>>>> run3AllocArr               >>>>> run3AllocArr               >>>>> run3AllocArr
 + objects(3)_new                 + objects(3)_new                 - <undef>
 + objects(4)_init                + objects(4)_init                + objects(3)_new
 <<<<< run3AllocArr               <<<<< run3AllocArr               - <undef>
 <<<<< main                       <<<<< main                       - objects(3)_new
                                                                   + objects(4)_init
                                                                   <<<<< run3AllocArr
                                                                   <<<<< main

pr -m -t -w 100  <(./gfortran-7.bin 4)  <(./gfortran-8.bin 4)  <(./ifort.bin 4) 
 >>>>> main                       >>>>> main                       >>>>> main
 >>>>> run4AllocArrAssgn          >>>>> run4AllocArrAssgn          >>>>> run4AllocArrAssgn
 + objects(2)_new                 + objects(2)_new                 + objects(2)_new
 <<<<< run4AllocArrAssgn          <<<<< run4AllocArrAssgn          - objects(2)_new
 <<<<< main                       <<<<< main                       <<<<< run4AllocArrAssgn
                                                                   <<<<< main

pr -m -t -w 100  <(./gfortran-7.bin 5)  <(./gfortran-8.bin 5)  <(./ifort.bin 5) 
 >>>>> main                       >>>>> main                       >>>>> main
 >>>>> run5MoveAlloc              >>>>> run5MoveAlloc              >>>>> run5MoveAlloc
 >>>>> getAlloc                   >>>>> getAlloc                   >>>>> getAlloc
 + o_alloc                        + o_alloc                        + o_alloc
 <<<<< getAlloc                   <<<<< getAlloc                   - `4�[=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=]
 <<<<< run5MoveAlloc              <<<<< run5MoveAlloc              - o_alloc
 - o_alloc                        - o_alloc                        <<<<< getAlloc
 <<<<< main                       <<<<< main                       - o_alloc
                                                                   <<<<< run5MoveAlloc
                                                                   - o_alloc
                                                                   <<<<< main

pr -m -t -w 100  <(./gfortran-7.bin 6)  <(./gfortran-8.bin 6)  <(./ifort.bin 6)
 >>>>> main                       >>>>> main                       >>>>> main
 >>>>> run6MovePtr                >>>>> run6MovePtr                >>>>> run6MovePtr
 >>>>> getPtr                     >>>>> getPtr                     >>>>> getPtr
 + o_pointer                      + o_pointer                      + o_pointer
 <<<<< getPtr                     <<<<< getPtr                     - `��[=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=]
 - o_pointer                      - o_pointer                      - o_pointer
 <<<<< run6MovePtr                <<<<< run6MovePtr                <<<<< getPtr
 <<<<< main                       <<<<< main                       - o_pointer
                                                                   <<<<< run6MovePtr
                                                                   <<<<< main

TLDR:Gfortran 中存在已知的未解决问题。英特尔声称全力支持。一些编译器声称不支持。


一般来说,关于可靠性和可用性的问题是相当主观的,因为你必须考虑许多你独有的点(你需要支持多个编译器吗?你需要支持他们的旧版本吗?究竟是哪些? ? 如果某个实体没有最终确定,这有多重要?)。

你提出了一些没有实际代码示例很难回答的主张,并且可能是一个单独的完整问题和答案的主题。 Gfortran 在此错误报告 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=37336 (the link points to a meta-bug that points to several individual issues tracked in the bugzilla). It is known and that the feature is not finished and that there are outstanding issues. Most notably (at least for me), function results are not being finalized. An overview of the status of other compilers (simplified to Y/N/paritally) is at http://fortranwiki.org/fortran/show/Fortran+2003+status 中发布了 Fortran 2003 和 2008 功能的当前实施状态,并在 Fortran 论坛文章中定期更新。

我不能谈论那些所谓的 Intel Fortran 的虚假定稿。如果您在编译器中发现错误,您应该向供应商提交错误报告。英特尔通常反应迅速。

不过,有些个别问题可以得到解答。您可能会找到关于它们的单独 Q/As。但是:

  • Gfortran 不会为 PROGRAM 块中声明的实例调用析构函数,而 Ifort 会(参见示例中的 run1)。

    • 在主程序中声明的变量按照标准隐式获取save属性。编译器不应生成任何自动终结。
  • Intel Fortran 然而,当分配一个函数 return 值时,也会调用它

    • 正如 Gfortran bugzilla 中指出的那样,gfortran 尚未最终确定函数结果变量。
  • 这意味着,程序员必须显式检查实例是否已初始化。

    • Fortran 标准中恐怕没有这样的概念。我不知道“如果变量已经看到任何形式的初始化”是什么意思。请注意,初始化函数 与任何其他函数一样
  • 当使用现代功能时,对可分配数组的赋值意味着分配,这同样适用,除​​了没有未初始化的实例可供 IntelFortran 调用析构函数。

    • 不确定这到底是什么意思。 Fortran 中没有这样的"initialization"。也许功能结果又来了?
  • Allocatable/Pointers 来自函数。

    • 正如多次指出的那样,函数结果在当前版本的 Gfortran 中没有正确完成。

如果你想详细回答任何一点,你真的必须提出一个具体的问题。这个太宽泛了。此站点的 help/instructions 包含 "Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. Avoid asking multiple distinct questions at once. See the [ask] for help clarifying this question."