Fortran 90函数返回指针

3

我看到了这个问题:

Fortran动态对象

并且被接受的答案让我怀疑我是否安全地编写了以下函数(而不允许内存泄漏)。

   function getValues3D(this) result(vals3D)
     implicit none
     type(allBCs),intent(in) :: this
     real(dpn),dimension(:,:,:),pointer :: vals3D
     integer,dimension(3) :: s
     if (this%TF3D) then
       s = shape(this%vals3D)
       if (associated(this%vals3D)) then
            stop "possible memory leak - p was associated"
       endif
       allocate(vals3D(s(1),s(2),s(3)))
       vals3D = this%vals3D
     else; call propertyNotAssigned('vals3D','getValues3D')
     endif
   end function

当我运行代码时出现此警告,但如果this%vals3D之前(在此函数之前)设置,它不应该关联吗?我目前遇到内存错误,当我引入一个包含此功能的新模块时开始显示它们。
非常感谢任何帮助。
我认为我没有表述清楚。我想制作以下类,并知道如何在内存安全方面实现该类。也就是说:
   module vectorField_mod
   use constants_mod
   implicit none

   type vecField1D
     private
     real(dpn),dimension(:),pointer :: x
     logical :: TFx = .false.
   end type

   contains

   subroutine setX(this,x)
     implicit none
     type(vecField1D),intent(inout) :: this
     real(dpn),dimension(:),target :: x
     allocate(this%x(size(x)))
     this%x = x
     this%TFx = .true.
   end subroutine

   function getX(this) result(res)
     implicit none
     real(dpn),dimension(:),pointer :: res
     type(vecField1D),intent(in) :: this
     nullify(res)
     allocate(res(size(this%x)))
     if (this%TFx) then
       res = this%x
     endif
   end function

   end module

以下代码测试此模块:

   program testVectorField
   use constants_mod
   use vectorField_mod
   implicit none

   integer,parameter :: Nx = 150
   real(dpn),parameter :: x_0 = 0.0
   real(dpn),parameter :: x_N = 1.0
   real(dpn),parameter :: dx = (x_N - x_0)/dble(Nx-1)
   real(dpn),dimension(Nx) :: x = (/(x_0+dble(i)*dx,i=0,Nx-1)/)
   real(dpn),dimension(Nx) :: f
   real(dpn),dimension(:),pointer :: fp
   type(vecField1D) :: f1
   integer :: i

   do i=1,Nx
    f(i) = sin(x(i))
   enddo

   do i=1,10**5
     call setX(f1,f) ! 
     f = getX(f1) ! Should I use this? 
     fp = getX(f1) ! Or this?
     fp => getX(f1) ! Or even this?
   enddo
   end program

目前,我正在使用Windows操作系统。当我按下CTRL-ALT-DEL并查看性能时,“物理内存使用情况历史记录”会在每次循环迭代时增加。因此,我认为我有一个内存泄漏问题。

所以我想重新提出我的问题:这是内存泄漏吗?(每个上述情况的内存都会增加)。如果是,那么有没有办法在仍然使用指针的情况下避免内存泄漏?如果不是,那么到底发生了什么,我应该担心吗?是否有一种方法可以降低这种行为的严重程度?

很抱歉初次提问比较含糊。希望现在更加明确了。


我建议尽可能远离返回指针的函数,因为它们非常容易出错。 - Vladimir F Героям слава
返回子程序中的指针可能更安全,这样说是否公平?我在下面的评论中提到,我必须使用指针。 - Charles
是的,请使用子程序或可分配变量。 - Vladimir F Героям слава
2个回答

3
你是否仅限于使用Fortran 90?在Fortran 2003中,你可以使用可分配函数结果来实现。这样更加安全。如果使用指针函数结果,无论此代码是否有内存泄漏,都取决于如何引用该函数,而你没有展示。如果必须从过程返回指针,则通过子程序参数返回更加安全。
但是...
这个函数是无意义的。在前一行将其作为SHAPE参数引用后,测试此%vals3D的关联状态是毫无意义的。如果指针组件未关联(或具有未定义的指针关联状态),则不允许引用它。
此外,如果指针组件已关联,则你所做的就是调用stop!
也许你已经将代码转录到问题中了?
如果你只需删除从if开始的整个结构,即if (associated(this%vals3D))...,则你的代码可能会有意义。
但是...
  • if this%TF3D is true, then this%vals3D must be associated.
  • when you reference the function, you must use pointer assignment

    array_ptr => getValues3D(foo)
    !          ^
    !          |
    !          + this little character is very important.
    

    Forget that little character and you are using normal assignment. Syntactically valid, difficult to pick the difference when reading code and, in this case, potentially a source of memory corruption or leaks that might go undetected until the worst possible moment, in addition to the usual pitfalls of using pointers (e.g. you need to DEALLOCATE array_ptr before you reuse it or it goes out of scope). This is why functions returning pointer results are considered risky.


您的完整代码存在多个内存泄漏问题。每次分配一个指针类型的变量时,您需要几乎保证会有对应的释放语句。

在您的测试代码中存在一个循环结构。ALLOCATE语句在setter和getter中都被频繁调用。那么对应的DEALLOCATE语句在哪里呢?


我只能使用Fortran 90。在Fortran 90中,派生数据类型中不允许使用可分配数组。我同意,并且知道是否存在内存泄漏取决于我如何引用该函数,这是有帮助的。我应该包含此函数的实现,但它的作用仅是作为getter函数。我从我发布的网站中插入了代码片段,并发现出现了警告。 - Charles
问题中的代码示例中没有函数,因此您不能只是“插入”代码。但是除此之外 - 链接问题中的逻辑与您的代码中的逻辑不匹配 - 也许您正在混淆派生类型中的函数结果和指针组件? - IanH
我忘记接受这个问题的任何答案,而我已经决定这个答案让我朝着正确的方向前进:不要使用函数返回分配的指针;它们太危险了。就像我们在http://stackoverflow.com/questions/24091998/fortran-90-difference-between-compaq-visual-fortran-and-gfortran中讨论的那样。再次感谢。 - Charles

1
每次调用setX时,您类型的x组件的任何先前分配的内存都将泄漏。由于您调用该函数10^5次,因此将浪费100000-1个副本。如果您知道this%x的大小永远不会改变,只需通过检查ASSOCIATED(this%x)是否为真来查看以前的调用是否已经分配了内存。如果是,则跳过分配并直接移动到赋值语句。如果大小发生更改,则必须首先取消分配旧副本,然后再分配新空间。

关于setX的另外两个小注释:虚拟参数xTARGET属性似乎是多余的,因为您从未获取该参数的指针。其次,您类型的TFx组件似乎也是多余的,因为您可以检查x是否已分配。

对于函数getX,为什么不完全跳过分配,并仅设置res => this%x?诚然,这将返回对底层数据的直接引用,这可能是您想避免的。

在你的循环中,
   do i=1,10**5
     call setX(f1,f) ! 
     f = getX(f1) ! 我应该使用这个吗? 
     fp = getX(f1) ! 还是这个?
     fp => getX(f1) ! 或者甚至是这个?
   enddo
如果你采用我上面的更改,fp => getX(f1)将允许你获取到类型的底层x组件的指针。另外两个使用赋值运算符,将从getX的结果中复制数据到f或(如果先前分配)fp中。如果fp未分配,则代码将崩溃。
如果你不想直接访问底层数据,那么我建议getX的返回值应该被定义为一个自动数组,其大小由this%x确定。也就是说,你可以将函数写成
   function getX(this) result(res)
     implicit none
     type(vecField1D),intent(in) :: this
     real(dpn),dimension(size(this%x,1)) :: res
     res = this%x
   end function

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接