我在 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)
将B
与Barr(n)
关联,删除B
之前的任何关联。您需要进行“清理”的唯一原因是 B 是否负责管理某些内存(即,如果它指向其他没有指向的东西)。由于您仅使用B
来指向以其他方式管理的内存,因此无需进行清理。
nullify(B)
使B
无效,并且对Barr(n)
没有影响,正确。可能值得一读 nullify 之间的区别和 deallocate 。简而言之,nullify(B)
将B
指向null()
,并且不影响B
前一个目标,而deallocate(B)
解除分配B
的目标(如果这是有效操作),并让B
解除关联。<- 您不需要
无效
。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 correspondingassociate 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/