2016-11-09 4 views
4

Ich entwickle einen objektorientierten Fortran-Code für die numerische Optimierung mit Polymorphismus, der von abstrakten Typen unterstützt wird. Da es eine gute TDD-Praxis ist, versuche ich, alle Optimierungstests in dem abstrakten Typ class(generic_optimizer) zu schreiben, der dann von jeder instanziierten Klasse ausgeführt werden sollte, z. B. von type(newton_raphson).Segmentierungsfehler mit zurückgestellten Funktionen und non_overridable Schlüsselwort

Alle Optimierungstests verfügen über einen Aufruf an call my_problem%solve(...), der im abstrakten Typ als deferred definiert ist und natürlich eine andere Implementierung in jedem abgeleiteten Typ aufweist.

Die Frage ist: wenn in jeder nicht-abstrakten Klasse I die latente Funktion als non_overridable definieren, ich Segmentierungsfehler erhalten wie:

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() 

Nach einigem trial-and-error, ich habe bemerkt, dass Ich kann den Fehler vermeiden, wenn ich die non_overridable Deklaration entferne. In diesem Fall ist das kein Problem, aber ich wollte dies erzwingen, da für diesen Code zwei Polymorphismeniveaus unwahrscheinlich sind. Habe ich stattdessen irgendwelche Anforderungen aus dem Standard verletzt?

Hier ist ein Beispielcode, der den Fehler reproduziert. Ich habe es mit gfortran 5.3.0 und 6.1.0 getestet.

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

Aus einem kurzen Blick, gibt es keinen offensichtlichen Grund, warum das 'non_overridable' Attribut verboten ist (und später nicht zu überschreiben). Ich habe jedoch keinen Zugriff auf einen Compiler, um das Problem zu reproduzieren. – francescalus

+0

Sieht für mich wie ein Compiler-Fehler aus. – IanH

+0

Was hat das mit Deferreds zu tun? –

Antwort

0

das Problem zu verengen, entfernte ich das abstrakte Attribut und Datenelemente aus dem Code des OP, so dass

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 

Mit diesem Code gfortran-6 gibt

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

während ifort- {14,16} und Oracle fortran 12,5 ergeben

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

Ich habe auch versucht, die Funktionen mit Subroutinen zu ersetzen (zu drucken, die Routinen tatsächlich genannt werden):

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 

mit allen anderen Teilen gleich gehalten. Dann gfortran-6

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

gibt während ifort- {14,16} und Oracle Fortran 12.5 geben

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

Wenn ich non_overridable aus dem oben genannten Codes zu entfernen, gibt gfortran das gleiche Ergebnis wie die anderen Compiler. So kann dies ein spezifisches Problem gfortran + non_overridable (wenn der obige Code ist standardkonformes) ...

(Der Grund, warum OP Segmentierungsfehler bekam, dass gfortran zugegriffen werden die deferred Prozedur in dem Muttertyp (generic_type) mit null-Zeiger, wenn dies der Fall ist, wird die Geschichte konsistent)


bearbeiten

das gleiche außergewöhnliche Verhalten von gfortran tritt auch auf, wenn wir Typ1 erklären als abstract..Insbesondere erhalten wir

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

Wenn wir weiter square() in Typ1 machen deferred wenn wir die Definition von Typ1 als

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

und das Hauptprogramm als

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

    call t2 % test(2.0) 
end program 

ändern zu sein (dh , keine Implementierung gegeben) und so den Code fast gleich dem Fall des OP machen,

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 

Dann gibt Ifort-16 und Oracle-12.5 4.0 mit call t2 % test(2.0), während Gfortran-6 Segmentierungsfehler führt. Wenn wir nämlich als

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

kompilieren wir

bekommen
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. 

Also insgesamt, so scheint es, als ob die Bindung Namen square() in Typ1 (was nicht implementiert) fälschlicherweise durch gfortran (möglicherweise mit null aufgerufen wird Zeiger). Und noch wichtiger, wenn wir non_overridable aus der Definition von Type2 löschen, gibt gfortran auch 4,0 (ohne Segmentierungsfehler).

+0

sehr interessant @roygvib, danke! Sieht so aus, als ob gfortran annimmt, dass die Elternroutine aufgerufen wird, selbst wenn non_overridable von einem erweiterten Typ angegeben wird. Lesen "Moderne Fortran erklärt" von Metcalf, Reid, Cohen, fand ich: - Wenn das 'non_overridable' Attribut erscheint, kann diese typgebundene Prozedur nicht während der Typendung überschrieben werden; - 'non_overridable' ist inkompatibel mit' deferred', da dazu die typgebundene Prozedur außer Kraft gesetzt werden muss; - Das Überschreiben einer typgebundenen Prozedur ist nicht zulässig, wenn die geerbte Version das Attribut 'non_overridable' aufweist. –

+0

Hallo, ich habe Ihren ursprünglichen Code erneut betrachtet, aber es sieht immer noch so aus, dass Ihr Code korrekt ist ... Mein Verständnis ist, dass unabhängig davon, ob wir 'non_overridable' an eine typgebundene Prozedur von Type2 anfügen, deren Implementierung (dh Type2_square) sollte von einer Instanz von Type2 aufgerufen werden. (Mein Verständnis ist, dass 'non_overridable' es verbietet, dass square() von einem Kindtyp von Type2 (zB Type3) überschrieben wird, so dass es keinen Einfluss auf das Verhalten von Type2 selbst hat.) – roygvib

+0

Wie für den Satz ist" non_overridable "inkompatibel mit 'deferred'" in Chap.14.6.1 von "Modern Fortran Explained" (Ich kaufte die Kindle-Version :), ich denke, das bedeutet, dass wir nicht schreiben können etwas wie 'procedure (...), deferred, non_overridable :: square ', dh wir können die beiden Schlüsselwörter nicht gleichzeitig angeben. Auf der anderen Seite ist es wahrscheinlich OK, 'procedure (...), deferred :: square' für Type1 und' procedure, non_overridable :: square => Type2_square' für Type2 (wahrscheinlich ...) zu verwenden. – roygvib

Verwandte Themen