指针作为派生类型的组成部分

Pointers as components of derived types

我从这个答案 () 了解到,我应该尝试使用可分配数组而不是数组指针作为派生类型的组件。我打算这样做,但明年我被锁定在我当前的代码中,需要能够使用它并理解它,直到明年我可以做出更大更好的改变。

我还认为这个问题具有一定的普遍意义,因为我认为此处的 Fortran 行为非常不直观,但显然是正确的。因此,更好地理解它的工作原理和原因可能很有价值(否则我假设编译器根本不会让我们这样做,对吧?)。

抱歉介绍有点长。我将尝试将此作为一个已被尽可能削减的带注释的程序来完成:

program main

   type tRet
      real :: agi
      real :: wages_tgt(5)          ! compiler won't let me declare as
                                    ! target but later I point to this
      real, pointer :: wages(:)
   end type tRet

   type(tRet), target :: ret             ! I don't quite understand 
   type(tRet), target :: orig1, orig2    ! why but compiler insists
                                         ! these be targets
   ret%wages => ret%wages_tgt(1:1)
   ret%wages = (/ 11. /)
   orig1 = ret
   ret%wages = (/ 99. /)
   orig2 = ret

这是程序的上半部分,下面是一些结果,打印输出显示在右侧的注释中:

   ! This is not want I want and I was surprised, but it is kind
   ! of explained in the other answer why this happens, so OK...

   print *, "orig1%wages ", orig1%wages    ! 99.
   print *, "orig2%wages ", orig2%wages    ! 99.


   ! But if I copy from orig1 or orig2 into ret then it
   ! works like I wanted it to work in the first place!
   ! But I don't completely understand why it works...

   ret = orig1
   print *, "ret%wages   ", ret%wages      ! 11.
   print *, "orig1%wages ", orig1%wages    ! 11.
   print *, "orig2%wages ", orig2%wages    ! 11.

   ret = orig2
   print *, "ret = orig2 "
   print *, "ret%wages   ", ret%wages      ! 99.
   print *, "orig1%wages ", orig1%wages    ! 99.
   print *, "orig2%wages ", orig2%wages    ! 99.

end program main

我很高兴能对这里发生的事情做出很好的解释。我想这里具有讽刺意味的是,我并不那么担心为什么这是一个坏主意,而是为什么我的解决方法似乎工作正常?

也许总结我的问题的最简单方法是:究竟是什么指向什么?

编译器:GNU Fortran (GCC) 4.8.5 20150623 (Red Hat 4.8.5-16)

这里发生的事情是,当您复制派生数据类型时,派生类型的每个组件都会发生不同的事情。当你做 orig1 = ret:

  • 声明的数组,例如wages_tgt,被赋予了新的值。这相当于说 orig1%wages_tgt = ret%wages_tgt。这些数组中的每一个在内存中占据 单独的 个位置。
  • 指针,例如wages,指向源指针当前指向的任何位置。这相当于说 orig1%wages => ret%wages。这两个指针的目的地是内存中 相同的 位置。在此处的示例中,任何 wages 曾经指向的内存中的唯一位置是 ret%wages_tgt.

考虑到这一点,您的结果对我来说很有意义。添加一些选择性的附加注释:

ret%wages => ret%wages_tgt(1:1)

ret%wages = (/ 11. /)   ! so wages_tgt = 11 also

orig1 = ret             ! This is BOTH a copy and re-point
                        ! * orig1%wages_tgt =  ret%wages_tgt (11)
                        ! * orig1%wages     => ret%wages_tgt

ret%wages = (/ 99. /)   ! this changes ret%wages & ret%wages_tgt
                        ! to 99 and also orig1%wages since it is
                        ! also pointing to ret%wages_tgt.

                        ! note that orig1%wages_tgt is UNCHANGED 
                        ! (still 11) but nothing is pointing to it!

代码往下...

ret = orig1  ! ret%wages_tgt = orig1%wages_tgt (11) 
             ! no repointing actually happens this time b/c we have
             ! set up a circular relationship between all the
             ! pointers such that ALL of them point to ret%wages_tgt

这是对我在这里提出的一个问题的额外回答,但并没有真正明确。 Fortran 在 IMO 中的工作方式令人困惑的方面是,您最终会得到导致不直观行为的循环指针(即使它根据 f90 规范正确)。

但是通过从 orig1%wages 显式指向 orig1%wages_tgt(对于 orig2 也是类似的),您至少可以在某种程度上避免循环指针。这是与问题中相同的代码,但添加了一些明确的指向。

ret%wages => ret%wages_tgt(1:1)
ret%wages = (/ 11. /)

orig1 = ret
orig1%wages => orig1%wages_tgt(:size(ret%wages))   ! *** added code ***

ret%wages = (/ 99. /)

orig2 = ret
orig2%wages => orig2%wages_tgt(:size(ret%wages))   ! *** added code ***

print *, "orig1%wages ", orig1%wages    ! 11.
print *, "orig2%wages ", orig2%wages    ! 99.

ret = orig1
print *, "ret%wages   ", ret%wages      ! 11.
print *, "orig1%wages ", orig1%wages    ! 11.
print *, "orig2%wages ", orig2%wages    ! 99.

ret = orig2
print *, "ret = orig2 "
print *, "ret%wages   ", ret%wages      ! 99.
print *, "orig1%wages ", orig1%wages    ! 11.
print *, "orig2%wages ", orig2%wages    ! 99.

因此,通过保持 orig1orig2 指针不同(并避免循环指向),您可以将 orig1 复制到 ret 而不会产生更改 orig2.

然而,这里还有一个奇怪的事情是,如果我用 associated 进行测试,它声称 orig1 没有指向 orig2,即使我明确地指向了 orig2 并且行为似乎还反映:

print *, "assoc?", associated(orig1%wages, orig1%wages_tgt) ! F