2016-11-09 16 views
4

我正在開發一個面向對象的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 
+0

從簡單的看,沒有明顯的原因爲什麼'non_overridable'屬性被禁止(並且以後不會嘗試覆蓋)。不過,我無法訪問編譯器來重現此問題。 – francescalus

+0

對我來說看起來像一個編譯器錯誤。 – IanH

+0

這與延遲有什麼關係? –

回答

0

要縮小的問題,我刪除從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訪問deferred程序在父類型generic_type),具有空指針;如果是這樣的話,這個故事變得一致)


編輯

gfortran同樣異常行爲,當我們宣佈的Type1爲abstract也會發生。特別是,如果我們改變的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給出call t2 % test(2.0)爲4.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. 

所以,總體來說,它好像綁定名稱square()中的Type1(沒有實現)被錯誤地gfortran與空叫(可能指針)。更重要的是,如果我們從Type2的定義中刪除non_overridable,gfortran也會給出4.0(沒有分段錯誤)。

+0

@roygvib很有意思,謝謝!看起來像gfortran假定即使在從擴展類型指定non_overridable時也調用父例程。 Metcalf,Reid,Cohen在閱讀「Modern Fortran Explained」時發現: - 如果出現'non_overridable'屬性,那麼在類型擴展期間不能覆蓋該類型綁定過程; - 'non_overridable'與'deferred'不兼容,因爲這需要覆蓋類型綁定過程; - 如果繼承的過程具有'non_overridable'屬性,則不允許覆蓋類型綁定過程。 –

+0

嗨,我已經考慮過你的原代碼了,但它仍然在我看來,你的代碼是正確的......我的理解是,無論我們是否將'non_overridable'附加到Type2的類型綁定過程中,它的實現(即Type2_square)應該從Type2的實例中調用。 (我的理解是'non_overridable'禁止使用Type2(例如Type3)的子類型覆蓋square(),所以它對Type2本身的行爲沒有任何影響。) – roygvib

+0

至於句子「'non_overridable'不兼容在「Modern Fortran Explained」的第14.6.1節中(我買了Kindle版本:),我想這意味着我們不能寫'procedure(...),deferred,non_overridable :: square ',即我們不能同時指定兩個關鍵字。另一方面,對於Type1使用'procedure(...),deferred :: square'或對於Type2使用'procedure,non_overridable :: square => Type2_square'可能是可以的(可能...)。 – roygvib