Элемент массива Polymorphi c, назначенный с помощью конструктора типа, вызывает ошибку сегментации при доступе - PullRequest
2 голосов
/ 08 мая 2020

В процессе изучения некоторых объектно-ориентированных функций Fortran я пытаюсь создать массив (group) определенного пользователем типа (wrapper) с компонентом polymorphi c (obj). Компонент polymorphi c имеет class(parent), и я хочу выделить его, например, type(child), где child расширяет тип parent.

Если я использую конструктор типа для child для выделения элемента массива group(1)%obj = child(1.), выделение кажется успешным, однако при доступе к компоненту, например, group(1)%obj%val, при запуске исполняемого файла возникает ошибка сегментации. Это происходит только в том случае, если компонент polymorphi c является элементом массива. Если я использую распределяемый скаляр obj, распределение и последующий доступ будут работать должным образом. Кроме того, в случае с массивом, если я предпочитаю использовать выделение из источника или перемещать выделение из скаляра в элемент массива, я снова получаю ожидаемое поведение.

Описанное поведение наблюдается при использовании gfortran (9.2.0) компилировать. Используя ifort (19) или nagfor (6.1), код компилируется и работает, как ожидалось. Насколько я понимаю, Q & A на этом сайте и другие указывают на то, что то, что я пытаюсь сделать, в принципе верно. Глядя на список ошибок gfortran , можно увидеть ряд проблем, связанных с полиморфизмом, но я не могу найти ни одной, точно соответствующей моей конкретной проблеме c.

Таким образом, мой вопрос: это:

  • Является ли представленный ниже код допустимым Fortran и наблюдаемое поведение из-за ошибки в gfortran?
  • Или, если я виноват в написании недопустимого Fortran (и мне просто повезло чтобы не вызвать WW3 с ifort и nagfor), где моя ошибка?

Вот MCVE, полностью иллюстрирующий то, что я пытаюсь сделать (можно было бы сделать более минимальным, если бы только воспроизвести ошибку) :

module udt_m
  implicit none

  type, abstract :: parent
    real :: val
  end type parent

  type, extends(parent) :: child
  end type child

  interface child
    procedure child_constructor
  end interface

  contains
    function child_constructor(val) result(out)
      implicit none
      real, intent(in)    :: val
      type(child)         :: out
      out%val = val
    end function child_constructor
end module udt_m

program poly_array
  use udt_m
  implicit none

  class(parent), allocatable :: obj

  type :: wrapper
    class(parent), allocatable :: obj
  end type wrapper

  type(wrapper), allocatable :: group(:)

  ! scalar instance
  obj = child(1.)
  if (allocated(obj)) then
    write(*, '(g0)') 'obj allocated'
    write(*, '(*(g0))') 'obj%val=', obj%val
  end if

  ! array wrapped instance
  allocate(group(1))
  group(1)%obj = child(1.) ! constructor assignment seemingly works, later access fails with gfortran
  ! group(1)%obj = obj                            ! workaround: scalar temporary
  ! allocate(group(1)%obj, source=child(1.))      ! workaround: sourced allocation
  ! call move_alloc(from=obj, to=group(1)%obj)    ! Workaround: call move_alloc(from=scalar, to=array element)

  if (allocated(group(1)%obj)) then
    write(*, '(g0)') 'group(1)%obj allocated'
    write(*, '(*(g0))') 'group(1)%obj%val=', group(1)%obj%val ! access causes segmentation fault with gfortran
  end if

end program poly_array

Скомпилировано с использованием:

gfortran -Og -g -fbacktrace -Wall -Wextra -Wpedantic -fcheck=all -std=f2008 -fsanitize=address,undefined -o poly_array.out poly_array.f90

Фактический результат (полученный с помощью gfortran)

./poly_array.out
obj allocated
obj%val=1.00000000
group(1)%obj allocated

Program received signal SIGSEGV: Segmentation fault - invalid memory reference.
...

Ожидаемый результат (полученный с помощью ifort или nagfor):

./poly_array.out
obj allocated
obj%val=1.000000
group(1)%obj allocated
group(1)%obj%val=1.000000

1 Ответ

3 голосов
/ 08 мая 2020

Давайте упростим ваш код; при принятии решения о том, есть ли ошибка компилятора, действительно способная к go. Приведенный ниже код дает мне ошибку сегментации с gfortran 8 и 10.

program poly_array

  type :: parent
    real :: val
  end type parent

  type :: wrapper
    class(parent), allocatable :: obj
  end type wrapper

  type(wrapper), allocatable :: group(:)

  allocate(group(1))
  group(1)%obj = parent(1.)
  write(*, *) group(1)%obj%val

end program poly_array

Является ли эта программа действительной программой Fortran? Да.

Что может небольшое сокращение программы сказать нам о полном кейсе? Проблема здесь в минимальном случае, по-видимому, заключается во внутреннем присваивании c переменной polymorphi c (group(1)%obj = parent(1.)) - заметная проблема c в современном коде Fortran. Если мы заменим это внутреннее присваивание c выделением из источника, сбоя во время выполнения не будет, а ожидаемый результат:

program poly_array

  type :: parent
    real :: val
  end type parent

  type :: wrapper
    class(parent), allocatable :: obj
  end type wrapper

  type(wrapper), allocatable :: group(:)

  allocate(group(1))
  allocate(group(1)%obj, source=parent(1.))
  write(*, *) group(1)%obj%val

end program poly_array

Распределение из источника в примере вопроса также позволяет избежать проблемы времени выполнения. Мы можем увидеть почти то же самое без выделения из источника, но с выделением и присваиванием встроенным c компоненту или инициализацией по умолчанию.

Заключение: да, это ошибка gfortran (/ отсутствие поддержки правильных встроенных c присвоение).

...