Fortran 的“最终”子程序在实际使用中是否足够可靠?

kdb*_*kdb 7 fortran gfortran fortran2003 intel-fortran

现代 Fortran 包含各种面向对象的思想,包括通过FINAL关键字“析构函数”的概念。

MODULE mobject
  TYPE :: tobject
    ! Data declarations
  CONTAINS
    FINAL :: finalize
  END TYPE
CONTAINS
  SUBROUTINE finalize(object)
    TYPE(tobject) object
    ...
  END SUBROUTINE
END MODULE
Run Code Online (Sandbox Code Playgroud)

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

  • GFortran 无法销毁存储在数组中的对象。
  • 英特尔 Fortran:
    • 在分配时执行虚假和潜在的多余破坏,甚至可能在包含垃圾数据的内存上,以及
    • 从函数返回时执行对析构函数的虚假调用。

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

这些不一致给我带来了一些关于析构函数的实际可用性的问题。是否有任何一种行为完全符合标准?这个标准不清楚吗?标准是否可能包含导致不直观行为的条款?

详细分析(代码见下)

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

  • 标量对象。对于声明为标量的实例,如果变量已经看到任何形式的初始化,Gfortran 和 IFort 都会调用析构函数。然而,英特尔 Fortran 在分配函数返回值时,也会调用它

    • 在用函数中的数据覆盖之前在堆栈上的未初始化对象上,以及
    • 似乎在newObject函数的末尾。

    然而,这可以通过在执行任何清理之前显式检查对象是否已初始化来防止。

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

  • 数组中的对象。如果对象包含在数组中,并且数组超出范围,

    • Gfortran 不会调用析构函数。
    • 英特尔 Fortran 可能会调用析构函数,具体取决于给定数组成员的初始化方式。
    • 数组是否声明没有区别allocatable
  • 通过赋值初始化的可分配数组。当使用现代功能时,分配给可分配数组意味着分配,同样适用,除​​了没有 IntelFortran 可以调用析构函数的未初始化实例。

  • 来自函数的可分配/指针。

    • GFortran 不会在函数结束时调用析构函数,将一个allocatable对象或 a返回pointer给一个对象,而是在客户端代码中显式地或通过超出allocatables的范围释放该值时调用它。这正是我所期望的。
    • 英特尔 Fortran 在一些其他情况下调用:
      • 当对象被声明时allocatable,而不是当它是 a 时pointer,英特尔 Fortran 会在退出函数时调用函数本地值的析构函数。
      • 当初始化与隐含分配(函数内部的对象var = newObject(...)),或者在的情况下pointer的变体,具有显式分配(allocate(var); var = newObject(...)),析构函数调用上未初始化的存储器,在可见run5MoveAllocrun6MovePtr%name含垃圾数据。这可以通过使用allocate(var); call var%init(...)模式来解决。

测试代码

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

测试代码的输出

>> 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?\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
 <<<<< 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                     - `??\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
 - o_pointer                      - o_pointer                      - o_pointer
 <<<<< run6MovePtr                <<<<< run6MovePtr                <<<<< getPtr
 <<<<< main                       <<<<< main                       - o_pointer
                                                                   <<<<< run6MovePtr
                                                                   <<<<< main
Run Code Online (Sandbox Code Playgroud)

Vla*_*r F 5

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


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

您提出了一些在没有实际代码示例的情况下难以回答的声明,并且可能是单独的完整问题和答案的主题。Gfortran 在此错误报告https://gcc.gnu.org/bugzilla/show_bug.cgi?id=37336 中发布了 Fortran 2003 和 2008 功能的当前实施状态(该链接指向一个元错误,该错误指向多个单独的在 bugzilla 中跟踪的问题)。众所周知,该功能尚未完成并且存在未决问题。最值得注意的是(至少对我而言),函数结果尚未最终确定。http://fortranwiki.org/fortran/show/Fortran+2003+status 提供了其他编译​​器状态的概述(简化为 Y/N/paritally),并在 Fortran 论坛文章中定期更新。

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

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

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

    • 主程序中声明的变量save根据标准隐式获取属性。编译器不应该生成任何自动终结。
  • 然而,英特尔 Fortran 在分配函数返回值时,也会调用它

    • 正如 Gfortran bugzilla 中所指出的,gfortran 还没有最终确定函数结果变量。
  • 这意味着,程序员必须明确检查实例是否已初始化。

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

    • 不确定这实际上意味着什么。Fortran 中没有这样的“初始化”。也许函数再次产生结果?
  • 来自函数的可分配/指针。

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

如果您想详细回答其中任何一点,您确实必须提出一个具体问题。这个太宽泛了。此站点的帮助/说明包含“请编辑问题以将其限制为具有足够详细信息的特定问题,以确定适当的答案。避免同时提出多个不同的问题。请参阅 [询问] 以帮助澄清此问题。”