如何判断一个 fortran 数组指针是直接分配的,还是与另一个对象相关联?

Posted

技术标签:

【中文标题】如何判断一个 fortran 数组指针是直接分配的,还是与另一个对象相关联?【英文标题】:How can I tell if a fortran array pointer has been allocated directly, or is associated with another object? 【发布时间】:2016-11-15 21:16:59 【问题描述】:

我正在使用包含一些数组指针的 fortran 代码;根据用户输入,它们可能被设置为使用赋值语句=> 指向另一个数组,或者它们可能直接使用allocate 语句进行分配。

这会在代码末尾产生释放内存的问题,因为在第一种情况下我只想nullify 指针,而在第二种情况下我需要deallocate 它以防止内存泄漏。问题是associated 在这两种情况下都返回true,所以我不知道如何在不手动跟踪的情况下判断我所处的情况。由于有很多这样的数组指针,我宁愿避免这样做。

有没有简单的方法来区分这两种情况?

谢谢!

【问题讨论】:

“在代码的末尾”,你的意思是有多接近结尾?在整个执行过程中,我可能会关心内存泄漏等问题,但在我的程序结束时,确保一切都被整理好不再是我的责任。 我不确定我是否理解你的意思......当然我应该瞄准在程序终止之前分配的所有内存? 我的意思是,考虑一下(可怕的)程序:allocatable i; allocate(i); end。对于没有deallocate,我一点也不感到内疚。有时我想负责解除分配,但不是在这里。您的问题的前提是应该有deallocate 声明吗?我的第一条评论就是为了证明这一点。 啊抱歉,是的,我感到内疚!每个allocate 都应该有一个deallocate。如果我这样做:integer, pointer :: i(:); allocate i(5); nullify(i); end 那么我最终会得到一些空间,这些空间没有被释放并且代码也无法访问。 为了清楚起见,以免其他人偶然发现这个问题并像我一样感到困惑——绝对没有必要显式释放内存,无论是在最后使用 deallocate 还是 nullify的一个程序。除非您的操作系统损坏,否则它将在程序结束时获得分配给程序的所有内存。如果 OP 关心的是在执行期间的另一点获取内存,那将是另一回事。值得牢记的是,最新版本的 Fortran 标准保证在分配的对象超出范围时释放。 【参考方案1】:

这听起来像一个可怕的混乱。我假设你有这样的东西:

program really_bad
    implicit none
    integer, dimension(:), pointer :: a
    integer, dimension(:), allocatable, target :: b
    logical :: point_to_b

    allocate(b(10))
    write(*, *) "Should I point to B?"
    read(*, *) point_to_b

    if (point_to_b) then
        a => b
    else
        allocate(a(10))
    end if

    ! Destroy A

end program really_bad

而你的问题是破坏性的部分。如果a 指向b,那么你想要NULLIFY 它,因为你需要保留b。但是如果a 没有指向b,而你只是NULLIFY 它,就会发生内存泄漏。

正如@ian-bush 指出的那样,您可以检查某事是否与其他某事相关联,但这意味着您必须将指针与所有可能的目标进行比较,并且您声称您有很多目标。

您可以尝试一件事,但这可能是一个更糟糕的想法,即尝试解除分配,并使用其STAT= 虚拟参数来检查解除分配是否真的有效。请注意,这是一个可怕的 hack,我只让它在 Intel 上工作,而不是在 GFortran 上工作,但这里什么都没有:

program don
    implicit none
    integer, dimension(:), pointer :: a
    integer, dimension(:), target, allocatable :: b
    logical :: point_to_b
    integer :: stat

    allocate(b(10))
    b = (/ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 /)
    write(*, *) "Should I point to B?"
    read(*, *) point_to_b
    if ( point_to_b ) then
        a => b
    else
        allocate(a(10))
    end if
    deallocate(a, stat=stat)
    if ( stat /= 0 ) nullify(a)
end program don

更好的方法可能是将数组指针替换为同时具有指针和标志的类型。

program don
    implicit none
    type :: my_array_pointer_type
        integer, dimension(:), pointer :: array
        logical :: master
    end type my_array_pointer_type
    type(my_array_pointer_type) :: a
    integer, dimension(:), target, allocatable :: b
    logical :: point_to_b
    integer :: stat

    allocate(b(10))

    b = (/ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 /)
    write(*, *) "Should I point to B?"
    read(*, *) point_to_b
    if ( point_to_b ) then
        a%master = .false.
        a%array => b
    else
        a%master = .true.
        allocate(a%array(10))
    end if

    if (a%master) then
        deallocate(a%array)
    else
        nullify(a%array)
    end if
end program don

如果您将另一个指针指向a 然后尝试销毁它,这些建议都不会帮助您。在这种情况下,我真的建议重新考虑您的整个项目设计。

【讨论】:

非常感谢您的回答。你已经完全了解情况了。我还得出结论,您最后一个带有附加标志的方法是前进的唯一方法(没有其他东西会指向指针,所以那里没有问题)。我希望能够避免这种情况,但似乎真的没有其他办法了。【参考方案2】:

您的数据结构有多复杂?也就是说,在给定时间有多少指针同时关联到单个目标? Fortran 标准在对通过allocate 语句实例化的目标进行引用计数时完全保持沉默,即,它没有指定一种机制来确保在所有指针都解除关联时释放目标内存。内置垃圾回收正是为什么在 99.9% 的情况下,allocatable 属性优于 pointer 属性的原因。在极少数情况下,您绝对必须使用指针,并且它的目标分配在其他子程序中,然后将调用链传递给不同的过程,跟踪指针所有权将成为一场噩梦。

如果您可以使用现代 Fortran 编译器 (2008+),以下模块可能会有所帮助;仅供参考,在模块中声明的变量会自动继承 save 属性。目标foo 是一个私有模块变量,ref_counter 跟踪与foo 关联的指针数量。通过重载deallocate 语句,我们可以安全地使无效或无效+释放。与call deallocate(some_ptr, dealloc_stat)

module foo_target

  ! Explicit typing only
  implicit none

  private
  public :: deallocate, nullify, point_at_foo

  ! Neither foo nor ref_counter are public
  real, pointer      :: foo(:) => null()
  integer, protected :: ref_counter = 0

  interface deallocate
     module procedure deallocate_ptr
  end interface deallocate

  interface nullify
     module procedure nullify_ptr
  end interface nullify

contains

  subroutine deallocate_ptr(ptr_to_foo, return_stat)
    ! Dummy arguments
    real, pointer, intent (in out) :: ptr_to_foo(:)
    integer,       intent (out)    :: return_stat
    ! Local variables
    enum, bind(C)
       enumerator :: DEALLOC_ERROR=1, NO_ASSOC, BAD_ASSOC
    end enum
    integer :: alloc_stat

    ! Initialize
    return_stat = 0

    block_construct: block

      ! Check association
      if (ref_counter == 0 .or. .not. associated(foo)) then
          return_stat  = NO_ASSOC
          exit block_construct
      else if (.not. associated(ptr_to_foo, foo)) then
          return_stat = BAD_ASSOC
          exit block_construct
      end if

      if (ref_counter > 1 ) then
          ! Further associations still persist, only nullify
          call nullify(ptr_to_foo)
      else if (ref_counter == 1) then
          ! No remaining associations, nullify and deallocate
          call nullify(ptr_to_foo)
          deallocate(foo, stat=alloc_stat)
          ! Check deallocation status
          if (alloc_stat /= 0) return_stat = DEALLOC_ERROR
      end if

    end block block_construct

  end subroutine deallocate_ptr


  subroutine nullify_ptr(ptr_to_foo)
      ! Dummy arguments
      real, pointer, intent (in out) :: ptr_to_foo(:)

      ! Terminate association
      nullify(ptr_to_foo)

      ! Decrement
      ref_counter = ref_counter - 1

    end subroutine nullify_ptr


    function point_at_foo() result (return_value)
      ! Dummy arguments
      real, pointer :: return_value(:)

      ! Establish association
      return_value => foo

      ! Increment
      ref_counter = ref_counter + 1

    end function point_at_foo

end module foo_target

【讨论】:

我使用了一些类似的结构,但在我看来,只有当您创建一个全新的引用派生类型并重载分配并实现终结时,引用计数的全部功能才能获得(与您的nullify_ptr 和 point_at_foo。【参考方案3】:

也许它对某人有用。我将我的引用计数的简单实现推送到 GitHub,它使您能够跟踪对变量的引用。当引用数为 0 时,对象会自动释放。链接https://github.com/LadaF/Fortran-RefCount

它基于论文Car - A reference counting implementation in Fortran 95/2003 和书籍Scientific Software Design: The Object-Oriented Way 的想法。

引用对象本身存储指向另一个对象refptr 的指针,该对象存储指向数据本身的指针以及指向它的引用数量。分配被重载,以便更新引用计数。引用对象的最终确定会根据需要从引用计数中减去。

  type refptr
    integer :: ref_count = 0
    class(*), allocatable :: data
  contains
    final :: refptr_finalize
  end type

  type :: ref
    type(refptr),pointer :: ptr => null()
    integer :: weak = 0 !1.. this is a weak reference, -1.. make weak references to this
  contains
    procedure :: assign_star
    generic :: assignment(=) => assign_star
    ...
    procedure :: pointer => ref_pointer
    final :: ref_finalize
  end type

  interface ref
    module procedure ref_init
    module procedure ref_init_1
  end interface

那么使用就很简单了:

type(ref) :: a, b

a = 42
print *, "a:", a%value(0)

!this does not copy the value, it points the reference
b = a
print *, "b:", b%value(0)

!sets a to point to something new, b still points to 42
a = 2.3

免责声明:自动定稿功能刚刚实施,并未进行过多测试。到目前为止,我已经将它与手动调用一起使用以在 gfortran-4.8 中完成。这已经过广泛的测试。

【讨论】:

以上是关于如何判断一个 fortran 数组指针是直接分配的,还是与另一个对象相关联?的主要内容,如果未能解决你的问题,请参考以下文章

如何将可分配数组传递给 Fortran 中的子例程

Fortran如何编写非零元素

如何得到指针指向的数组的长度

Fortran - 可分配派生类型的可分配数组

Fortran 子程序中的数组分配

在fortran中只为二维数组分配一维