Programmieren in Fortran 90/95


    1. Computer

    Zunächst wollen wir uns etwas genauer mit der

    Computer-Hardware

    beschäftigen. Versucht man, den prinzipiellen Aufbau von Computern schematisch darzustellen, könnte man folgenden groben Aufbau skizzieren:

    Schematischer
Aufbau eines Computers

    Kernstück eines jeden Computers ist der Prozessor. Er verarbeitet sämtliche eingehenden Informationen in Form von elektrischen Signalpegeln und steuert deren Weiterleitung in den internen Speicher, auf externe Speichergeräte wie z.B. die Festplatte oder auf Ausgabegeräte, wie den Bildschirm oder den Drucker. Oft werden vom Prozesser Informationen an die ALU (Arithmetic Logical Unit) weitergeleitet, die die Rechenoperationen (Additionen, Multiplikationen) durchführt and die Ergebnisse wieder an den Prozessor zurückleitet, wo sie weiterverarbeitet werden.
    Informationen, auf die der Prozessor schnell wieder zugreifen muss, werden im internen Speicher (sogenannter flüchtiger Speicher in Speicherchips) abgelegt. Daten, auf die nicht so schnell zugegriffen zu werden braucht, werden von dem Steuerwerk des Rechners auf der Festplatte (Harddrive) gespeichert. Weitere externe Speichermedien wie Diskette (Floppy Disk), MO-Disks, writeable CDROMs, FireWire-Platten und USB-Memory-Sticks etc. können vom Anwender zur Datensicherung und zur Zwischenspeicherung verwendet werden.

    Mit CPU (Central Processing Unit) bezeichnet man das Kernstück aus Prozessor, ALU und prozessornahem Speicher (Cache). Die CPU steckt auf einen sogenannten Motherboard auf der sich u.a. weitere Steuerchips, weitere Speicherchips und das Bus-System befinden, über das die Daten weitergeleitet werden.

    Speziell in PCs übernimmt die Funktion der Bildschirmausgabe meist eine Graphikkarte; die Ausgabe auf die Festplatte wird von einer speziellen Controllerkarte gesteuert. Manche Hersteller plazieren die Controller-Chips und die Chips für die Graphikkarte manchmal mit auf dem Mainboard. Dies ist in der Regel bei Laptops der Fall.
    Die Bandbreite der Geräte, die man heute als Computer oder Rechner bezeichnet, reicht vom sogenannten "Handheld"

    Palm Handheld IIIc

    über Personal Computer (PCs oder Desktops),

    Desktop PC

    Workstations und Mainframes bis hin zu Supercomputern, bei denen ein Rechner eine ganze Halle einnehmen kann:

    http://www.llnl.gov/asci/platforms/bluepac/

    Das obere Bild stammt aus dem Lawrence Livermore National Laboratory und zeigt die Anlage BluePacific SST
    (
    http://www.llnl.gov/asci/platforms/bluepac/ ). In 168 Schränken sind 5856 CPUs, die zu 1464 Rechenknoten und 168 Ausgabeknoten auf Festplatte verschaltet sind. Pro CPU stehen 384-640 Megabyte und pro Knoten zwischen 1.5 und 2.5 Gigabyte an internen Speicher zur Verfügung . Pro Sekunde soll soll der Rechner "3.9 teraOPS" (1.000.000.000.000 Operationen pro Sekunde) schaffen. Als Hinweis für den Nutzer der Rechnenanlage ist das Bild sinnigerweise mit "Please use more than 1 CPU/node whenever possible!"
    untertitelt. Das Betriebssystem für diesen Supercomputer basiert auf Unix und als Compiler stehen C und Fortran 90 zur Verfügung.

    Die jeweils aktuellste Ranking-Liste der 500 schnellsten Computer der Welt finden Sie unter www.top500.org.

    Computer-Software

    Bei der Software unterscheidet man zwischen System-Software (Betriebssystem-Software) und Anwendungs-Software.
Das Betriebssystem (engl. operating system) ist das Programm, das beim Start eines Rechners geladen wird. Es dient als Schnittstelle zwischen den Rechnerkomponenten (Hardware und BIOS) und den Anwendungen, die auf dem Rechner laufen. Es bietet außerdem grundlegende Funktionen für die Verwaltung und Pflege des Betriebs- und Dateisystems.

Am weitesten verbreitet sind die drei Betriebssysteme Windows von Microsoft, MacOS von Apple Macintosh und die UNIX-Varianten. Zunehmende Verbreitung findet das UNIX-ähnliche System Linux. Weniger verwendet wird hingegen das Betriebssystem OS/2 von IBM. ( http://www.netlexikon.de/)

Auf PCs sind hauptsächlich als Betriebssysteme Windows und Linux im Einsatz. Größere Workstations in wissenschaftlich/ingenieurwissenschaftlichen Umfeld laufen in der Regel unter Unix in der jeweiligen herstellerspezifischen Version (z.B. Solaris, IRIX, Digital Unix). Die Vorteile von Unix liegen in seiner Stabilität. Unix erlaubt das gleichzeitige Durchführen einer fast beliebig großen Anzahl an Programmen und stürzt so gut wie nie ab. In der Regel wird auf einer graphischen Oberfläche mit verschiedenen Fenstern gearbeitet. Die Eingabe der Befehle erfolgt überwiegend textorientiert in einer sogenannten Shell, die als Mittler zwischen den User-Befehlen und dem eigentlichen Betriebssystem fungiert.
Um mit Unix erfolgreich arbeiten zu können, sind Kenntnisse einiger grundlegender Kommandos notwendig (siehe
"Unix für Einsteiger" (deutsch) und "UNIXhelp for Users" (englisch)).

Unter Anwendungssoftware (engl. application programms) versteht man im allgemeinen komplexere Programme, die jeweils für spezielle Einsatzgebiete geschaffen wurden. Typische Anwendungsprogramme im Desktop-Bereich sind Textverarbeitung (engl. word processors), Tabellenkalkulation (engl. spread sheets), Datenbankverwaltung (engl. data base management) und Präsentationssoftware.

Im technisch-wissenschaftlichen Bereich findet man unter anderem CAD(Computer Aided Design)-, CAE(Computer Aided Engineering)-, CIM(Computer Integrated Manufacturing)- und CASE(Computer Aided Software Engineering)-Programme, sowie Computer-Algebra-Systeme (Maple, Matlab) und Compiler.

Stichwort: IT-Kompetenz

Die Anwendungssoftware, die Compiler und die Shell bzw. die Windows-Oberfläche des Betriebssystems stellen die Schnittstelle dar, die es dem menschlichen Anwender ermöglicht, den Computer als Werkzeug zur Lösung spezifischer Aufgaben einzusetzen. Dabei läuft die Kommunikation mit der Computer-Hardware über mehrere Schichten ab:

Schalenmodell User-Anwendungssoftware-Betriebssystem-Hardware

Damit ein menschlicher Anwender die mit elektrischen Spannungspegeln arbeitenden Siliciumchips des Computers erfolgreich für seine Zwecke einsetzen kann, reicht es völlig aus
  1. dass jede Schicht die Informationen so aufbereitet, dass sie von den angrenzenden Schichten verarbeitet werden kann.
  2. Die eigentliche Informationsverarbeitung im Computer findet auf unterster Hardwareebene statt. Dort ist keinerlei Intelligenz angesiedelt, sondern es wird lediglich mit extrem hoher Geschwindigkeit und exakter Genauigkeit in strikter binärer Logik eine Abfolge an Maschinenbefehlen verarbeitet.
  3. Die Intelligenz liegt bei den findigen Entwicklern der Programme, die dafür gesorgt haben, dass der Datenfluss nach innen und nach außen sinnvoll vonstatten geht und natürlich auch bei den Hardwareentwicklern, die funktionsfähige und leistungsfähige Chips konzipieren und herstellen. Nicht zuletzt entscheiden noch die Fähigkeiten des Anwenders und seine Vertrautheit mit den Programmen der zweitoberen Schicht darüber, wie gut es ihm gelingt, den Computer als Werkzeug einzusetzen.
Nach heutiger Technologie reicht es für einen einen einfachen Computeranwender in der Regel aus, mit der Anwendungssoftware vertraut zu sein. Ein typisches Beispiel wären hierfür der Büro-, Verwaltungs- und Arztpraxis-Bereich, wo sich auf den Schreibtischen meist PCs mit einem Windows-Betriebssystem und entsprechender Anwendungssoftware befinden. Hier arbeitet der Anwender meist mit den graphischen Icons und den Menübuttons der Anwenderprogramme und gibt kaum mehr Befehle auf Kommandozeilenebene in der MSDOS-Box ein, wie es noch vor 15 Jahren für den erfolgreichen Umgang mit dem Computer gang und gäbe war. Heute sieht zum Beispiel der typische Einsatz eines Computers bei einem Arzt im Sprechzimmer so aus, dass nachdem der Rechner eingschaltet und hochgefahren wurde (d.h. nachdem sich das Betriebssystem geladen hat) sich der Arzt einloggen kann. Auf seiem Bildschirm erscheinen die Icons seiner Anwendungsprogramme. Der Arzt startet dann per Mausclick ein Patientenverwaltungprogramm, über das er auf die vorher gespeicherten Patienteninformationen zugreifen kann. Die auf die Bedürfnisse des Arztes hin entwickelte Anwendungssoftware erlaubt ihm unter anderem, aktuelle Diagosen anzufügen, Medikamente zu verschreiben und auf einem Rezeptformular auszudrucken.
Manchmal fügt das System zu der Diagnose gleich die entsprechenden Abrechnungsziffern für die Krankenkassen automatisch oder mit Unterstützung des Arztes hinzu. Meist bietet die Arztsoftware noch die Möglichkeit bietet, bei Verordnungen in einer elektronischen Medikamentendatenbank nachzuschlagen.
Manche Arztsoftware geht so weit, dass sie bei anstehenden Abrechnungen an die Krankenkassen auf der Basis von Statistiken und implementiertem "Expertenwissen" über Punktesätze und Vergütungschlüssel der Krankenkassen auch noch die Kassenabrechnung "optimieren" kann. Um seinen Computer für sich sinnvoll einsetzen zu können, braucht der Mediziner keinerlei Betriebssystem- und Hardware-Kenntnisse. Für ihn reicht es vollkommen aus, zu wissen, wie er das Arztprogramm bedienen muss, damit er, von seiner Warte aus betrachtet, vom Computer als Werkzeug einsetzen kann. Die Arztsoftware wiederum musste von Menschen entwickelt werden (Anwendungsprogrammierer), die beides kennen mussten: die Denkweise des Arztes und die Bedürfnisse des Arztes und die Arbeitsweise des zugrundeliegenden Betriebssystems. Mit der Entwicklung des Quellcodes für das Arztprogramm haben sie den Vermittler oder besser gesagt, das vermittelnde System, zwischen Mensch und Betriebssystem geschaffen. Das Arztprogramm selbst muss in einer Form vorliegen, dass es von dem Betriebssystem an die Hardware zum Abarbeiten weitergereicht werden kann. Dazu werden Compiler eingesetzt, die Programmcode automatisiert in Maschinensprache übersetzen. Das Betriebssystem ist die Steuerungs-Software eines Computers, es regelt, wie die Informationen in der CPU abgearbeitet werden und kontrolliert unter anderm die Datenflüsse zu den internen und externen Speichern sowie zu den Ein- und Ausgabe-Interfaces. Der Anwendungsprogrammierer für die Arztsoftware muss nach innen nur die Schnittstelle zum Betriebssystem und dessen Verhalten kennen. Die Schnittstelle von Betriebssystem und Hardware und der optimale Übergang der Informationen zwischen beiden liegt im Aufgabenbereich des Systemprogrammiers, dessen Arbeitsgebiet die hardwarenahe Programmierung ist.

Programmiersprachen

Nach der Deutschen Industrie-Norm (DIN) 44300, Teil 4 versteht man unter einer Programmiersprache, eine zum Abfassen von Computerprogrammen geschaffene Sprache.
Ein Programm ist eine von einem Computer interpretierbare vollständige Arbeitsanweisung zur Lösung einer Aufgabe.

In unserer gegenwärtigen Technologie ist jede Art von Information auf Hardware-Ebene in der Form von zwei Zuständen codiert: als offener oder geschlossener Schaltkreis, als niedriger Spannungspegel oder als hoher Spannungspegel. Stets wird, um eine Informationseinheit zu codieren, als kleinste Informationseinheit ein Bit verwendet. Die beiden möglichen Zustandsformen (offen oder geschlossen, hoher oder niedriger Spannungs- oder Ladungspegel) lassen sich als logische Null (0) oder als logische Eins (1) interpretieren. Mit der Informationscodierung über einen von zwei möglichen Zuständen legt man als Basis das Dualsystem (oft auch Binärsystem genannt) zugrunde. Jede Zahl aus unserem Zehner- oder Dezimalsystem lässt sich auch im dualen System darstellen. Zum Beispiel sieht die Zahl 75 aus unserem gewöhnlichen Dezimalsystem im Dualsystem (Binärsystem) wie folgt aus:

75 = 70 + 5  
  = $\displaystyle 7 \cdot 10^1 + 5 \cdot 10^0  
  = 7510  
75 = 64 + 8 + 2 + 1  
  = $\displaystyle 1 \cdot 2^6 + 0 \cdot 2^5 + 0 \cdot 2^4 + 0 \cdot 2^3 +
0 \cdot 2^2 + 1 \cdot 2^1 + 1 \cdot 2^0$  
  = 10010112  

Binäre Maschinensprache

Die Signale, die die Computer-Hardware verarbeitet, sind in der binären Maschinensprache mittels einer Abfolge an logischen Nullen und Einsen codiert. Die in Maschinensprache codierten Anweisungen, die sogenannten Binaries, sind streng von der Rechnerarchitektur des jeweiligen Rechners abhängig und nicht zwischen Rechnern verschiedenen Typs, z.B. einer DEC Alpha und einem Rechner von Silicon Graphics portierbar. Wurde ein sogenanntes Binary oder Executable für eine bestimmte Rechnerarchitektur erzeugt, so lässt es sich nicht auf einem Rechner eines anderen Typs, ja nicht einmal zwischen Rechnern des gleichen Herstellers mit verschiedenen CPU-Typen, ausführen. In den Anfangsgründen der Computertechnik wurden Programme direkt in Maschinensprache entwickelt. Deshalb spricht man von der Maschinensprache auch als Programmiersprache der 1. Generation. Die ausführbaren Programme (die Binaries oder Executables) direkt als Abfolge von Nullen und Einsen zu schreiben, ist extrem mühsam, aufwendig und fehleranfällig.

Der Programmierer einer Programmiersprache der 1. Generation muss den Steuercode programmieren und dabei die Speicherverwaltung selbst regeln. Das bedeutet unter anderem, dass sich der Maschinensprache-Programmierer um das Einlesen der Daten in die CPU und um das Rausschreiben der Ergebnisdaten in die wiederum binär codierten Speicheradressen selbst kümmern muss. In Maschinensprache kann eine einzige 1 an einer Stelle, an welcher eigentlich eine 0 stehen sollte, das gesamte Projekt zu Fall bringen. Dort, wo für die Prozessoren die Befehlssätze (engl. instruction sets) entwickelt werden, muss natürlich auch noch heute Maschinensprache eingesetzt werden. Ein Fehler hier oder auf Schaltkreisebene kann dazu führen, dass bei einer ganzen Produktionsserie eines Chips, wie vor ein paar Jahren geschehen, der Computer in bestimmten Situationen schlichtweg falsch rechnet. (siehe auch Rechenfehler im Pentium Prozessor von Intel im Sommer 1994, Rechenfehler: Patch für den Ultra-Sparc-III-Chip, 2001).

Assembler

Mit den Programmiersprachen der 2. Generation, den sogenannten Assemblern, wurde/wird die Programmierarbeit schon etwas leichter. Statt einer Folge von Nullen und Einsen sind die Steuerbefehle jetzt meist leichter lern- und erkennbare Kürzel aus meist 3 Buchstaben, aus denen sich die Wirkungsweise des Befehls erkennen lässt (z.B. ADD statt 1101, um den Befehl zum Addieren zu geben).

Um die Speicherverwaltung müsste und muss sich der Assembler-Programmierer selbst kümmern. Die hardwarenahe Programmierung mit Assembler wird noch heute in den Forschungs- und in der Entwicklungslabors bei besonders zeitkritischen Aufgaben eingesetzt, beispielsweise um mit Analog-Digital-Wandler-Karten aufgenommenen Daten schnell in den Speicher des Rechners übertragen zu können. Auch hier gilt, dass die Assemblerprogramme abhängig vom Prozessortyp sind. Die Aufgabe des Assemblers ist es, den vom Programmierer entwickelten Assembler-Code in Maschinensprache umzusetzen.

Eine sehr gute, inforative Darstellung zu Assembler findet sich in Wikipedia http://en.wikipedia.org/wiki/Assembly_language.

Höhere Programmiersprachen

Unabhängig von der Hardware-Architektur wird man durch die Programmierung in höheren Programmiersprachen. Ein sogenannter Compiler oder manchmal auch ein Interpreter macht in höheren Programmiersprachen dem Programmierer das Leben leichter:
  1. Die Anweisungen an den Computer werden in einer Sprache und nach Prinzipien verfasst, die analytisch denkenden Menschen zugänglich sind.
  2. Die Bezeichnungen für die einzelnen Befehlsanweisungen orientieren sich meist an der englischen Sprache.
  3. Das zu erlernende Vokabular (der Befehlssatz) ist im Vergleich zum englischen Wortschatz gering.
  4. An Grammatikregeln und für die Zeichensetzung ist nur wenig zu lernen.
  5. Den bisher entwickelten und verbreiteten Programmiersprachen liegt eine strenge formale mathematische Logik zugrunde.
Allerdings:
  1. Anders als ein menschlicher Gesprächspartner verfügt ein Computer von Natur aus über kein Hintergrundwissen und keine Intuition. Er kann nicht ahnen, was der Mensch beabsichtigt.
  2. Ein Computersystem (Hardware+Software) ist eine reine Maschine, die genau die Befehle durchführt, die an sie gerichtetet sind und die in ihren Programmen enthalten sind. Nicht mehr und nicht weniger.
Das bedeutet:
  1. dass jedes zu einer Problemlösung gehörende Detail exakt und formal korrekt in den Programmanweisungen beschrieben werden muss. Nur dann kann man vom Computer auch richtige Ergebnisse erwarten.
  2. Ein Computer jetziger Bauart verzeiht keine Rechtschreib-, und Interpunktionsfehler. Sind solche vorhanden sind, wird der Computer gar keine oder falsche Ergebnisse liefern.
Zu den höheren Programmiersprachen gehören die Programmiersprachen der 3. Generation , deren allgemeines Merkmal ist, dass sie algorithmisch orientiert sind. Beispiele für 3GLs ( 3rd Generation Languages) sind die typischen klassischen Programmiersprachen FORTRAN, Basic, COBOL, Pascal und C. Bei den Programmiersprachen der 3. Generation muss dem Rechner mittes des zu erstellenden Programmcodes der Lösungsweg Schritt für Schritt vorgegeben werden. Dies bedeutet, dass der Programmentwickler ganz genau wissen muss, wie die Lösung bei gegebener Problemstellung zu finden ist und dass es auch Aufgabe des Programmieres ist, alle Sonderfälle in dem Programmquelltext mit zu behandeln.

Von Programmiersprachen der 4. Generation spricht man, wenn auf dem Weg zur Problemlösung nur die Fragestellung für den Rechner exakt formuliert werden muss, aber dem Computer nicht mehr mitgeteilt zu werden braucht, auf welchem Weg die Lösung gefunden werden kann. Das im Rechner implementierte System muss somit Regelsätze enthalten, nach denen das Problem zunächst klassifiziert wird. Nach weiteren Regelsätzen wird dann eine Lösung ermittelt. Man spricht von den 4GLs als nicht-prozeduralen Programmiersprachen, um sie von den prozeduralen, algorithmisch orientierten 3GLs abzugrenzen. Unter den "Programmiersprachen", die als 4GLs eingeordnet werden, finden sich so unterschiedliche Systeme wie die Datenbankabfragesprache SQL, Computer-Algebra-Systeme wie z.B. Maple und Matlab und Entwicklungssysteme zur Programmentwicklung, sogenannte CASE-Tools (Computer Aided Software Engineering). Die Grenzen der Programmiersprachen der 4. Generation liegen in den implementierten Regelsätzen. Nur Fragen, für die der Anwendungsentwickler die Klassifikationsregeln und den dazu gehörigen Lösungsweg implementiert hat, sind beantwortbar. Zu prüfen, ob die gegebenfalls gelieferte Lösung wirklich richtig ist oder ob das System eventuell implizit versteckte und falsche Annahmen getroffen hat, bleibt dem Anwender überlassen.

Bei der Frage, was unter Programmiersprachen der 5. Generation zu verstehen sei, teilen sich die Meinungen. Manche sind der Ansicht, CASE-Tools (z.B. Rational Rose), die eine graphische Programmierung mit Hilfe von verschiedenen verschiebbaren Objekten erlauben, seien als 5GLs anzusehen. Als Kriterium wird herangezogen, dass der eigentliche Programmcode aus der Entwicklungsumgebung heraus automatisch generiert wird. Andere sind der Meinung, dass dies noch nicht ausreichen könne, um von einer Programmiersprache der 5. Generation zu sprechen und ziehen vielmehr als Kriterium heran, dass es sich um ein wissensbasiertes System handeln müsse.

Weitere wichtige Entwicklungsrichtungen von Programmiersprachen stehen evtl. etwas außhalb diesen starren Schemas. Dazu gehören die objektorientierten Programmiersprachen, wie z.B. C++, Java, Smalltalk, VBA und ADA, die anders als die 3GL nicht mehr starr zwischen Daten und den auf die Daten anzuwendenden Operationen trennen. Vielmehr werden in OOLs beides zu sogenannten Objektklassen zusammengefasst, deren Eigenschaften wieder auf andere Objektklassen vererbt werden können. Der dahinter stehenden Grundgedanke für das objektorientierte Programmierkonzept ist ein anderes als bei den prozedural orientierten Programmiersprachen. Bei den OOLs werden Daten und die darauf anwendbaren Funktionen zu sogenannten Objekten verknüpft und gekapselt mit einer definierten Schnittstellen (Nachrichtenaustauschstelle) nach außen versehen. Über diese Schnittstelle erfolgt der Datenaustausch mit anderen Objekten.

Grundlage prädikativer/logischer Progammiersprachen (z.B. PROLOG und Lisp) ist die Formulierung von Aussagen und Regeln. Durch Verknüfung dieser Eingaben werden neue (wahre) Aussagen erhalten.

Abweichend von der strengen binären Logik (eine Aussage ist entweder wahr oder sie ist falsch) gibt es auch Ansätze eine unscharfe Logik (Fuzzy Logic) basierend auf verschiedenen Wahrscheinlichkeitsstufen der Aussagen und daraus folgernde (verschieden wahrscheinliche) Schlussketten zu implementieren. Auf diese Art wird manchmal versucht, Expertenwissen in Computerregelsätze zu übertragen. Wird dies zur Steuerung von Maschinen oder Produktionsstraßen verwendet, spricht man von Fuzzy Control. Bei der Programmierung von Mehrprozessormaschinen (mehr als eine CPU) in einem Rechner oder bei der Verteilung einer Aufgabe auf mehrere Rechner (Distributed Computing) ergeben sich wiederum spezielle Anforderungen an die Aufgabenverteilung und die Kommunikation der einzelnen CPUs bzw. der Rechner untereinander, die das Einsatzfeld für verteilte und parallele Programmiersprachen sind.

Am Ende dieses Abschnitts seien noch einmal die allen Programmiersprachen zugrundeliegenden Ideen zusammengestellt:

Höhere Programmiersprachen
  1. dienen der Formulierung von Anweisungen an den Rechner in einer für den Menschen verständlichen Form
  1. strenge, formale Sprachen mit eindeutiger Syntax und Semantik

    Die Syntax bezeichnet die nach bestimmten Regeln festgelegten Verknüpfungsmöglichkeiten von Zeichen und Befehlen aus dem Zeichen- und Befehlsvorrat einer Programmiersprache.
    Die Semantik entspricht der Bedeutung, die mit der Abfolge der Sprachelemente in der Programmiersprache verknüpft wird.
    kurz:

    • Die Syntax legt die zugelassenen Zeichen und Zeichenfolgen in einem Programm fest
    • Die Semantik legt die Bedeutung der Zeichenfolgen in einem Programm fest
    • Die vordefinierten Befehlswörter orientieren sich meist an der englischen Sprache
  1. Mittels eines Hilfsprogramms (Compiler oder Interpreter) kann das Programm in die für den Computer verständliche Maschinensprache übersetzt und danach ausgeführt werden.
  1. Weitestgehend sind die höheren Programmiersprachen von der Hardware-Architektur des Rechners unabhängig. (Dies gilt insbesondere für die nach ANSI standardisierten Programmiersprachen).

Wer sich noch etwas vertiefter mit den verschiedenen Ansätzen der Programmiersprachen und der Programmentwicklung beschäftigen möchte, sei z.B. auf

  1. http://www.adaic.com/docs/reports/lawlis/content.htm
  2. http://www.uni-koblenz.de/~motzek/html/inhalt.htm
verwiesen.

Das Ausführen von Programmen höherer Programmiersprachen

Wenn Sie mit Hilfe eines Editors Ihren Quellcode entwickelt haben, müssen noch ein paar Zwischenschritte zurückgelegt werden, bis Ihr Programm in Maschinensprache vorliegt und von der Computer-Hardware abgearbeitet werden kann. Die Zwischenschritte sind:

Ablaufschema Compiling-Linkage-Execution

Beim Compilieren wird der Programmcode in Maschinenssprache übersetzt. Nach gängiger Konvention wird der übersetzte Code mit dem Programmnamen und der Endung '.o' unter UNIX bzw. unter Windows mit '.obj' versehen, um kenntlich zu machen, dass es sich um einen sogenannten Objectcode handelt. Bibliotheksfunktionen wie zum Beispiel numerische Libraries liegen typischerweise als vorcompilierter Objectcode vor.

Mit dem Linker werden die benötigten Objectcodes zu einem ausführbaren Programm verknüpft. Dieses wird unter UNIX - wenn man keine andere Bezeichnung vorgibt - defaultmäßig unter UNIX mit 'a.out' bezeichnet.

Ein Fortran Compiler unter Windows erzeugt ein Executable mit der Endung '.exe'.

Die durch Compilieren und Linken erzeugten Programme sind als Executables direkt ausführbar. Unter UNIX kann man an der Kommandozeile das Programm als ./a.out bzw. mit seinem zugewiesenen Namen direkt starten.

Analog können die unter Windows erzeugten ausführbaren Programme direkt aus dem Eingabefenster heraus gestartet werden.

Es ist eher ungewöhnlich, dass ein längeres selbstentwickeltes Programm gleich auf Anhieb fehlerfrei übersetzt wird und gleich die richtigen Ergebnisse liefert. In der Regel treten sukzessive einige Fehler auf.

  1. Beim Compilieren findet eine reine Syntax-Überprüfung statt, d.h. der Computer führt eine Art Rechtschreibprüfung nach Schreibweise und Zeichensetzung durch.
  2. Was der Compiler nicht durchführen kann, ist eine Überprüfung der Semantik, d.h. eine Prüfung der Bedeutung der in dem Programm enthaltenen Sprachelemente (des Sinngehalts).
Treten bei der Compilierung Fehler auf, so gibt Ihnen in der Regel der Compiler Hinweise auf die Zeile(n), in denen Fehler vorliegen können und auf die Art des Fehlers (der Fehler). Falls Syntax-Fehler auftreten sollten, kann der Programmcode nicht in Objectcode übersetzt werden. Dies ist nur bei syntaktisch vollständig korrekten Quelltexten möglich. Der Programmcode muss dementsprechend überarbeitet und korrigiert werden.

  1. Beim Linken können ebenfalls Syntaxfehler auftreten. Dies führt ebenfalls zu einer Fehlermeldung mit dem daraus folgenden Abbruch des Vorgangs. Treten Fehler beim Linken auf, müssen die Link-Befehle an der Kommandozeile bzw. bei Verwendung von "make" das Makefile überprüft werden.

Sind beim Compileren oder Linken Fehler aufgetreten, liegt es jetzt an Ihnen, diese Fehler zu finden und zu beseitigen.

  1. Ist es Ihnen gelungen, Ihren Programmcode fehlerfrei zu compilieren (und ggf. zu linken), können Sie das erzeugte Executable an der Kommandozeile (UNIX bzw. MSDOS-Box (Eingabeaufforderung)) oder von der integrierten Entwicklungsumgebung aus (z.B. beim Compaq Digital Visual Fortran Compiler) starten.

  2. Sollten Ihnen logische Fehler beim Programmaufbau unterlaufen sein, kann dies zu einem Programmabbruch führen. Auch hier erhalten Sie eine Fehlermeldung. Da jedoch von der Hardware mit dem Executable maschinennaher Code abgearbeitet wird, aus dem "unnötige Informationen" für die Hardware entfernt wurden, kann Ihnen der Rechner nun nicht mehr mitteilen, an welcher Stelle Ihres Programmcodes der Fehler auftritt. Jedoch kann er Ihnen eine Fehlerbeschreibung aus Hardwaresicht geben, z.B. "bus error" oder "runtime error".

Auch hier sind wiederum Sie gefordert, den zugrundliegenden Fehler zu finden und zu eliminieren.

Nicht immer verursachen logische/semantische Fehler einen Programmabbruch. Haben Sie z.B. aus Versehen mit Ihrem Programmcode eine Endlosschleife gestrickt, so wird immer wieder diese Schleife abgearbeitet werden und in diesem Fall wird Ihr Programm ohne Eingriff von außen endlos weiterrechnen.

Bei der Suche nach logischen Fehlern auf Programmkonstruktebene (auf Ebene des Algorithmus) besteht die einfachste Methode zur Eingrenzung eines logischen Fehlers darin, zusätzlichen Code einzubauen. Sie können z.B. zusätzliche Programmzeilen einbauen, die Ihnen später anhand der darin enthaltenen Bildschirmausgaben Hinweise geben, welcher Programmabschnitt gerade abgearbeitet wurde, bevor der "bus error" oder der "runtime error" auftreten.

  1. Bliebt noch die Möglichkeit, dass Ihnen Fehler auf Daten- oder Algorithmus-Ebene unterlaufen sein könnten, d.h. Ihr Programm läuft zwar richtig ab, tut aber nicht das Richtige, weil Ihnen bei der Eingabe oder schon beim Entwurf des Algorithmus ein Fehler unterlaufen ist. Dadurch werden die Ergebnisse, die Ihnen Ihr Programm liefert, schlichtweg falsch.

Ein einfaches Beispiel für einen logischen Fehler bzw. einen Fehler auf Algorithmus-Ebene:

Bei einer Berechnung muss eine Zahl mit 0.1 multipliziert werden. Statt *0.1 verwenden Sie versehentlich /0.1 und weisen den Rechner dadurch an, durch 0.1 zu dividieren statt mit 0.1 zu multiplizieren, wie es eigentlich verlangt worden war. Aufgrund dieses Fehlers wird der berechnete Wert um den Faktor 100 zu groß.

Neben solchen fatalen Tippfehlern können logische bzw. Algorithmusfehler z.B. aus der falschen Wahl des Datentyps oder noch tiefgreifender aus Denkfehlern des Programmentwicklers resultieren.

Um Fehler auf logischer Ebene zu vermeiden, ist es unabdingbar, das Programm anhand mehrerer strukturell verschiedener Testdatensätzen zu überprüfen. Der für die Testdaten erwartete Programmoutput muss unabhängig von den entwickelten Programm auf anderem Wege (z.B. per Hand/Taschenrechner/ Computer-Algebra-System etc.) ermittelt worden sein, um einen angemessenen Vergleichmaßstab vorliegen zu haben.

Folgender Ablaufplan soll den Mechanismus der Fehlersuche auf den verschiedenen Ebenen nochmals verdeutlichen:

Ablaufplan Fehlersuche

Evtl. muss Ihr an sich funktionierendes Programm noch bezüglich der Laufzeit oder der Speicheraufteilung optimiert werden. Immer, wenn Sie Veränderungen am Source-Code vornehmen, hat das modifizierte Programm erneut den gesamten Fehlersuchzyklus zu durchlaufen. Und erst, wenn eine Entwicklungsebene erfolgreich war, kann die nächsthöhere Ebene der Programmentwicklung durchlaufen werden.

Wollen Sie ein umfangreicheres Programm dabei beobachten, wie es Schritt für Schritt arbeitet und welche Werte jeweils in den einzelnen Schritten den Variablen zugewiesen werden, empfiehlt sich der Einsatz eines sogenannten Debuggers. Ein Debugger ist auch hilfreich, wenn Sie eine komplexere Fehlersuche in großen Programmpaketen durchführen müssen.
Um Programme später debuggen zu können, müssen sie unter UNIX vorher mit der Compileroption -g compiliert werden.

Erst wenn Sie sich sehr sicher sind, dass Ihr Programm fehlerfrei und optimal arbeitet, sollten Sie den Programmentwicklungszyklus abschließen.

Strukturierte Programmentwicklung (Top-Down-Entwurf) bei algorithmisch orientierten Programmiersprachen der 3. Generation (3GLs)

Die Zeit, die man mit der Programmentwicklung und der Fehlersuche verbringt, lässt sich minimieren, wenn man überlegt und strukturiert an die Programmentwicklung herangeht. Gleichzeitig steigt bei einem überlegten Programmierkonzept die Qualität und die Lesbarkeit des Programmcodes, wenn man ein paar einfache und grundlegende Prinzipien beachtet:

  1. Fertigen Sie eine eindeutige und klare Problembeschreibung an.
  2. Beschreiben Sie genau, welche Informationen eingegeben und welche ausgegeben werden sollen.
  3. Arbeiten Sie per Hand für einen einfachen Datensatz die Lösung per Hand/Taschenrechner/Computer-Algebra-System aus.
  4. Strukturieren Sie nun für das generalisierte Problem die einzelnen Schritte. Teilen Sie dazu das Gesamtproblem in immer feinere Teilschritte auf, die sukzessive weiter verfeinert werden können. ("vom Groben zum Feinen = Top-Down-Entwurf") (Bei diesem Schritt können Sie Datenfluss-Diagramme oder Pseudocode zu Hilfe nehmen).
  5. Setzen Sie erst jetzt den von Ihnen entwickelten Lösungsalgorithmus in Programm-Code um. Realisieren Sie evtl. einzelne, in sich abgeschlossene Programmteile als Unterprogramme.
  6. Durchlaufen Sie den oberen Fehlersuch- und Eliminierungszyklus so oft wie nötig.
  7. Speziell bei der Auswahl Ihrer Testdaten sollten Sie sich Gedanken machen, ob die verwendeten Testdatensätze soweit wie möglich voneinander unabhängig sind und die Spezifität des Problems hinreichend verkörpern.

2. Die Programmiersprache FORTRAN

FORTRAN ist die Ursprache aller algorithmisch orientierten wissenschaftlichen Programmiersprachen. In Laufe der Zeit wurde FORTRAN mit der Entwicklung der Computertechnik kontinuierlich verbessert und immer wieder standardisiert. Dabei wurden die Sprachelemente kontinuierlich weiterentwickelt, wobei die Rückwärtskompatibilität gewahrt wurde. So ist Fortran 90/95 eine sehr moderne, strukturierte Sprache, die es allerdings noch erlaubt, "unsaubereren" FORTRAN 77-Code und Stil zu verwenden.
Der Aspekt der Rückwärtskompatibilität wurde bei den ANSI-Standardisierungen der Sprache 1966, 1977, 1991 und 1997 bewusst gewählt: durch das Einsatzgebiet der Sprache im technischen-wissenschaftlichen Bereich steckt in jedem Programmpaket ein immenses Know-How, das stets gewahrt und weiter nutzbar bleiben sollte. Fortran 90/95 selbst ist eine sehr moderne Sprache, die dazugehörigen Compiler finden sich auf jedem Hochleistungsrechner und meist auch auf den Workstations.

3. Zur historischen Entwicklung von FORTRAN

Der Name FORTRAN wurde von FORmula TRANslator abgeleitet und markiert, dass die Sprache entwickelt wurde, um es den damaligen Programmieren zu erleichtern, wissenschafliche Formeln in Computercode umzusetzen. Zur Erinnerung: in der Zeit, als die ersten Wurzeln von FORTRAN bei IBM in den Jahren 1954 bis 1957 entwickelt wurden, musste noch in Maschinensprache (0er und 1er) oder in Assembler (CPU abhängiger Code aus 3 Buchstaben mit expliziter zu programmierender Speicherverwaltung) programmiert werden. Das Programmieren in Maschinensprache und Assembler war, verglichen mit der neu entwickelten Programmiersprache, äußerst umständlich und fehleranfällig. Mit FORTRAN jedoch konnte der Programmierer nun seine Programme in einer der englischen Sprache angepassten Syntax schreiben. Der Programmcode war klar strukturiert, Formeln konnten in nahezu mathematischer Syntax eingegeben werden. Hinzu kam dass sich der wissenschaftliche Anwender um die explizite Speicherverwaltung nicht mehr zu kümmern brauchte: ein riesengroßer Fortschritt! Die Umsetzung der Gleichungen in eine Problemlösung war so viel einfacher geworden und die Programmentwicklung ging im Vergleich zu vorher rasend schnell. Kein Wunder, dass sich der Einsatz von FORTRAN sehr schnell durchsetzte.

Bald wurden für andere Rechnerarchitekturen als den ursprünglichen IBM 704 FORTRAN-Compiler entwickelt.

Anfangs war der Sprachumfang von FORTRAN noch sehr bescheiden. Im Laufe der Zeit wurde er kontinuierlich erweitert und die Sprache verbessert. Um die Kompatibilität zwischen verschiedenen Rechnerarchitekturen zu wahren, wurden die Sprachelemente in FORTRAN 66, FORTRAN 77, Fortran 90 und Fortran 95 vom American National Standards Institute (ANSI) genormt.

Die kontinuierliche Erweiterung und Verbesserung von Fortran findet auch noch heute statt. Die Entwicklungrichtung der Sprachelemente geht in Richtung Parallelisierung, High Performance Computing (HPF - High Performance Fortran) und Distibuted Computing.

Auch hier hier hat sich in den letzten Jahren ein Wikipedia-Eintrag zu Fortran entwickelt, der sowohl die historische Entwicklung als auch den aktuellen Stand der Programmiersprache umfassend und klar diskutiert: http://en.wikipedia.org/wiki/Fortran

4. Zwei einfache Programmbeispiele in Fortran 90

Das klassische C-Programm "Hello World!" ist manchen von Ihnen wahrscheinlich bekannt
#include <stdio.h>

int main(void)
{
printf("Hello World!\n");
return 0;
}

Im Vergleich zu C schaut "Hello World!" in Fortran 90 etwas weniger kryptisch und einfacher aus (
hello.f90)
program hello
write(*,*) 'Hello World!'
end program hello
Als Beispiel noch ein einfaches Fortran 90 - Programm, welches eine Umrechnungstabelle von Grad Fahrenheit nach Grad Celsius erzeugt (celsius_table.f90)
!
! Programm Fahrenheit-Celsius-Tabelle
!
! druckt eine einfache Umrechnungstabelle von Grad Fahrenheit nach Grad Celsius
!
        program celsius_table
        implicit none
        real ::  Fahrenheit, Celsius

        write(*,*) '  Fahrenheit     Celsius'
        write(*,*) '--------------------------'
        do Fahrenheit = 30.0, 220.0, 10.0
                Celsius = (5.0/9.0) * (Fahrenheit-32.0)
                write(*,*) Fahrenheit,Celsius
        end do
        end program celsius_table
Während mit den write(*,*)-Anweisungen wiederum Text auf die Standardausgabe (Bildschirm) geschrieben wird, sorgt der Abschnitt mit
        do Fahrenheit = 30.0, 220.0, 10.0
                Celsius = (5.0/9.0) * (Fahrenheit-32.0)
                write(*,*) Fahrenheit,Celsius
        end do
dafür, dass mittels einer Programmschleife der Celsiuswert zu den Fahrenheitwerten 30.0, 40.0, 50.0 usf. bis zum Endwert 220.0 berechnet werden.

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)