Reputation: 21
I use the pointers and types in my Fortran code, but still have no sureness that everything is correct. One typical example:
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
I cannot find the answer for the following: can I be sure that nullification of the pointer B does not create any changes in the target Barr(:)?
Upvotes: 2
Views: 192
Reputation: 2981
So everything you're doing is technically correct, but as you can see from the comments it's going to make a lot of Fortran developers twitchy.
To answer your specific questions first: in your code section
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 => Barr(n)
associates B
with Barr(n)
, removing any association B
had beforehand. The only reason you would need to do "cleanup" was if B
was responsible for managing some memory (i.e. if it pointed to something that nothing else pointed to). Since you're only using B
to point at memory managed in other ways, there's no cleanup to do.nullify(B)
nullifies B
, and has no effect on Barr(n)
, correct. It may be worth reading the difference between nullify and deallocate. Put simply, nullify(B)
points B
at null()
, and doesn't affect B
's previous target, while deallocate(B)
deallocates B
's target (if this is a valid operation), and leaves B
disassociated.nullify
. The lines nullify(B)
; B => Barr(n)
will point B
at null()
and then immediately point B
at Barr(n)
. This is equivalent to just pointing B
at Barr(n)
without the nullify
.Historically, Fortran's party piece on the performance front was that it assumed its variables weren't aliased. This means that if you're working with a variable you assume that the contents of that variable won't change unless you explicitly change them. Making this assumption allows an optimising compiler to make a bunch of optimisations which wouldn't be possible otherwise.
In modern fortran, there are (at least) two ways of causing variables to be aliased. The first is passing the same variable (or parts of that variable) to two different arguments of the same procedure, and the second is by using pointers. In your code, you are aliasing Barr(n)
by pointing B
at it.
In the best case, the compiler will know that you're aliasing a variable, and the worst that will happen is that you might miss out on some optimisations. In the worst case, however, the compiler won't know that you're aliasing a variable, and will make optimisations that assume no aliasing. This can very easily break your code, in hard-to-notice, hard-to-debug kinds of ways. In order to understand which of the two cases you'll be dealing with, you need to understand in detail how Fortran thinks about aliasing. It's much easier to just avoid aliasing altogether.
Instead of using pointers to store arrays of variable size, consider using allocatable arrays. For example, your code section
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
would become
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
Then, instead of using pointers to refer to specific bits of variables, consider just using the variables directly, or using associate if the variables are too verbose. For example, your code section
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
would become
do n=1,na
associate(B => Barr(n))
B%r1(:) = 1
B%r2(:) = 2
!...
CALL checkr(B%r1,B%r2)
!...
end associate
enddo
Note that B
here is not a pointer, and you should not declare B
before the associate
statement. From the compiler's point of view, B
is simply another way of accessing Barr(n)
. The relevant section of the Fortran 2018 draft standard is section 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.
Upvotes: 2