Programmieren in Fortran 90/95


  1. Module als Weiterentwicklung der veralteten COMMON-Blöcke von FORTRAN 77
  2. Fortran 90/95 bietet noch eine weitere Möglichkeit zum bisher kennengelernten "pass by reference scheme" um Informationen zwischen einzelnen Programmeinheiten (Hauptprogramm und Unterprogrammen) zu teilen und auszutauschen.

    Diese zweite Möglichkeit, Werte zwischen einzelnen Programmteilen auszutauschen, besteht in der Verwendung von Modulen. In einem module-Programmblock können diejenigen Variablen deklariert werden, deren Werte zwischen den einzelnen Programmeinheiten ausgetauscht werden sollen. Innerhalb der Programmeinheiten, in denen das jeweilige Modul verwendet wird, kann bei Bedarf der Wert der im Modul deklarierten Variablen verändert werden. Sollen in einem Modul Konstanten vereinbart werden, deren Werte in den Programmeinheiten zwar verwendet, aber nicht verändert werden sollen, so werden diese wie üblich mit dem Attribut parameter versehen.

    Der Aufbau (Syntax) eines einfachen Moduls zum Austausch von Werten bzw. Einbinden von Konstanten:

  module <Name des Moduls>
     implicit none
     save    ! stellt sicher, dass der Inhalt in den Speicherplaetzen
             ! zwischen den einzelnen Einbindevorgaengen in den
             ! einzelnen Programmeinheiten unveraendert bleibt

     ! Deklaration der Konstanten
          <Datentyp>, parameter :: <Name der Konstante> = <Wert>
          ...
     ! Deklaration der Variablen
          <Datentyp> :: <Name der Variablen>
          ...
  end module <Name des Moduls>

Natürlich können in Modulen auch Datenfelder vereinbart werden.

Module werden vor Beginn des Hauptprogramms deklariert, während Unterprogramme hinter dem Hauptprogramm stehen sollten.

Soll in einer Programmeinheit der Inhalt eines vorher deklarierten Modules verwendet werden, so wird das Modul mit

  use <Name des Moduls>

unmittelbar hinter der program- bzw. der subroutine- oder der function-Anweisung eingebunden (d.h. noch bevor das übliche

    implicit none

folgt).

Wird man z.B. der Wert der Kreiszahl pi (eine Konstante) in mehr als einer Programmeinheit gebraucht, so kann man pi innerhalb eines Moduls deklarieren und dieses Modul in die entsprechenden Programmeinheiten einbinden (pi_module.f90).

 module kreiszahl
  implicit none
  save
     real, parameter :: pi = 3.141593
end module kreiszahl   

program kreis
  implicit none
  real :: radius, umfang
  ! Deklaration der Function
  real :: area  

  write(*,*) 'Berechnung von Umfang und Flaeche eines Kreises'
  write(*,*) 'Geben Sie den Radius ein:'
  read(*,*) radius
  call kreisumfang (radius,umfang)
  write(*,*) 'Umfang : ', umfang
  write(*,*) 'Flaeche: ', area(radius)
 
end program kreis

subroutine kreisumfang(r,u)
 use kreiszahl 
   implicit none
   real, intent(in)  :: r
   real, intent(out) :: u
      u = 2.0 * pi * r
   return   
end subroutine kreisumfang


real function area (r)
use kreiszahl
   implicit none
   real, intent(in) :: r
   area = pi * r * r
   return
end function area

Im folgenden Beispiel (parabel_module.f90) wird ein Modul verwendet, um die Koeffizienten einer quadratischen Funktion zwischen dem Hauptprogramm und der
real function f(xarg) auszutauschen.

module koeffizienten
   implicit none
   save                 ! stellt sicher, dass die (an den Speicherplaetzen
                        ! der in dem modul deklarierten Variablen)         
                        ! abgelegten Werte zwischen den einzelnen
                        ! Programmeinheiten, in denen das Modul 
                        ! eingebunden wird, erhalten bleiben 
   real :: a, b, c
end module koeffizienten


program parabel
  use koeffizienten    ! damit sind die in dem Module koeffizienten
                        ! enthaltenen Definitionen bekannt
   implicit none
   integer :: i
   real    :: x
   real    :: f

10 format(6X,A$)
20 format(6X,A3,G12.5,A5,G12.5)
   write(*,*) 'Bitte geben Sie zu f(x) = a*x**2 + b*x + c die Koeffizienten ein'
   write(*,10) 'a = '; read(*,*) a
   write(*,10) 'b = '; read(*,*) b
   write(*,10) 'c = '; read(*,*) c
   write(*,*) 'Zu welchem Wert x soll der Funktionswert f(x) berechnet werden?'
   write(*,10) 'x = '; read(*,*) x
   write(*,20) 'f (',x,' ) = ', f(x) 

end program parabel


real function f(xarg)
  use koeffizienten      ! damit sind die in dem Module koeffizienten
                         ! enthaltenen Definitionen einschliesslich
                         ! der im Hauptprogramm zugewiesenen Werte bekannt
  implicit none 
  real, intent(in) :: xarg
  f = a * xarg * xarg + b * xarg + c 
  return 
end function f

Im obigen Beispiel wird das Modul koeffizienten genutzt, um Speicherplatz für die Koeffizienten a, b und c anzulegen. Im Hauptprogramm werden für Variablen a, b und c vom Anwender Zahlenwerte einzulesen in den zugehörigen Speicherplätzen abgelegt.

     real function f(xarg)
stehen aufgrund der Anweisung
     use koeffizienten
die Datentypvereinbarungen von a, b und c sowie die in den Speicherplätzen abgelegten Koeffizientenwerte zur Verfügung.

Eigenschaften von Modulen

  • Durch eine Modul-Definition wird der Speicherbereich für die in dem Modul deklarierten Konstanten und Variablen angelegt.
  • Durch die use-Anweisung wird dieser Speicherbereich für andere Programmeinheiten nutzbar gemacht.
  • Innerhalb der Programmeinheiten, in denen das Module über die use-Anweisung eingebunden wurde, kann aus diesen Speicherplätzen Information gelesen und geschrieben werden, es sei denn, dass in dem Modul Konstanten (Variablen mit dem Attribut parameter) vereinbart wurden, deren Speicherplätze sind schreibgeschützt.
  • Werden aus einem Modul nur ein oder wenige deklarierte Konstanten oder Variablen benötigt, so bindet man diese in die Programmeinheit mit dem Attribut only, gefolgt von einem einfachen Doppelpunkt ein:
          use < Name des Moduls > ,only : < Name(n) der Variablen/Konstanten >

Hat man in einem Programm sehr viele Konstanten zu deklarieren, deren Werte in den einzelnen Programmeinheiten gebraucht werden, ist das module-Konzept von Fortran 90/95 unschlagbar:

Vorteile von Modulen bei der Konstanten-Deklaration

  • Der module-Deklarationsblock mit den für alle Programmeinheiten wichtigen Konstanten ist klar identifizierbar.
    Evtl. notwendige Änderungen und Anpassungen können an einer zentralen Stelle leicht vorgenommen werden.
  • Konstanten lassen sich auch in Modulen durch das Attribut parameter vor unbeabsichtigten Modifikationen schützen.
  • Das Einbinden der in Modulen enthaltenen Informationen ist eindeutig und klar:
          use <Name des Moduls>
  • Werden aus einem Modul nur ein oder wenige deklarierte Konstanten benötigt, so bindet man diese in die Programmeinheit mit dem Attribut only, gefolgt von einem einfachen Doppelpunkt ein:
          use < Name des Moduls > ,only : < Name(n) der Konstanten >

Aus diesem Grunde sollten in größeren, aus einzelnen Programmeinheiten bestehenden Programmen zum Austausch von Konstantenwerten stets Module eingesetzt werden. Dies ist besonders sinnvoll, wenn viele Konstantenwerte gleichzeitig in verschiedenen Programmeinheiten verwendet werden sollen, zum Beispiel, wenn man ein Programmpaket erstellen möchte, um einfache Probleme aus der Elektrodynamik zu lösen, kann man alle häufig verwendeten physikalischen Konstanten in einem Modul zu vereinbaren und dieses Modul in den einzelnen Programmeinheiten einbinden.

module physikalische_Konstanten
    implicit none
    save
    real, parameter ::  pi   = 3.141593     ! Kreiszahl pi
    real, parameter ::  e    = 1.6022e-19   ! Elementarladung [C]
    real, parameter ::  me0  = 9.1095e-31   ! Ruhemasse des Elektrons [kg]
    real, parameter ::  c    = 2.99792e8    ! Vakuumlichtgeschwindigkeit [m/s]
    real, parameter ::  eps0 = 8,8542e-12   ! el. Feldkonstante [C/Vm]
    real, parameter ::  mu0  = 4.*pi*1.e-7  ! Magnetische Feldkonstante [Vs/Am] 
end module physikalische_Konstanten


programm einfache_Elektrodynamik
    use physikalische_Konstanten
    implicit none
      ...
      ...
end programm einfache_Elektrodynamik


real function Feldstaerke_Zylinderkondensator(...,...)
    use physikalische_Konstanten
    implicit none
      ... 
end function Feldstaerke_Zylinderkondensator


real function Feldstaerke_Plattenkondensator(...,...)
    use physikalische_Konstanten
    implicit none
      ... 
end function Feldstaerke_Plattenkondensator


real function Kapazitaet_Plattenkondensator(...,...)
    use physikalische_Konstanten
    implicit none
      ... 
end function Kapazitaet_Plattenkondensator


real function Kapazitaet_Zylinderkondensator(...,...)
    use physikalische_Konstanten
    implicit none
      ... 
end function Kapazitaet_Zylinderkondensator


real function induzierter_Strom_in_linearem_Leiter(...,...)
    use physikalische_Konstanten
    implicit none
      ... 
end function induzierter_Strom_in_linearem_Leiter 

! plus weitere Unterprogramme

Einsatzmöglichkeiten von Modulen

Module kann man auch dazu verwenden, um nicht nur Konstanten, sondern auch die Werte von Variablen zwischen den Programmeinheiten auszutauschen. Dabei können die Unterprogramme auch dazu eingesetzt werden, um die an den entsprechenden Speicherplätzen abgelegten Werte willentlich zu verändern. Die veränderten Variablenwerte stehen dann der nächsten Programmeinheit, in der das Modul verwendet wird, zur Verfügung.

Um die Informationen aus den Speicherplätzen der in dem Modul deklarierten Variablen (und Konstanten) entnehmen zu können, reicht es, das Modul einzubinden. Sobald ein Modul eingebunden wurde, können die Werte aller Variablen (nicht der Konstanten) innerhalb der Programmeinheit verändert werden. Wird der einer in dem Modul zugewiesenen Variablen ein neuer Wert zugewiesen, so wird dieser sogleich an dem entsprechenden Speicherplatz der Variablen eingetragen.

Eine gewisse Gefahr besteht nun darin, dass in eingebundenen Modulen enthaltene Variablen versehentlich modifiziert werden. Im Vergleich zum Unterprogrammaufrufen mit dem zugehörigen "pass by reference scheme", bei dem über die korrespondierende Liste an aktuellen und formalen Parametern exakt die Schnittstelle definiert ist, über die zwischen aufrufender Programmeinheit und dem Unterprogramm Informationen ausgetauscht werden, ist die Informationsübertragung über Module weniger restriktiv.

Module, die Unterprogramme enthalten (engl. module procedures)

Neben der Deklaration von Konstanten und Variablen lassen sich ganze Unterprogramme in Module einfügen. Sobald in einer anderen Programmeinheit das Modul durch eine use-Anweisung eingebunden wird, steht (stehen) in dieser Programmeinheit die im Modul enthaltene Unterprogrammroutine(n) zur Verfügung.

Die Syntax eines Moduls, welches ein Unterprogramm enthält:

  module <Name des Moduls>
     implicit none
     save    ! stellt sicher, dass der Inhalt in den Speicherplaetzen
             ! zwischen den einzelnen Einbindevorgaengen in den
             ! einzelnen Programmeinheiten unveraendert bleibt
     ! Deklaration der Konstanten und Variablen 
       ...
     contains
          ! normaler Unterprogrammcode
               ...
  end module <Name des Moduls>

Als Beispiel wird in einem einfachen Programm zur Berechnung von Fläche und Umfang eines Kreises (kreis.f90) die Funktion area_kreis als module procedure eingesetzt.

module kreisflaeche
   implicit none
   save
   real, parameter :: pi = 3.141593
   contains 
       real function area_kreis (r)
          implicit none
          real, intent(in) :: r
          
          area_kreis = pi * r * r
       return
       end function area_kreis     
end module kreisflaeche

program kreis
   use kreisflaeche

   implicit none
   real :: radius
   
   
   write(*,'(1X,A$)') 'Geben Sie den Radius des Kreises ein: '
   read(*,*) radius
   
   write(*,*) 'Die Flaeche des Kreises betraegt: ', area_kreis(radius)
   write(*,*) 'Der Umfang des Kreises betraegt:  ', 2.0 * pi * radius
   
end program kreis

Vorteile des Konzepts von Unterprogrammen in Modulen (engl. module procedures)

  • Bei der Compilierung einer "module procedure" wird stets geprüft,
    ob beim Aufruf dieses Unterprogramms die Datentypen der aktuellen Parameter
    mit den Datentypen in der Deklaration des Unterprogramms angegebenen formalen Parametern tatsächlich übereinstimmen.

  • Bei functions braucht der Datentyp der über Module eingebundenen "module procedure functions" nicht mehr deklariert zu werden.

Um zeigen zu können, wie gut der Datentyp-Check beim Aufruf von module procedures funktioniert, wird das Beispielprogramm nochmals um eine real function volume_kugel(r) ergänzt, die hinter dem Hauptprogrammende angefügt wurde. (kreis2.f90):

module kreisflaeche
   implicit none
   save
   real, parameter :: pi = 3.141593
   contains 
       real function area_kreis (r)
          implicit none
          real, intent(in) :: r
          
          area_kreis = pi * r * r
       return
       end function area_kreis     
end module kreisflaeche

program kreis
   use kreisflaeche
   
   implicit none
   real :: radius
   real :: volume_kugel

   write(*,'(1X,A$)') 'Geben Sie den Radius des Kreises ein: '
   read(*,*) radius
   
   write(*,*) 'Die Flaeche des Kreises betraegt: ', area_kreis(radius)
   write(*,*) 'Der Umfang des Kreises betraegt:  ', 2.0 * pi * radius
   write(*,*) 'Das Volumen einer Kugel betraegt: ', volume_kugel(radius)
   
end program kreis


real function volume_kugel(r)
   use kreisflaeche, only : pi
   implicit none
   real, intent(in) :: r
   volume_kugel = 4.0/3.0 * pi * r**3
return
end function volume_kugel

Um das unterschiedliche Verhalten des Compilers bei einem Datentyp-Fehler in Zusammenhang mit

  1. der module procedure area_kreis(radius) und
  2. der Function am Ende des Hauptprogramms volume_kugel(radius)
zu provozieren, wird im Hauptprogramm absichtlich der Datentyp von radius
   real :: radius
auf den Datentyp integer verstellt
   integer :: radius
und das Verhalten des Compilers beobachtet. Bei der Compilierung erhält man z.B. mit dem Salford FTN95-Compiler
  1. bei der module procedure-Routine einen Error
    kreis2.F90(25) :
    error 327 - In the INTERFACE to AREA_KREIS (from MODULE KREISFLAECHE), 
    the first dummy argument (R) was of type REAL(KIND=1),
    whereas the actual argument is of type INTEGER(KIND=3)
    
  2. und der Unterprogramm-Routine nur eine Warning
    kreis2.F90(32) : 
    warning 676 - In a call to VOLUME_KUGEL from another procedure, 
    the first argument was of type INTEGER(KIND=3), it is now REAL(KIND=1)
    

Im ungünstigsten Fall werden Warnings übersehen oder durch spezielle Compiler-Flags unterdrückt, so dass ein fehlerhaftes Executable erzeugt werden kann.

Fazit: Für eine sorgfältige Programmentwicklung in Fortran 90/95 empfiehlt es sich also, Unterprogramme in Module zu kapseln, um beim Compilieren eine explizite Datentyp-Prüfung mit evtl. Fehlermeldungen zu erhalten und von der FORTRAN 77 - Version mit separaten Unterprogrammen (ohne das Modul-Konstrukt von Fortran 90/95) Abstand zu nehmen.

  1. Das interface-Konstrukt
  2. Das interface-Konstrukt bietet eine weitere Möglichkeit ein explizites Interface zu schaffen. Mit einem separaten interface-Block kann man - wie bei den module procedures - erreichen, dass der Compiler bei der Übersetzung des Programmcodes prüft, ob die Datentypen von aktuellen und formalen Parametern zwischen aufrufender Programmeinheit und Unterprogramm tatsächlich übereinstimmen. Sollte dies an einer Stelle nicht der Fall sein, bricht der Compiler mit einer Fehlermeldung den Übersetzungsvorgang an der kritischen Stelle ab.

    Beispiel (interface_pi_module.f90):

    module kreiszahl
      implicit none
      save
         real, parameter :: pi = 3.141593
    end module kreiszahl   
    
    program kreis
      use kreiszahl
      implicit none
      
    interface
    subroutine kreisumfang(r,u)
    use kreiszahl
       implicit none
       real, intent(in)  :: r
       real, intent(out) :: u
    end subroutine kreisumfang
    
    real function area (r)
    use kreiszahl
       implicit none
       real, intent(in) :: r
    end function area
    
    end interface
    
      real :: radius, umfang
      ! Deklaration der Function nicht mehr notwendig,
      ! wenn interface-Block den Deklarationsteil der function 
      ! enthaelt
      !real :: area  
    
      write(*,*) 'Berechnung von Umfang und Flaeche eines Kreises'
      write(*,*) 'Geben Sie den Radius ein:'
      read(*,*) radius
      write(*,*) 'pi = ', pi
      call kreisumfang (radius,umfang)
      write(*,*) 'Umfang : ', umfang
      write(*,*) 'Flaeche: ', area(radius)
     
    end program kreis
    
    subroutine kreisumfang(r,u)
    use kreiszahl
       implicit none
       real, intent(in)  :: r
       real, intent(out) :: u
          u = 2.0 * pi * r
       return   
    end subroutine kreisumfang
    
    
    real function area (r)
    use kreiszahl
       implicit none
       real, intent(in) :: r
       area = pi * r * r
       return
    end function area
    

    Der interface-Block im Hauptprogramm enthält jeweils pro "angebundenen" Unterprogramm

    Die Verwendung des interface-Konstrukts führt ebenso wie das Verfahren der module procedures dazu, dass während des Compilierens eine explizite Prüfung stattfindet, ob die Datentypen der aktuellen Parameter (beim Unterprogrammaufruf) tatsächlich mit den formalen Parameters (die im Unterprogramm deklariert wurden) übereinstimmen.

    Der interface-Block wird ebenfalls eingesetzt, wenn vom einem Fortran 90/95 - Programm aus auf externe FORTRAN 77 -, C- oder vorcompilierte Bibliotheks-Routinen zugegriffen werden soll.

    Das interface-Konstrukt bietet des weiteren eine elegante Möglichkeit, dynamisch allokierte Datenfelder an Unterprogramme zu übergeben, ohne dass dem Unterprogramm über die Liste der aktuellen Parameter oder ein eingebundenen Modul die Anzahl der Komponenten in den einzelnen Dimensionen mitgeteilt werden müsste (vgl. Übergabe von Datenfeldern an Unterprogramme ohne Größenangaben (engl. assumed-shape arrays) ).

    Mit dem interface-Konstrukt hat man folgende schematischen Struktur des Deklarationsteils des Hauptprogrammteils:

    Aufbau des Deklarationsteils im Hauptprogramm

    program < Programmname>
       ! Module einbinden mit
       use < Name des Moduls >
       ! bzw.
       use < Name des Moduls >, only : < Namen der benötigten Konstanten, 
                                         Variablen oder module procedure > 
       implicit none
    
       interface
           ! Deklarationsteil des Unterprogramms bis 
           ! einschliesslich der formalen Parameter      
           ! Endezeile des Unterprogramms
         
           ! evtl. weitere Unterprogramme nach gleichem Schema 
       end interface
    
        ! Deklaration der Konstanten
        ! evtl. nach Datentyp sortiert
        ! und immer gut dokumentiert
     
        ! Deklaration der Variablen
        ! evtl. nach Datentyp sortiert
        ! und immer gut dokumentiert
    
    
        ! evtl. Deklaration der Datentypen der Functions
        ! (falls diese nicht schon als module procedure 
        ! oder im interface-Block angebunden)
    

Zurück zur Vorlesungsseite


Heidrun.Kolinsky@uni-bayreuth.de
(Dr. Heidrun Kolinsky, Rechenzentrum der Universität Bayreuth, Gebäude NW2, Raum 159, Universitätsstraße 30, D-95440 Bayreuth, Tel. 0921/55-2687)