Указатели как компоненты производных типов

Из этого ответа я понимаю (Fortran копия указателя), что я должен попытаться использовать выделяемые массивы, а чем указатели на массивы как компоненты производных типов. Я намереваюсь сделать это, но я привязан к своему текущему коду на следующий год, и мне нужно уметь его использовать и понимать, пока я не смогу внести более крупные и лучшие изменения в следующем году.

Я также думаю, что этот вопрос представляет некоторый общий интерес, потому что я считаю, что поведение fortran здесь довольно неинтуитивно, хотя, по-видимому, правильно. Так что, вероятно, есть смысл в лучшем понимании того, как и почему это работает (иначе я предполагаю, что компилятор вообще не позволит нам это сделать, верно?).

Простите за длинное вступление. Я постараюсь сделать это как одну аннотированную программу, которая была максимально сокращена:

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

Это верхняя половина программы, вот некоторые результаты с выводом на печать, показанным в виде комментариев справа:

   ! 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

Я рад любому хорошему объяснению того, что здесь происходит. Ирония здесь, я полагаю, в том, что я не так беспокоюсь о том, почему это плохая идея, а скорее о том, почему мой обходной путь работает нормально?

Или, может быть, самый простой способ резюмировать мой вопрос: Что именно указывает на что?

Компилятор: GNU Fortran (GCC) 4.8.5 20150623 (Red Hat 4.8.5-16)


person JohnE    schedule 08.02.2018    source источник
comment
Когда ваш associated вывод отладки помещается в ваш код? В конце концов, после ret=orig2?   -  person Ross    schedule 08.02.2018
comment
Что касается ваших комментариев о возможных целях: этот вопрос частично их решает.   -  person francescalus    schedule 08.02.2018


Ответы (2)


Здесь происходит следующее: когда вы копируете производный тип данных, для каждого компонента производного типа происходят разные вещи. Когда вы делаете orig1 = ret:

  • Объявленным массивам, например wages_tgt, присваиваются новые значения. Это эквивалентно слову orig1%wages_tgt = ret%wages_tgt. Каждый из этих массивов занимает отдельные места в памяти.
  • Указатели, такие как wages, указываются на то место, куда в данный момент указывает указатель источника. Это эквивалентно слову orig1%wages => ret%wages. Назначением обоих этих указателей является одно и то же место в памяти. В приведенном здесь примере единственное место в памяти, на которое когда-либо указывалось wages, - это ret%wages_tgt.

Имея это в виду, ваш результат имеет для меня смысл. Добавление некоторых выборочных дополнительных аннотаций:

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!

Ниже в коде ...

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
person Ross    schedule 08.02.2018
comment
Я должен добавить, что больше всего сбивало с толку то, что копии ret в orig1 и orig2 по существу устанавливали это в разрешение кругового указателя, которое, как я знаю, довольно хорошо понимаю (я думаю), но это делает правильное использование довольно неинтуитивным IMO, даже если оно правильное и ожидается в соответствии со спецификацией языка f90. - person JohnE; 08.02.2018
comment
Правки хорошие. Я действительно не понимал, что сбивает с толку, поэтому я думаю, что перебор объяснил проблему. - person Ross; 08.02.2018
comment
Объяснение было отличным (особенно текст вверху), я был бы рад получить более подробные объяснения для многих ответов fortran здесь ;-) - person JohnE; 09.02.2018

Это своего рода бонусный ответ на вопрос, который у меня был здесь, но на самом деле он не был явным. Запутанный аспект того, как здесь работает fortran, IMO, заключается в том, что вы получаете круговые указатели, которые приводят к неинтуитивному поведению (даже если оно правильное в соответствии со спецификацией f90).

Но, явно указывая с orig1%wages на orig1%wages_tgt (и аналогично для orig2), вы можете избежать круговых указателей, по крайней мере, до некоторой степени. Вот тот же код, что и в вопросе, но с добавлением явного указания.

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.

Следовательно, сохраняя указатели orig1 и orig2 отдельными (и избегая кругового наведения), вы можете копировать orig1 в ret без побочного эффекта изменения orig2.

Однако остается еще одна странная вещь: если я тестирую с ассоциированным, он утверждает, что orig1 не указывает на orig2, хотя я явно указал на это, и поведение, похоже, также отражает это:

print *, "assoc?", associated(orig1%wages, orig1%wages_tgt) ! F
person JohnE    schedule 08.02.2018