MykolaBorysiv
MykolaBorysiv

Reputation: 21

Re-pointing of the type-structure in fortran

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

Answers (1)

veryreverie
veryreverie

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.

Specific questions

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
  • you don't "need to cleanup B before this operation". Calling 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.
  1. 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.
  2. You don't need the 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.

Why pointers are discouraged

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.

A better approach

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 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.

Upvotes: 2

Related Questions