Нечетное поведение строк Фортрана в производном типе после обращения к типу по указателю - PullRequest
0 голосов
/ 21 сентября 2018

Я пытаюсь написать простой модуль для обработки физических единиц в арифметических операциях.Моя цель - создать производные единицы из первичных.

Как вы можете видеть в следующем коде, у меня есть производный тип, а именно unit_t, в котором хранится строка, представляющая саму единицу, степеньединица измерения, коэффициент преобразования (для преобразования в SI), логическая переменная, показывающая, клонируется ли единица, и указатели next и prev, которые указывают на следующую или предыдущую единицу (в случае, если у нас есть комбинация единиц,например kg * m / s**2, так что в основном это связанный список, соединяющий различные устройства друг с другом).

У меня есть функция с именем unit_clone для клонирования основного устройства.Функция unit_int_pow перегружает оператор возведения в степень (**) и просто клонирует данную первичную единицу и обновляет ее экспоненту.Функция units_mul перегружает оператор умножения (*).Эта функция сначала проверяет, клонированы ли два заданных объекта (если нет, то клонирует их), а затем просто соединяет их, используя указатели next и `` `` prev```.

Вот мой код (вы должны скомпилировать его с помощью gfortran)

module units

  implicit none

  type unit_t
    character(len=16) :: symb
    integer :: pow
    real :: conv
    logical :: cloned
    type(unit_t), pointer :: next => null(), prev => null()
  end type unit_t


  ! definitions
  type(unit_t), target :: m = unit_t("m", 1, 1.d0, .false.)
  type(unit_t), target :: km = unit_t("km", 1, 1.d3, .false.)

  type(unit_t), target :: kg = unit_t("kg", 1, 1.d0, .false.)

  type(unit_t), target :: s = unit_t("s", 1, 1.d0, .false.)

  interface operator (**)
    procedure unit_int_pow
  end interface operator (**)


  interface operator (*)
    procedure units_mul
  end interface operator (*)

contains

  !> Cloning a given node (unit)
  function unit_clone(u) result (clone)
    implicit none

    type(unit_t), intent(in) :: u
    type(unit_t), allocatable, target :: clone

    allocate(clone)

    clone%symb = u%symb
    clone%conv = u%conv
    clone%pow = u%pow
    clone%cloned = .true.
    clone%next => u%next
    clone%prev => u%prev
  end function unit_clone


  !> integer powers
  function unit_int_pow(u1, p) result(u)
    implicit none

    type(unit_t), intent(in) :: u1
    integer, intent(in) :: p

    type(unit_t), allocatable, target :: u

    u = unit_clone(u1)
    u%pow = u%pow * p
  end function unit_int_pow


  !> multiplication
  function units_mul (u1, u2) result (u1c)
    implicit none

    type(unit_t), intent(in) :: u1, u2
    type(unit_t), allocatable, target :: u1c, u2c

    if ( u1%cloned ) then
      u1c = u1
    else
      u1c = unit_clone(u1)
    end if

    if ( u2%cloned ) then
      u2c = u2
    else
      u2c = unit_clone(u2)
    end if

    u2c%prev => u1c
    u1c%next => u2c
  end function units_mul
end module units

program test
  use units

  implicit none

  type(unit_t) :: u

  u = kg**2 * m

  print *, u%symb, "^", u%pow, " [expected: kg^2]"
  print *, u%next%symb, "^", u%next%pow, " [expected: m^1]"
  print *, u%next%prev%symb, "^", u%next%prev%pow, " [expected: kg^2]"
end program test

Проблема в том, что я получаю следующий вывод:

kg            ^           2  [expected: kg^2]
 �ȷ2�U        ^           1  [expected: m^1]
 �ȷ2�U        ^           2  [expected: kg^2]

По-видимому, после доступа к модулю next или next%prev (который является в основном заголовком этого короткого связанного списка), код выводит случайный символ вместо symb s.Если я изменю порядок переменных в производном типе, unit_t, например, если я поставлю symb в конце производного типа, я получу правильные symb с, но на этот раз неправильные pow с.

Есть идеи, что является причиной этого довольно странного поведения?


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

module units

  implicit none

  type unit_t
    character(len=16) :: symb
    real :: conv
    real :: pow = 1.d0
    logical :: cloned = .false.
    type(unit_t), pointer :: next => null(), prev => null()
  end type unit_t


  ! units definitions
  type(unit_t), target :: m = unit_t("m", 1.d0)
  type(unit_t), target :: km = unit_t("km", 1.d3)

  type(unit_t), target :: kg = unit_t("kg", 1.d0)

  type(unit_t), target :: s = unit_t("s", 1.d0)


  interface operator (**)
    procedure unit_int_pow
  end interface operator (**)


  interface operator (*)
    procedure units_mul
  end interface operator (*)

contains

  !> Cloning a given node (unit)
  function unit_clone(u) result (clone)
    implicit none

    type(unit_t), intent(in) :: u
    type(unit_t), pointer :: clone

    allocate(clone)

    clone%symb = trim(u%symb)
    clone%conv = u%conv
    clone%pow = u%pow
    clone%cloned = .true.
    clone%next => u%next
    clone%prev => u%prev
  end function unit_clone


  !> integer powers
  function unit_int_pow(u1, p) result(u)
    implicit none

    type(unit_t), intent(in) :: u1
    integer, intent(in) :: p

    type(unit_t), pointer :: u

    if ( u1%cloned ) then
      ! TODO: should be able to handle complex cases like: a * (b * c)**3
      !       most likly, only updating the power of the linked list chain
      !       would do the job
    else
      u => unit_clone(u1)
    end if
    u%pow = u%pow * p
  end function unit_int_pow


  !> multiplication
  function units_mul (u1, u2) result (u2c)
    implicit none

    type(unit_t), intent(in), target :: u1, u2
    type(unit_t), pointer :: u2c


    if ( u2%cloned ) then
      if ( associated(u2%prev) ) then
        u2c => u2%prev%next
      else
        u2c => u2
      end if
    else
      u2c => unit_clone(u2)
    end if


    if ( u1%cloned ) then
      if ( associated(u2%prev) ) then
        u2c%prev => u1%prev%next
      else
        u2c%prev => u1
      end if
    else
      u2c%prev => unit_clone(u1)
    end if

    u2c%prev%next => u2c
  end function units_mul
end module units

1 Ответ

0 голосов
/ 21 сентября 2018

A pointer в Фортране имеет три возможных статуса ассоциации:

  • связанный : указатель фактически указывает на определенные и выделенное переменное / соответствующее хранилище данных (его target);
  • диссоциировано : оно было (или является частью объекта, который был) явно аннулировано или освобожден , или его цель была правильно диссоциирована .
  • undefined : все, что отличается от первого, например, его цель (или стала) undefined , или был освобожден другими способами, помимо вызова deallocate непосредственно в самом указателе, среди других причин.

При выполнении экземпляразавершения подпрограммы (например, когда function units_mul достигает end function), любая несохраненная локальная переменная становится неопределенной.Кроме того, любая allocatable локальная переменная, которая не сохранена или является результатом функции, освобождается, и когда выделяемая сущность освобождается, она также становится неопределенной.

Возвращаясь к вашей проблеме, u2c является выделяемойнесохраненная локальная переменная внутри units_mul функции, с которой вы связываете u1c%next.Когда эта функция достигает конца, u2c завершает свой жизненный цикл и становится неопределенным, в результате чего u1c%next становится также неопределенным, в состоянии, указанном в жаргоне Фортрана как висячий указатель .

Это текст из Стандарта Фортрана, описывающий это явление (несмотря на то, что он ссылается на случай ассоциации хостов модулей, это та же логика):

Примечание 19.10

Указатель отмодуль программы модуля может быть доступен в подпрограмме через ассоциацию использования.Такие указатели имеют время жизни больше, чем цели, объявленные в подпрограмме, если только такие цели не сохранены.Следовательно, если такой указатель связан с локальной целью, существует вероятность того, что когда процедура, определенная подпрограммой, завершит выполнение, цель прекратит свое существование, оставив указатель «висящим».Этот документ считает, что такие указатели имеют неопределенный статус ассоциации.Они не связаны и не разъединены.Они не могут быть снова использованы в программе, пока их статус не будет восстановлен.От процессора не требуется определять, когда цель указателя перестает существовать.

Висячий указатель не является надежным указателем, и компилятор не имеет над ним контроля.Они могут по любой причине продолжать указывать на свой последний адрес памяти (и в некоторых случаях случайно дают ожидаемый результат, иначе значения будут случайными из случайного адреса памяти), но это, безусловно, будет break , и сбой может быть любым, от просто неправильных результатов до SIGSEG error или нарушение адреса памяти .

См. этот пример кода:

program dangling_pointer
  implicit none
  integer, pointer :: p(:)
  integer, allocatable :: a(:)

  call sub1(p)
  print *, 'sub1: ', p
  call sub2(p)
  print *, 'sub2: ', p
  call sub3(p, a)
  print *, 'sub3: ', p
  p => fun4()
  print *, 'fun4: ', p

contains
  subroutine sub1(dummy_p)
    ! the pointer passed as argument outlives the local target
    ! when the procedure ends, it becomes a "dangling pointer"
    integer, pointer :: dummy_p(:)
    integer, allocatable, target :: local_a(:)
    allocate(local_a(5))
    local_a = 100
    dummy_p => local_a
  end
  subroutine sub2(dummy_p)
    ! here the local variable is saved, so it persists. No problem here.
    integer, pointer :: dummy_p(:)
    integer, allocatable, target, save :: saved_a(:)
    allocate(saved_a(5))
    saved_a = 100
    dummy_p => saved_a
  end
  subroutine sub3(dummy_p, out_a)
    ! here the target is a passed argument, so it persists. No problem here.
    integer, pointer :: dummy_p(:)
    integer, allocatable, target :: out_a(:)
    allocate(out_a(5))
    out_a = 100
    dummy_p => out_a
  end
  function fun4() result(result_p)
    ! here the function result will be returned as a pointer. No problem here.
    integer, pointer :: result_p(:)
    allocate(result_p(5))
    result_p = 100
  end
end

С gfortran 9.0.0 я получаю:

 sub1:     14316208           0    14287184           0         100
 sub2:          100         100         100         100         100
 sub3:          100         100         100         100         100
 fun4:          100         100         100         100         100

Редактировать

Я думаю, этот фрагмент подойдет для вашегопроблема:

allocate(u1c%next)
if (u2%cloned) then
  u1c%next = u2
else
  u1c%next = unit_clone(u2)
end if
u1c%next%prev => u1c
...