fortran - Fortran 的 "final"子程序在实际使用中是否足够可靠?

标签 fortran gfortran intel-fortran fortran2003

现代 Fortran 包含各种面向对象的思想,包括通过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 无法销毁存储在数组中的对象。
  • 英特尔 Fortran:
  • 在赋值时执行虚假和潜在的多余销毁,甚至可能在包含垃圾数据的内存上,以及
  • 在从函数返回时对析构函数执行虚假调用。

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

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

    详分割析(代码见下)
  • 程序块。 Gfortran 不会为在 PROGRAM 块中声明的实例调用析构函数,而 Ifort 会(参见示例中的run1)。
  • 标量对象。 对于声明为标量的实例,如果变量已经看到任何形式的初始化,Gfortran 和 IFort 都会调用析构函数。然而,英特尔 Fortran 在分配函数返回值时,也会调用它

    堆栈上未初始化的对象上的
  • ,然后用函数中的数据覆盖它,以及
  • 看似在newObject函数的末尾。

  • 然而,这可以通过明确检查是否
    对象在执行任何清理之前被初始化。
    这意味着,程序员必须明确检查实例是否已初始化。
  • 数组中的对象。 如果对象包含在数组中,并且数组超出范围,
  • Gfortran 不会调用析构函数。
  • Intel Fortran 可能会调用析构函数,具体取决于给定数组成员的初始化方式。
  • 数组是否声明为allocatable没有区别。
  • 通过赋值初始化的可分配数组。 当使用现代功能时,分配给可分配数组意味着分配,同样适用,除​​了没有 IntelFortran 可以调用析构函数的未初始化实例。
  • 来自函数的可分配/指针。
  • GFortran 不会在函数结束时调用析构函数,将allocatable对象或pointer返回给对象,而是在客户端代码中明确或通过调用释放值时调用它超出allocatable的范围。这正是我所期望的。
  • Intel Fortran 在一些其他情况下调用:
  • 当对象被声明为 allocatable 而不是 pointer 时,英特尔 Fortran 在退出函数时调用函数本地值的析构函数。
  • 当使用隐式分配(var = newObject(...))初始化函数内部的对象时,或者在pointer变体的情况下,使用显式分配(allocate(var); var = newObject(...)),在未初始化的内存上调用析构函数,在“来自run5MoveAlloc的包含垃圾数据的run6MovePtr%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
    

    测试代码的输出
    >> 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
    

    最佳答案

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

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

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

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

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

  • Gfortran 不会为在 PROGRAM 块中声明的实例调用析构函数,而 Ifort 会(参见示例中的 run1)。
  • 主程序中声明的变量按照标准隐式获取save属性。编译器不应该生成任何自动终结。
  • 英特尔 Fortran 然而,在分配函数返回值时,也会调用它
  • 正如 Gfortran bugzilla 中所指出的,gfortran 还没有完成函数结果变量。
  • 这意味着,程序员必须明确检查实例是否已初始化。
  • Fortran标准中恐怕没有这个概念。我不知道“如果变量已经看到任何形式的初始化”可能意味着什么。请注意,初始化函数 与其他任何函数 一样。
  • 使用现代功能时,分配给可分配数组意味着分配,同样适用,除​​了没有 IntelFortran 可以调用析构函数的未初始化实例。
  • 不确定这实际上意味着什么。 Fortran 中没有这样的“初始化”。也许函数再次产生结果?
  • 函数的可分配/指针。
  • 正如多次指出的那样,当前版本的 Gfortran 中没有正确确定函数结果。

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

    关于fortran - Fortran 的 "final"子程序在实际使用中是否足够可靠?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59985499/

    相关文章:

    Fortran 选择格式以提高代码执行时间

    fortran - 访问运算符 "[ ], ( ), { }"在 Fortran 90 或 2003 中重载

    fortran - 派生类型的空数组

    gcc - 以非编译器特定的方式更改 Fortran 中的目录

    fortran - 在Fortran中将逻辑类型转换为 double

    Makefile:Intel fortran,文件夹中的源文件,和 Intel Math Kernel Library

    c# - 监控可执行文件的进度,以便可以在 Web 表单上报告

    Fortran 子例程值关键字

    constructor - 如何覆盖fortran中的结构构造函数

    fortran - 如何从 sublime 文本运行 gfortran 代码?