在派生类型之间传递的 Nopass 过程指针导致分段错误

Nopass procedure pointer passed between derived types causes Segmentation fault

我想在现代 Fortran 中的两个 类 之间传递过程指针。 这个过程指针应该

  1. 从第二个对象中调用
  2. 访问第一个对象的组件,而不将其作为伪参数。

这里有一个清晰的示例,想象一下对 ODE 求解器进行面向对象的包装:

module test_funptr
    implicit none
    public

    type, public :: ode_solver
        integer :: NEQ = 0
        procedure(ode_api), pointer, nopass :: f => null()
    contains
        procedure :: run
    end type ode_solver

    type, public :: ode_problem
        integer :: NEQ = 10
        procedure(ode_api), pointer, nopass :: yprime => null()
    contains
        procedure :: init
    end type ode_problem

    abstract interface
        subroutine ode_api(NEQ,YDOT)
            integer, intent(in) :: NEQ
            real(8), intent(inout) :: YDOT(NEQ)
        end subroutine ode_api
    end interface
contains
    ! Initialize problem variables
    subroutine init(this,NEQ)
        class(ode_problem), intent(inout) :: this
        integer, intent(in) :: NEQ

        ! Associate function pointer
        this%yprime => problem_api
    contains
        ! nopass ODE solver API
        subroutine problem_api(NEQ,YDOT)
            integer, intent(in) :: NEQ
            real(8), intent(inout) :: YDOT(NEQ)

            integer :: i

            print *, 'entered problem API with NEQ=',NEQ
            forall(i=1:NEQ) YDOT(i) = real(i,8)
        end subroutine
    end subroutine init

    subroutine run(this)
        class(ode_solver), intent(inout) :: this

        real(8) :: ydot(this%neq)

        ydot = 0.0

        print *, 'enter solver run with NEQ=',this%NEQ
        print *, 'is function associated? ',associated(this%f)

        call this%f(this%neq,ydot)
    end subroutine run
end module test_funptr

program test
    use test_funptr

    type(ode_solver) :: solver
    type(ode_problem) :: prob

    call prob%init(10)

    ! Associate ode solver
    solver%neq = prob%NEQ
    solver%f => prob%yprime

    call solver%run()
end program test

这个程序 returns 与 gfortran-10:

 enter solver run with NEQ=          10
 is function associated?  T

Program received signal SIGILL: Illegal instruction.

程序似乎关联正确,但无法调用。我是在传递过程指针时做错了什么,还是在做一些不合标准的事情?我担心 contained 子例程可能超出范围,但如果是这样,我该如何实现此行为?

棘手的部分当然是函数应该从另一个变量实例访问数据。

在宿主过程超出范围后调用指向内部过程的过程指针是非法的。

Fortran 2015 N2123的草案在NOTE 15.17中提到了这一点

NOTE 15.17
An internal procedure cannot be invoked using a procedure pointer from either Fortran or C after the host instance completes execution, because the pointer is then undefined. While the host instance is active, however, if an internal procedure was passed as an actual argument or is the target of a procedure pointer, it could be invoked from outside of the host subprogram.

... an example follows

通常,内部程序是使用蹦床实现的。也就是说,一段放在堆栈上的可执行代码,可以访问本地范围并调用过程本身。然后指针是指向蹦床的指针。一旦宿主函数超出范围,指向堆栈的指针将无效。

正如指出的那样,内部(contained)过程不是可行的方法,因为它们不能成为过程指针的目标。希望这会被编译器捕获。

我找到了一种优雅的方法来实现在两个 classes 之间传递接口过程的目标:

  1. class 1 需要 调用 该函数:它必须包含指向 class 2
  2. 的指针
  • nopass 函数应该在这个 class 内部,作为一个内部过程(这样,它永远不会超出范围)
  • 这个 class 必须包含一个(多态)指针,指向 class 2
  • 中的实例化对象
  1. class 2 包含实际实现,它应该实例化一个包含相同接口函数的抽象类型,但派生类型作为伪参数

这里我提供了一个有效的实现:

module odes 
    implicit none

    type, abstract, public :: ode_problem
           integer :: NEQ
       contains
           procedure(ode_api), deferred :: fun
    end type ode_problem

    type, public :: ode_solver
         integer :: NEQ
         class(ode_problem), pointer :: problem => null()
         contains
             procedure :: init
             procedure :: run
    end type ode_solver

    abstract interface
       subroutine ode_api(this,YDOT)
           import ode_problem
           class(ode_problem), intent(inout) :: this
           real(8), intent(out) :: YDOT(this%NEQ)
       end subroutine ode_api
    end interface

    contains

    ! Associate problem to ODE solver
    subroutine init(this,my_problem)
        class(ode_solver), intent(inout) :: this
        class(ode_problem), intent(in), target :: my_problem

        this%neq = my_problem%NEQ
        this%problem => my_problem

    end subroutine init

    ! call the nopass f77 interface function
    subroutine run(this)
       class(ode_solver), intent(inout) :: this
       real(8) :: YDOT(this%NEQ)
       integer :: i 

       if (.not.associated(this%problem)) stop 'solver not associated to a problem'

       ! This will be in general passed to another function as an argument 
       call ode_f77_api(this%NEQ,YDOT)

       contains

         subroutine ode_f77_api(NEQ,YDOT)
             integer, intent(in) :: NEQ
             real(8), intent(out) :: YDOT(NEQ)

             ! This is just a nopass interface to this problem's function that can
             ! access internal storage
             call this%problem%fun(YDOT)
         end subroutine ode_f77_api

    end subroutine run    

end module odes

! Provide an actual implementation
module my_ode_problem
   use odes
   implicit none

        type, public, extends(ode_problem) :: exp_kinetics
            real(8) :: k = -0.5d0
            contains
               procedure :: fun => exp_fun
        end type exp_kinetics

   contains

        subroutine exp_fun(this,YDOT) 
            class(exp_kinetics), intent(inout) :: this
            real(8), intent(out) :: YDOT(this%NEQ)
            integer :: i

            forall(I=1:this%NEQ) YDOT(i) = this%k*real(i,8)
            print 1, this%NEQ,(i,YDOT(i),i=1,this%NEQ)

            1 format('test fun! N=',i0,': ',*(/,10x,' ydot(',i0,')=',f5.2,:))

        end subroutine exp_fun

end module my_ode_problem

program test_fun_nopass
        use odes
        use my_ode_problem
        implicit none

        type(exp_kinetics) :: prob
        type(ode_solver) :: ode

        prob%NEQ = 10
        call ode%init(prob)

        call ode%run()

        stop 'success!'
end program test_fun_nopass          

这个节目returns:

test fun! N=10: 
           ydot(1)=-0.50
           ydot(2)=-1.00
           ydot(3)=-1.50
           ydot(4)=-2.00
           ydot(5)=-2.50
           ydot(6)=-3.00
           ydot(7)=-3.50
           ydot(8)=-4.00
           ydot(9)=-4.50
           ydot(10)=-5.00
STOP success!