pointers - Fortran 中类型结构的重新指向

标签 pointers fortran

我在 Fortran 代码中使用了指针和类型,但仍然不确定一切是否正确。一个典型的例子:

program CheckArray

integer :: na = 10, nb=20
integer :: n
type arrtype
  real, pointer :: r1(:) => null()
  real, pointer :: r2(:) => null()
end type
type(arrtype), pointer :: Barr(:) => null()
type(arrtype), pointer :: B       => null()

!-----------

allocate(Barr(1:na))
do n=1,na
  allocate(Barr(n)%r1(-nb:nb),Barr(n)%r2(-nb:nb))
enddo

!----------- from here the lines where I need help

do n=1,na
  B => Barr(n)     ! <- do I need to cleanup B before this operation?
  B%r1(:) = 1
  B%r2(:) = 2
  !...
  CALL checkr(B%r1,B%r2)
  !...
  nullify(B)       ! <- 1) only B nullified, but not Barr(n)? 
                   !    2) do I need this operation?
enddo

end program

我找不到以下问题的答案:我能否确定指针 B 的无效不会在目标 Barr(:) 中造成任何更改?

最佳答案

所以你所做的一切在技术上都是正确的,但正如你从评论中看到的那样,这会让很多 Fortran 开发人员感到不安。

具体问题

首先回答您的具体问题:在代码部分

do n=1,na
  B => Barr(n)     ! <- do I need to cleanup B before this operation?
  B%r1(:) = 1
  B%r2(:) = 2
  !...
  CALL checkr(B%r1,B%r2)
  !...
  nullify(B)       ! <- 1) only B nullified, but not Barr(n)? 
                   !    2) do I need this operation?
enddo
  • 您不需要“在此操作之前清理 B”。调用 B => Barr(n)BBarr(n) 关联,删除 B 之前的任何关联。您需要进行“清理”的唯一原因是 B 是否负责管理某些内存(即,如果它指向其他没有指向的东西)。由于您仅使用 B 来指向以其他方式管理的内存,因此无需进行清理。
  1. nullify(B) 使 B 无效,并且对 Barr(n) 没有影响,正确。可能值得一读 nullify 之间的区别和 deallocate 。简而言之,nullify(B)B 指向 null(),并且不影响 B前一个目标,而 deallocate(B) 解除分配 B 的目标(如果这是有效操作),并让 B 解除关联。<
  2. 您不需要无效nullify(B) 行; B => Barr(n) 会将 B 指向 null(),然后立即将 B 指向 巴尔(n)。这相当于仅将 B 指向 Barr(n),而不使用 nullify

为什么不鼓励使用指针

从历史上看,Fortran 在性能方面的亮点是 assumed它的变量不是 aliased 。这意味着,如果您正在使用变量,您会假设该变量的内容不会更改,除非您显式更改它们。做出这个假设允许优化编译器进行一系列优化,否则这是不可能的。

在现代 Fortran 中,(至少)有两种方法可以使变量产生别名。第一种是将相同的变量(或该变量的一部分)传递给同一过程的两个不同参数,第二种是使用指针。在您的代码中,您通过将 B 指向它来为 Barr(n) 加上别名。

在最好的情况下,编译器会知道您正在为变量添加别名,而最坏的情况是您可能会错过一些优化。然而,在最坏的情况下,编译器不会知道您正在为变量添加别名,并且会在假设没有别名的情况下进行优化。这很容易以难以察觉、难以调试的方式破坏您的代码。为了了解您将处理这两种情况中的哪一种,您需要详细了解 Fortran 如何看待别名。完全避免混叠要容易得多。

更好的方法

不要使用指针来存储可变大小的数组,而是考虑使用 allocatable数组。例如,您的代码部分

integer :: na = 10, nb=20
integer :: n
type arrtype
  real, pointer :: r1(:) => null()
  real, pointer :: r2(:) => null()
end type
type(arrtype), pointer :: Barr(:) => null()

allocate(Barr(1:na))
do n=1,na
  allocate(Barr(n)%r1(-nb:nb), Barr(n)%r2(-nb:nb))
enddo

会变成

integer :: na = 10, nb=20
integer :: n
type arrtype
  real, allocatable :: r1(:)
  real, allocatable :: r2(:)
end type
type(arrtype), allocatable :: Barr(:)

allocate(Barr(1:na))
do n=1,na
  allocate(Barr(n)%r1(-nb:nb), Barr(n)%r2(-nb:nb))
enddo

然后,不要使用指针来引用变量的特定位,而是考虑直接使用变量,或者使用 associate如果变量太详细。例如,您的代码部分

do n=1,na
  B => Barr(n)     ! <- do I need to cleanup B before this operation?
  B%r1(:) = 1
  B%r2(:) = 2
  !...
  CALL checkr(B%r1,B%r2)
  !...
  nullify(B)       ! <- 1) only B nullified, but not Barr(n)? 
                   !    2) do I need this operation?
enddo

会变成

do n=1,na
  associate(B => Barr(n))
    B%r1(:) = 1
    B%r2(:) = 2
    !...
    CALL checkr(B%r1,B%r2)
    !...
  end associate
enddo

请注意,这里的B不是指针,并且您不应在associate语句之前声明B。从编译器的角度来看,B 只是访问 Barr(n) 的另一种方式。相关章节Fortran 2018 draft standard是第 19.5.1.6 节:

[...] Execution of an ASSOCIATE [...] statement establishes an association between each selector and the corresponding associate name of the construct.

[...]

Each associate name remains associated with the corresponding selector throughout the execution of the executed block. Within the block, each selector is known by and may be accessed by the corresponding associate name. On completion of execution of the construct, the association is terminated.

关于pointers - Fortran 中类型结构的重新指向,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70510099/

相关文章:

c++ - `this == &x` 是确定指针 (this) 和引用 (x) 是否指向同一对象的正确方法吗?

c - 将 strtof 值分配给 C 中的二维数组

c++ - 使用类名的模板值作为指针

arrays - FORTRAN - 子程序中的可分配数组

module - Fortran 模块中类型之间的循环依赖

linux - 在 Fortran 中读取大型 HDF5 数据集时 Valgrind 挂起

c++ - C++ vtables中的双重间接

c++ - C++ 中 typedef 和指针的规则是什么?

fortran - 当类型在编译时已知时,是否有额外的开销调用具有多态派生类型的子例程?

interface - 如何在 Fortran 界面中使用用户定义类型