JohnE
JohnE

Reputation: 30444

Pointers as components of derived types

I understand from this answer (Fortran copy of pointer) that I should try to use allocatable arrays rather than array pointers as components of derived types. I intend to do so but am locked into my current code for the next year and need to be able to use it and understand it until I can make the bigger and better changes next year.

I also think this question is of some general interest because I think fortran behavior here is pretty unintuitive although apparently correct. So there is likely value in better understanding how and why it works (or else I assume the compiler wouldn't let us do this at all, right?).

Sorry for the longish intro. I'll try to do this as one annotated program that has been whittled down as much as possible:

program main

   type tRet
      real :: agi
      real :: wages_tgt(5)          ! compiler won't let me declare as
                                    ! target but later I point to this
      real, pointer :: wages(:)
   end type tRet

   type(tRet), target :: ret             ! I don't quite understand 
   type(tRet), target :: orig1, orig2    ! why but compiler insists
                                         ! these be targets
   ret%wages => ret%wages_tgt(1:1)
   ret%wages = (/ 11. /)
   orig1 = ret
   ret%wages = (/ 99. /)
   orig2 = ret

That's the top half of the program, here's some results with print output shown as comments to the right:

   ! This is not want I want and I was surprised, but it is kind
   ! of explained in the other answer why this happens, so OK...

   print *, "orig1%wages ", orig1%wages    ! 99.
   print *, "orig2%wages ", orig2%wages    ! 99.


   ! But if I copy from orig1 or orig2 into ret then it
   ! works like I wanted it to work in the first place!
   ! But I don't completely understand why it works...

   ret = orig1
   print *, "ret%wages   ", ret%wages      ! 11.
   print *, "orig1%wages ", orig1%wages    ! 11.
   print *, "orig2%wages ", orig2%wages    ! 11.

   ret = orig2
   print *, "ret = orig2 "
   print *, "ret%wages   ", ret%wages      ! 99.
   print *, "orig1%wages ", orig1%wages    ! 99.
   print *, "orig2%wages ", orig2%wages    ! 99.

end program main

I'm happy for any nice explanation of what is going on here. The irony here, I guess, is that I'm not so worried about why this is a bad idea but rather why does my workaround seem to work fine?

Or maybe the easiest way to summarize my question is: What exactly is pointing to what?

Compiler: GNU Fortran (GCC) 4.8.5 20150623 (Red Hat 4.8.5-16)

Upvotes: 3

Views: 551

Answers (2)

JohnE
JohnE

Reputation: 30444

This is sort of a bonus answer to a question I had here but didn't really make explicit. The confusing aspect to how fortran works here IMO is that you end up with circular pointers that leads to unintuitive behavior (even if it correct according to the f90 specification).

But by explicitly pointing from orig1%wages to orig1%wages_tgt (and similarly for orig2) you can avoid the circular pointers, at least to some degree. Here's the same code as in the question, but with the some explicit pointing added.

ret%wages => ret%wages_tgt(1:1)
ret%wages = (/ 11. /)

orig1 = ret
orig1%wages => orig1%wages_tgt(:size(ret%wages))   ! *** added code ***

ret%wages = (/ 99. /)

orig2 = ret
orig2%wages => orig2%wages_tgt(:size(ret%wages))   ! *** added code ***

print *, "orig1%wages ", orig1%wages    ! 11.
print *, "orig2%wages ", orig2%wages    ! 99.

ret = orig1
print *, "ret%wages   ", ret%wages      ! 11.
print *, "orig1%wages ", orig1%wages    ! 11.
print *, "orig2%wages ", orig2%wages    ! 99.

ret = orig2
print *, "ret = orig2 "
print *, "ret%wages   ", ret%wages      ! 99.
print *, "orig1%wages ", orig1%wages    ! 11.
print *, "orig2%wages ", orig2%wages    ! 99.

Hence, by keeping orig1 & orig2 pointers distinct (and avoiding circular pointing), you can copy orig1 to ret without the side effect of changing orig2.

A remaining strange thing here, however, is that if I test with associated, it claims orig1 does not point to orig2 even though I explicitly pointed in that way and the behavior seems to also reflect that:

print *, "assoc?", associated(orig1%wages, orig1%wages_tgt) ! F

Upvotes: 1

Ross
Ross

Reputation: 2179

What is happening here is that when you copy a derived data type, different things happen for each component of the derived type. When you do orig1 = ret:

  • Declared arrays, such as wages_tgt, are assigned new values. This is equivalent to saying orig1%wages_tgt = ret%wages_tgt. Each of these arrays occupy separate places in memory.
  • Pointers, such as wages, are made to point to wherever the source pointer is currently pointing. This is equivalent to saying orig1%wages => ret%wages. The destination of both of these pointers is the same location in memory. In the example here, the only location in memory ever pointed to by any wages is ret%wages_tgt.

With that in mind, your result makes sense to me. Adding some selective additional annotations:

ret%wages => ret%wages_tgt(1:1)

ret%wages = (/ 11. /)   ! so wages_tgt = 11 also

orig1 = ret             ! This is BOTH a copy and re-point
                        ! * orig1%wages_tgt =  ret%wages_tgt (11)
                        ! * orig1%wages     => ret%wages_tgt

ret%wages = (/ 99. /)   ! this changes ret%wages & ret%wages_tgt
                        ! to 99 and also orig1%wages since it is
                        ! also pointing to ret%wages_tgt.

                        ! note that orig1%wages_tgt is UNCHANGED 
                        ! (still 11) but nothing is pointing to it!

Lower down in the code...

ret = orig1  ! ret%wages_tgt = orig1%wages_tgt (11) 
             ! no repointing actually happens this time b/c we have
             ! set up a circular relationship between all the
             ! pointers such that ALL of them point to ret%wages_tgt

Upvotes: 2

Related Questions