【问题标题】:Segmentation fault with deferred functions and non_overridable keyword带有延迟函数和 non_overridable 关键字的分段错误
【发布时间】:2017-03-23 11:12:45
【问题描述】:

我正在开发一个面向对象的 Fortran 代码,用于通过抽象类型支持的多态性进行数值优化。因为这是一个很好的 TDD 实践,所以我尝试在抽象类型 class(generic_optimizer) 中编写所有优化测试,然后应该由每个实例化类运行,例如 type(newton_raphson)

所有优化测试都调用call my_problem%solve(...),在抽象类型中定义为deferred,当然每个派生类型都有不同的实现。

问题是:如果在每个非抽象类中我将延迟函数定义为non_overridable,则会出现分段错误,例如:

Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()

(gdb) where
#0  0x0000000000000000 in ?? ()
#1  0x0000000000913efe in __newton_raphson_MOD_nr_solve ()
#2  0x00000000008cfafa in MAIN__ ()
#3  0x00000000008cfb2b in main ()
#4  0x0000003a3c81ed5d in __libc_start_main () from /lib64/libc.so.6
#5  0x00000000004048f9 in _start ()

经过反复试验,我注意到如果删除 non_overridable 声明,我可以避免该错误。在这种情况下,这不是问题,但我想强制执行这一点,因为此代码不太可能存在两级多态性。我是否违反了标准中的任何要求?

这里是重现错误的示例代码。我一直在使用 gfortran 5.3.0 和 6.1.0 对其进行测试。

module generic_type_module
    implicit none
    private

    type, abstract, public :: generic_type
        real(8) :: some_data
        contains
        procedure (sqrt_interface), deferred :: square_root
        procedure, non_overridable           :: sqrt_test
    end type generic_type

    abstract interface
       real(8) function sqrt_interface(this,x) result(sqrtx)
          import generic_type
          class(generic_type), intent(in) :: this
          real(8), intent(in) :: x
       end function sqrt_interface
    end interface

    contains

    subroutine sqrt_test(this,x)
        class(generic_type), intent(in) :: this
        real(8), intent(in) :: x
        print *, 'sqrt(',x,') = ',this%square_root(x)
    end subroutine sqrt_test

end module generic_type_module

module actual_types_module
    use generic_type_module
    implicit none
    private

    type, public, extends(generic_type) :: crashing
       real(8) :: other_data
       contains
       procedure, non_overridable :: square_root => crashing_square_root
    end type crashing
    type, public, extends(generic_type) :: working
       real(8) :: other_data
       contains
       procedure :: square_root => working_square_root
    end type working

    contains

    real(8) function crashing_square_root(this,x) result(sqrtx)
       class(crashing), intent(in) :: this
       real(8), intent(in) :: x
       sqrtx = sqrt(x)
    end function crashing_square_root
    real(8) function working_square_root(this,x) result(sqrtx)
       class(working), intent(in) :: this
       real(8), intent(in) :: x
       sqrtx = sqrt(x)
    end function working_square_root

end module actual_types_module

program deferred_test
    use actual_types_module
    implicit none
    type(crashing) :: crashes
    type(working)  :: works

    call works%sqrt_test(2.0_8)
    call crashes%sqrt_test(2.0_8)

end program

【问题讨论】:

  • 快速浏览一下,没有明显的理由禁止 non_overridable 属性(以后也不会尝试覆盖)。不过,我无权使用编译器来重现该问题。
  • 在我看来像一个编译器错误。
  • 这与 Deferreds 有什么关系?
  • 请不要在写得很好的代码中使用real(8),它真的很难看。 stackoverflow.com/documentation/fortran/939/data-types/4390/…
  • 谢谢你们的意见 - 看起来这是次要问题,但你认为我应该在 GCC bugzilla 上开一张票吗?

标签: oop fortran fortran2003 fortran2008


【解决方案1】:

为了缩小问题范围,我从 OP 的代码中删除了抽象属性和数据成员,这样

module types
    implicit none

    type :: Type1
    contains
        procedure :: test
        procedure :: square => Type1_square
    endtype

    type, extends(Type1) :: Type2
    contains
       procedure, non_overridable :: square => Type2_square
    endtype

contains

    subroutine test( this, x )
        class(Type1) :: this
        real :: x
        print *, "square(", x, ") = ",this % square( x )
    end subroutine

    function Type1_square( this, x ) result( y )
       class(Type1) :: this
       real :: x, y
       y = -100      ! dummy
    end function

    function Type2_square( this, x ) result( y )
       class(Type2) :: this
       real :: x, y
       y = x**2
    end function

end module

program main
    use types
    implicit none
    type(Type1) :: t1
    type(Type2) :: t2

    call t1 % test( 2.0 )
    call t2 % test( 2.0 )
end program

有了这段代码,gfortran-6 给出了

square(   2.00000000     ) =   -100.000000
square(   2.00000000     ) =   -100.000000

而 ifort-{14,16} 和 Oracle fortran 12.5 提供

square(   2.000000     ) =   -100.0000    
square(   2.000000     ) =    4.000000

我还尝试用子例程替换函数(以打印实际调用了哪些例程):

    subroutine test( this, x )
        class(Type1) :: this
        real :: x, y
        call this % square( x, y )
        print *, "square(", x, ") = ", y
    end subroutine

    subroutine Type1_square( this, x, y )
        class(Type1) :: this
        real :: x, y
        print *, "Type1_square:"
        y = -100      ! dummy
    end subroutine

    subroutine Type2_square( this, x, y )
        class(Type2) :: this
        real :: x, y
        print *, "Type2_square:"
        y = x**2
    end subroutine

所有其他部分保持不变。然后,gfortran-6 给出了

Type1_square:
square(   2.00000000     ) =   -100.000000    
Type1_square:
square(   2.00000000     ) =   -100.000000

而 ifort-{14,16} 和 Oracle fortran 12.5 提供

Type1_square:
square(   2.000000     ) =   -100.0000    
Type2_square:
square(   2.000000     ) =    4.000000 

如果我从上述代码中删除non_overridable,gfortran 会给出与其他编译器相同的结果。因此,这可能是 gfortran + non_overridable 的特定问题(如果上述代码符合标准)...

(OP出现分段错误的原因可能是gfortran访问了具有空指针的父类型(generic_type)中的deferred过程;如果是这种情况,故事就变得一致了。)


编辑

当我们将 Type1 声明为 abstract 时,也会发生 gfortran 的相同异常行为。具体来说,如果我们将 Type1 的定义更改为

    type, abstract :: Type1    ! now an abstract type (cannot be instantiated)
    contains
        procedure :: test
        procedure :: square => Type1_square
    endtype

和主程序一样

program main
    use types
    implicit none
    type(Type2) :: t2

    call t2 % test( 2.0 )
end program

我们得到

ifort-16    : square(   2.000000     ) =    4.000000    
oracle-12.5 : square( 2.0 ) =  4.0
gfortran-6  : square(   2.00000000     ) =   -100.000000  

如果我们进一步将 Type1 中的 square() 设为 deferred(即没有给出实现),从而使代码几乎等同于 OP 的情况,

type, abstract :: Type1  ! now an abstract type (cannot be instantiated)
contains
    procedure :: test
    procedure(Type1_square), deferred :: square  ! has no implementation yet
endtype

abstract interface
    function Type1_square( this, x ) result( y )
        import
        class(Type1) :: this
        real :: x, y
    end function
end interface

然后 ifort-16 和 Oracle-12.5 给出 4.0 和 call t2 % test( 2.0 ),而 gfortran-6 导致分段错误。确实,如果我们编译为

$ gfortran -fsanitize=address test.f90   # on Linux x86_64

我们得到

ASAN:SIGSEGV    (<-- or "ASAN:DEADLYSIGNAL" on OSX 10.9)
=================================================================
==22045==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 
                (pc 0x000000000000 bp 0x7fff1d23ecd0 sp 0x7fff1d23eac8 T0)
==22045==Hint: pc points to the zero page.

总的来说,似乎 Type1 中的绑定名称 square() (没有实现)被 gfortran 错误地调用(可能带有空指针)。更重要的是,如果我们从 Type2 的定义中去掉 non_overridable,gfortran 也会给出 4.0(没有分段错误)。

【讨论】:

  • 非常有趣的@roygvib,谢谢!看起来 gfortran 假设即使从扩展类型指定了 non_overridable ,也会调用父例程。阅读 Metcalf、Reid、Cohen 的“Modern Fortran Explained”,我发现: - 如果出现non_overridable 属性,则在类型扩展期间不能覆盖该类型绑定过程; - non_overridabledeferred 不兼容,因为这需要重写类型绑定过程; - 如果继承的过程具有non_overridable 属性,则不允许覆盖类型绑定过程。
  • 嗨,我再次考虑了您的原始代码,但在我看来您的代码仍然是正确的......我的理解是,无论我们是否将 non_overridable 附加到类型绑定Type2的过程,它的实现(即Type2_square)应该从Type2的一个实例中调用。 (我的理解是non_overridable禁止square()被Type2的子类型(比如Type3)覆盖,所以对Type2本身的行为没有影响。)
  • 至于《现代 Fortran 解释》第 14.6.1 章中的“non_overridable is incompatible with deferred”这句话(我买的是 Kindle 版:),我猜这意味着我们不能写procedure(...), deferred, non_overridable :: square之类的东西,即不能同时指定两个关键字。另一方面,对 Type1 使用 procedure(...), deferred :: square 对 Type2 使用 procedure, non_overridable :: square =&gt; Type2_square 可能是可以的(可能......)。
  • 因此,如果上述解释似乎合理,我将非常感谢您在 GCC bugzilla 上开票(我猜对您的问题 +4 赞成表示“继续”)。在comp.lang.fortran中征求意见可能也很有用,因为这样的线程很多(无论是某些代码符合标准,还是代码或编译器的错误)。
  • 感谢@roygvib,我在 GCC Bugzilla 上开了一张票。干杯!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-11
  • 1970-01-01
  • 1970-01-01
  • 2017-12-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多