尽管已完成,但派生类型的 Fortran 数组和内存泄漏

Posted

技术标签:

【中文标题】尽管已完成,但派生类型的 Fortran 数组和内存泄漏【英文标题】:Fortran array of derived types and memory leaks despite finalization 【发布时间】:2015-04-17 09:16:11 【问题描述】:

虽然我已经编写了最终过程,但我定义了一个派生类型并遇到了一些内存释放问题。代码如下

module ModuleCoordinate
  implicit none

  type :: TCoordinate
    real(8),dimension(:),pointer :: Coordinate => NULL()
    contains
      procedure :: TCoordinateAssignment
      generic,public :: Assignment(=) => TCoordinateAssignment
      final :: TCoordinateDel
  end type TCoordinate

  interface TCoordinate
    module procedure :: TCoordinateInit
  end interface TCoordinate

  contains
    subroutine TCoordinateDel(self)
      type(TCoordinate),intent(inout) :: self
      if(associated(self%Coordinate))deallocate(self%Coordinate)
    end subroutine TCoordinateDel

    subroutine TCoordinateAssignment(O1,O2)
      class(TCoordinate),intent(out) :: O1
      type(TCoordinate),intent(in) :: O2
      if(associated(O2%Coordinate))allocate(O1%Coordinate,source=O2%Coordinate)
    end subroutine TCoordinateAssignment

    type(TCoordinate) function TCoordinateInit(IVal1,IVal2) result(self)
      real(8),intent(in) :: IVal1,IVal2
      allocate(self%Coordinate(2))
      self%Coordinate=(/IVal1,IVal2/)
    end function TCoordinateInit
end module ModuleCoordinate

测试代码如下

program test
  implicit none
  integer(4),parameter :: NLoop=40000
  integer(4) :: i
  do i=1,NLoop
    call TestMemory1()
    call TestMemory2()
  end do
  pause
end program test

subroutine TestMemory1()
  use ModuleCoordinate
  implicit none
  integer(4),parameter :: DN=10
  integer(4) :: i
  type(TCoordinate),dimension(DN) :: a
  do i=1,DN
    a(i)=TCoordinate(1.0_8,1.0_8)
  end do
end subroutine TestMemory1

subroutine TestMemory2()
  use ModuleCoordinate
  implicit none
  type(TCoordinate) :: b1,b2,b3,b4,b5,b6,b7,b8,b9,b10
  b1=TCoordinate(1.0_8,1.0_8)
  b2=TCoordinate(1.0_8,1.0_8)
  b3=TCoordinate(1.0_8,1.0_8)
  b4=TCoordinate(1.0_8,1.0_8)
  b5=TCoordinate(1.0_8,1.0_8)
  b6=TCoordinate(1.0_8,1.0_8)
  b7=TCoordinate(1.0_8,1.0_8)
  b8=TCoordinate(1.0_8,1.0_8)
  b9=TCoordinate(1.0_8,1.0_8)
  b10=TCoordinate(1.0_8,1.0_8)
end subroutine TestMemory2

事实证明,子例程TestMemory2 可以,而TestMemory1 不行,这意味着当声明此派生类型的数组时,最终过程不起作用并且内存泄漏。

但是,如果我在此派生类型的定义中删除Coordinate 右侧的=> NULL(),这两个子例程似乎都运行良好。

释放指针Coordinate 时有何不同? 如果重要的话,编译器是 ifort_2013_sp1.3.174。

【问题讨论】:

您是否为该类型的数组提供了最终子程序? 不...我刚刚添加了它,它确实有效。非常感谢。但是即使我忘记了数组的最终过程,删除“=> NULL()”仍然有效。 【参考方案1】:

在我们看到的定稿过程的描述中(Fortran 2008, 4.5.6.2)

如果实体的动态类型有一个 final 子例程,其虚拟参数具有与正在终结的实体相同的类型参数和等级,则以实体作为实际参数调用它。否则,如果有一个基本的最终子程序,其虚拟参数具有相同的 kind 类型参数作为被最终确定的实体,它以实体作为实际参数调用。否则,此时不会调用任何子程序。

派生类型有一个最终子例程,仅为标量(rank-0)实体提供。为您的 1 级实体最终确定最简单的方法(在这种情况下似乎)是使您拥有的子例程成为元素。

我有点不愿意提及=>NULL() 方面,因为我目前没有办法测试我将要写的内容,但我会推测一下。

如果没有=>NULL() 默认初始化,指针组件具有未定义的关联状态。这意味着,当你这样做时

    b1=TCoordinate(1.0_8,1.0_8)

有趣的事情发生了。

作为作业的一部分,b1 在进入TCoordinateAssignment 时完成。最终确定涉及使用未定义关联状态的指针调用associated。这是不允许的(结果可能会出现任何结果)。

【讨论】:

顺便说一句:仔细考虑可分配组件而不是指针组件可能是一个很好的提醒。

以上是关于尽管已完成,但派生类型的 Fortran 数组和内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

在 Fortran 90 中使用二维数组与派生类型数组

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

GDB 可以用于在 Fortran 90 中打印派生类型的可分配数组的值吗? [复制]

在 fortran 中命名派生类型的良好做法

mpifort -DMPI 编译错误:派生类型“mpi_status”用作实际参数。适用于英特尔 Fortran,但不适用于 GNU Fortran

在派生类型中使用可分配的目标变量