Fortran im Wandel der Zeit
Seite 3: Beispiele
Beispiele en masse
Listing 3 enthält eine Funktion, ihren Aufruf in einem Hauptprogramm und die Deklaration und Benutzung von Feldern. Es ist möglich, beide Codeteile in einer Datei zu speichern, was im Sinne einer sauberen Strukturierung jedoch nicht zu empfehlen ist.
1 program function_example
2 double precision x(10), y(10)
3 integer k
4 double precision scalar_product
5 do 100 k=1,10
6 x(k) = k**2
7 y(k) = sin(x(k))
8 write(*,*), x(k), y(k)
9 100 continue
10 write(*,*) scalar_product(x, y, 10)
11 end
12
13 double precision function scalar_product(x, y, n)
14 double precision x(n), y(n)
15 integer n, k
16 scalar_product = 0.0d0
17 do 100 k=1,n
18 scalar_product = scalar_product + x(k)*y(k)
19 100 continue
20 end
Listing 3: Funktion mit Aufruf
Im Listing sind alle Variablen explizit deklariert und für die Gleitkommazahlen kommt doppelte Genauigkeit zum Einsatz. Im Hauptprogramm ist die Funktion mit dem Typ ihres Rückgabewertes (Zeile 4) ebenfalls deklariert, was nötig ist, da sie sonst sonst implizit als Typ real angesehen würde, was einen Type-Mismatch nach sich zöge. In der Signatur der Funktion in Zeile 13 ist der Typ des Rückgabewertes dem Schlüsselwort function vorangestellt. Alternativ lässt der Name der Funktion auch in ihr wie eine Variable mit dem entsprechenden Typ deklarieren. In den Zeilen 5 und 6 sind zwei Beispiele für die in Fortran vorhandenen mathematischen Funktion zu erkennen (** bezeichnet die Potenzfunktion).
Listing 4 stellt eine Subroutine zum Vertauschen der Werte zweier Variablen und ihre Verwendung in einem Hauptprogramm dar:
1 program subroutine_example
2 integer i, j
3 i = 1
4 j = 2
5 write(*,*) "vorher: i= ", i, "j = ", j
6 call swap(i, j)
7 write(*,*) "nachher: i= ", i, "j = ", j
8 end
9
10 subroutine swap(i, j)
11 integer i, j, k
12 k = i
13 i = j
14 j = k
15 end
Listing 4: Wertetausch
Es zeigt, dass Fortran mit Referenzen der Parameter arbeitet (Call by reference). Das sorgt dafür, dass auch im Hauptprogramm nach dem Aufruf der Subroutine die Werte der Variablen i und j vertauscht sind. In vergleichbarem C-Code wäre das ohne das Verwenden von Referenzen oder Zeigern nicht der Fall.
Ein weiteres charakteristisches und weit verbreitetes Element in Fortran-77-Programmen sind die sogenannten Common-Blöcke, die globale Variablen zusammenfassen. Jeder Block bezeichnet jedoch nur einen gemeinsamen Speicherbereich. Wie Entwickler ihn in einzelnen Subroutinen und Funktionen auf Variablen verteilen, steht ihnen frei. Listing 5 zeigt ein (nicht empfehlenswertes) Beispiel:
1 program common_blocks
2 common / cb / x, y
3 real x, y
4 x = 1.0
5 y = 2.0
6 write(*,*) 'im Hauptgrogramm: ', x, y
7 call sub()
8 end
9
10 subroutine sub()
11 common / cb / z
12 double precision z
13 write(*,*) 'in der Subroutine: ', z
14 end
Listing 5: common-Block
Im Hauptprogramm und in der Subroutine kommt der gleiche Common-Block mit dem Namen cb zum Einsatz, das Hauptprogramm interpretiert ihn jedoch mit zwei Gleitkommazahlen x, y einfacher und die Subroutine mit nur einer (z) von doppelter Genauigkeit. Beidesmal wird auf den gleichen gemeinsamen (common) Speicher zugegriffen, er wird aber Variablen unterschiedlichen Typs zugewiesen. Dass Fehler in dem Kontext besonders häufig passieren, ist klar.
In der Vor-Fortran-90-Welt gibt es weder dynamische Felder noch selbstdefinierte, strukturierte Datentypen. Auch Zeiger gehören nicht zum Standard. Viele Compiler-Hersteller kennen jedoch Cray-Pointer: ganzzahlige Variablen, die mit C-Pointern vergleichbar und kompatibel sind. Mit einer include-Anweisung lassen sich Dateien einbinden, die dann oft mit der Endung .inc versehen sind. Oft werden in Legacy-Code so globale Variablen in Common-Blöcken eingebunden.
Ganz alter Code (vor dem Standard Fortran IV, weit vor Fortran 77) verfügt noch nicht über die do-Schleife. Das führt zu abenteuerlichen Konstrukten: Listing 6 enthält die reale Codestruktur einer alten Funktion in einem Klimamodell:
1 IF(A.LE.1.0E-08) GO TO 10
2 B=ATAN(C/D)
3 IF(D) 11,10,12
4 11 B=B+E
5 GO TO 13
6 12 IF(C) 14,13,13
7 14 B=B+2.0*E
8 GO TO 13
9 10 IF(C) 15,16,17
10 15 B=1.5*E
11 GO TO 13
12 16 B=0.0
13 GO TO 13
14 17 B=E/2.0
15 13 F=B/G
Listing 6: Klimamodell
In ihm kommt das erwähnte arithmetische if zum Einsatz (Zeilen 3, 9). Je nachdem ob der Wert in der Klammer hinter dem if kleiner, gleich oder größer als Null ist, springt das Programm zu dem entsprechenden der drei dahinter angegebenen Labels. Solcher Code widerspricht so ziemlich allem, was man heute unter guter, strukturierter Programmierpraxis versteht und ist kaum lesbar.
Auf alle Details von Fortran 77 soll hier nicht eingegangen werden. Wer in die Welt vor Fortran 90 eintauchen will oder mit altem Code oder Codeteilen zu tun hat, kann in alten Auflagen entsprechender Bücher (z.B. [5]) stöbern. Viele davon sind antiquarisch für Centbeträge erhältlich.
Insgesamt hat Fortran 77 damit einige Möglichkeiten, die dazu verleiten, unsauberen und damit fehleranfälligen Code zu programmieren. Andererseits kann bei einer konsequent expliziten Typisierung und dem Verzicht auf abenteuerliche Konstrukte ein schneller, leicht lesbarer Code entstehen.
Ein Vorteil von Fortran 77 ist die leichte Ähnlichkeit zu C-Code. Da die Datentypen gegenseitige Entsprechungen haben (C: float – Fortran: real, double – double precision oder real(8), int – integer) gibt es an der Stelle keine Probleme. Man sollte die Entsprechungen jedoch bei den jeweiligen Compilern genau überprüfen. Auch Felder lassen sich von einer Sprache zur anderen etwa bei Funktionsaufrufen übergeben, da die C-Pointer mit den Referenzen der Fortran-Felder kompatibel sind. Zu beachten ist bei mehrdimensionalen Feldern die umgekehrte Speicherung: C speichert sie zeilen-, Fortran spaltenweise. Listing 7 zeigt ein Beispiel für einen Aufruf der Fortran-Subroutine aus Listing 4 von einem C-Programm:
1 #include <stdio.h>
2
3 extern int swap_(int*, int*);
4
5 int main() {
6 int i=1, j=2;
7 printf("vorher: i = %i, j =%i\n",i,j);
8 swap_(&i,&j);
9 printf("nachher: i = %i, j =%i\n",i,j);
10 return 0;
11 }
Listing 7: Aufruf Fortran aus C
Man beachte die Adressoperatoren für die integer-Variablen in C, denn Fortran kennt eben nur "Call by reference". Der eingesetzte GNU-Compiler übersetzte zuerst mit gfortran -c swap.f die Fortran-Subroutine, die für sich alleine in einer Datei swap.f steht, und erzeugte so die Objektdatei swap.o. Im Anschluss wurde der analoge Schritt für das C-Hauptprogramm aus Listing 7 mit gcc ausgeführt. Danach ließ sich mit gfortran listing7.o swap.o aus beiden Objektdateien ein ausführbares Programm generieren. Je nach Compiler ist der Unterstrich an den Namen der Fortran-Subroutine (Zeilen 3 und 8 im C-Programm) anzuhängen.