使用类型构造函数分配的多态数组元素在访问时会导致分段错误

Polymorphic array element assigned using type constructor causes segmentation fault when accessed

在学习 Fortran 的一些面向对象特性的过程中,我试图制作一个用户定义类型 (wrapper) 的数组 (group) 和一个多态组件 (obj).多态组件是class(parent),我想将它分配给例如type(child),其中 child 扩展类型 parent

如果我为child使用类型构造函数来分配数组元素group(1)%obj = child(1.),分配似乎成功了,但是,当访问组件时例如group(1)%obj%val,当 运行 可执行文件时发生段错误。只有当多态组件是数组元素时才会发生这种情况。如果我使用可分配标量 obj,分配和后续访问将按预期进行。此外,在数组的情况下,如果我宁愿使用源分配,或者将分配从标量移动到数组元素,我又会得到预期的行为。

所描述的行为是使用 gfortran (9.2.0) 编译时观察到的。使用 ifort (19) 或 nagfor (6.1),代码可以按预期编译和运行。据我了解,&'s on this site and others indicate that what I am trying to do is in principle valid. Looking at a list of gfortran bugs,有许多与多态性相关的问题,但我找不到完全符合我的具体问题的问题。

因此,我的问题是:

这是一个 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

让我们简化您的代码;在决定是否存在真正需要解决的编译器错误时。下面的代码为我提供了 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 程序吗?是的

将程序缩小一点可以告诉我们关于完整案例的什么信息?在最小情况下,这里的问题似乎出在多态变量 (group(1)%obj = parent(1.)) 的固有赋值中——这是现代 Fortran 代码中一个值得注意的问题区域。如果我们用源分配替换这个内部赋值,就没有运行时失败,预期结果:

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

问题示例中的源分配也避免了运行时问题。我们可以在没有源分配的情况下看到大致相同的情况,但对组件进行分配和内部分配或默认初始化。

结论:是的,这是一个 gfortran 错误(/缺乏对正确内部赋值的支持)。