【问题标题】:Fortran's interface operator behavior on allocatable arraysFortran 在可分配数组上的接口操作符行为
【发布时间】:2015-02-26 13:53:39
【问题描述】:

我有一个包含一组可分配数组的派生类型,我正在尝试重载一些运算符。由于我的数组可能变得非常大,我不希望 Fortran 对我的数组进行隐式复制,但我无法弄清楚它实际上做了什么。这是一个说明我的观点的最小程序:

module type_vector_field
  implicit none


  type vector_field
     double precision, dimension(:), allocatable :: f
  end type vector_field

  interface operator(+)
     module procedure vector_field_add
  end interface


contains


  function vector_field_add(vf1, vf2) result(vf3)
    type(vector_field), intent(in) :: vf1, vf2
    type(vector_field) :: vf3

    print*, "vf* addresses in add:  ", loc(vf1), loc(vf2), loc(vf3)
    vf3%f = vf1%f + vf2%f

  end function vector_field_add


  subroutine vector_field_add2(vf1, vf2, vf3)
    type(vector_field), intent(in) :: vf1, vf2
    type(vector_field), intent(out) :: vf3

    print*, "vf* addresses in add2: ", loc(vf1), loc(vf2), loc(vf3)
    vf3%f = vf1%f + vf2%f

  end subroutine vector_field_add2


end module type_vector_field

两个vector_field 的加法可以通过+ 运算符或vector_field_add2 子例程来完成。我使用以下示例程序对此进行了测试:

program main
  use type_vector_field
  implicit none

  integer :: n = 6283
  type(vector_field) :: vf1, vf2, vf3

  allocate(vf1%f(n), vf2%f(n))
  vf1%f = 3.1415
  vf2%f = 3.1415

  !! first version

  allocate(vf3%f(n))
  print*, "vf* addresses in main: ", loc(vf1), loc(vf2), loc(vf3)

  vf3 = vf1 + vf2
  print*, "vf* addresses in main: ", loc(vf1), loc(vf2), loc(vf3)
  print*, vf3%f(n)

  deallocate(vf3%f)

  !! second version

  allocate(vf3%f(n))
  print*, "vf* addresses in main: ", loc(vf1), loc(vf2), loc(vf3)

  call vector_field_add2(vf1, vf2, vf3)
  print*, "vf* addresses in main: ", loc(vf1), loc(vf2), loc(vf3)
  print*, vf3%f(n)

end program main

这是使用 gfortran-4.8.2 编译时的输出:

$ gfortran -g -Wall test.f90 -o test && test
 vf* addresses in main:           6303968          6304032          6304096
 vf* addresses in add:            6303968          6304032  140733663003232
 vf* addresses in main:           6303968          6304032          6304096
   6.2829999923706055     
 vf* addresses in main:           6303968          6304032          6304096
 vf* addresses in add2:           6303968          6304032          6304096
 vf* addresses in main:           6303968          6304032          6304096
   6.2829999923706055 

似乎子例程直接与主范围中定义的vf3 实例一起工作,而操作员在其范围内创建另一个vf3 实例并复制回结果。当我打印数组地址而不是派生类型地址时,我得到:

 vf*%f addresses in main:          39440240         39490512         39540784
 vf*%f addresses in add:           39440240         39490512                0
 vf*%f addresses in main:          39440240         39490512         39591056
   6.2829999923706055     
 vf*%f addresses in main:          39440240         39490512         39540784
 vf*%f addresses in add2:          39440240         39490512                0
 vf*%f addresses in main:          39440240         39490512         39540784
   6.2829999923706055     

因此,运算符似乎将其vf3%f 的地址复制到主作用域的vf3%f 实例中,而子例程直接与主作用域的vf3%f 一起工作。在这两种情况下,我都不明白子例程和运算符的vf3%f 指向哪里。

我的问题:

  • 有没有办法确定运算符和子程序在哪个数组中写入结果?
  • 有没有办法在使用+ 运算符时避免复制,还是我应该放弃这个想法?

【问题讨论】:

  • vf3=vf1+vf3 的情况下您希望会发生什么?
  • 我没有想到这个案例。我希望vf1 的值被添加到vf3 中,这从vue 的内存地址点来看是一团糟,我的子程序vector_field_add2 崩溃了。 无效的内存引用您是否建议我因为您的情况而在不创建临时数组的情况下无法实现+
  • 就是这样。特别是,对于c=a+b,结果a+b 被完全评估,并将结果分配给c。我不会写完整的答案,因为它可能包含在我对stackoverflow.com/q/28241473/3157076 的回答中。 [虽然我不会称之为重复。]

标签: fortran operator-overloading fortran90 dynamic-arrays


【解决方案1】:

据我所知,您遇到的问题与运算符重载无关。

或多或少是偶然的(我推测)您在子例程 vector_field_add2 中有 INTENT(OUT),而在 vector_field_add 中没有它。

INTENT(OUT) 的一个经常被忽视的效果是,如果虚拟参数在进入 vector_field_add2 时具有可分配属性,则所有可分配数组都会被释放。具有可分配属性的组件的类型也是如此。

如果你有

   real, dimension(:), allocatable: my_array

   allocate(my_array(some:thing))

然后做

 call sub1(my_array)

  sub1(a)
    real, dimension(:), intent(out) :: a

不出意外。

如果你这样做

   call sub2(a)

sub2 在哪里

   subroutine sub2(a)
   real, dimension(:), allocatable, intent(out) :: a
   end subroutine sub2

对 sub2 的调用等同于对deallocate 的调用。如果您省略 intent(out) 或替换它,例如使用intent(inout),没有任何反应。

同样的事情发生在你身上,只是你的类型中有可分配组件,所以它更不可见。

否则,使用运算符或直接调用子程序没有区别。它只对代码的清晰性很重要。编译器将带有重载运算符的赋值语句转换为子例程调用。

如果您想了解它是如何完成的,请使用-fdump-tree-original 选项翻译您的程序。这将生成一个文件,其主名称与您的源文件类似,并添加了 .003.original,您可以在其中看到 Fortran 代码的类 C 表示。

【讨论】:

  • vector_field_add 中包含intent(out) 毫无意义(当然),但值得补充的是,对于函数结果变量vf3,我们有vf3%f 最初是未分配的,很多在子程序中? [而且,需要明确的是,这只是回答关于“vf*%f 地址...”的部分?]
  • 实际上,当我省略intent(out) 或将其替换为intent(inout) 时,我在vector_field_add2 中得到了vf3%f 的非零地址。但这并不能帮助我判断我应该更好地使用运算符还是子例程。
  • 也许我很困惑,但你提到了子程序和“赋值语句”。问题是关于定义的操作(重载+)而不是定义的分配。那么,重点必须是函数而不是子程序?也就是说,“编译器将带有重载运算符的赋值语句转换为子例程调用。”我不清楚。你是说编译器会把vf3 = vf1+vf2当作call vector_field_add2(vf1, vf2, vf3)对待?
  • 几乎,但不完全(或足够接近,因此没有区别)它所做的是vf3 = vector_field_add (&vf1, &vf2);,其中 vf3 是一个结构。调用约定略有不同,但还不够重要。
  • 我确实很困惑。我以为你在谈论 Fortran,而不是 C。我现在明白你的意思了。
猜你喜欢
  • 1970-01-01
  • 2020-03-17
  • 1970-01-01
  • 2021-01-20
  • 2015-05-26
  • 1970-01-01
  • 2019-12-27
  • 2018-09-01
  • 2015-12-18
相关资源
最近更新 更多