Computer und Physik 1 of 14 http://physik.uni-graz.at/~cbl/C+P/admin/mk_combined_fi... Computer und Physik, version dated 2005-11-15 13:44:15 2.2 Programmiersprachen 2.2.1 Einführung 2.2.2 Maschinensprachen und Assembler 2.2.3 Höhere Sprachen 2.2.4 Unterschiede zwischen den Programmiersprachen 2.2.5 Softwareprojekte Adults worry a lot these days. Especially, they worry about how to make other people learn more about computers. They want to make us all "computer-literate". Literacy means both reading and writing, but most books and courses about computers only tell you about writing programs. (Marvin Minsky, 1984, Media Lab, Massachusetts Institute of Technology) 2.2.1 Einführung Wie wir im ersten Teil gesehen haben, ist der Befehlsvorrat eines Prozessors ziemlich beschränkt, im Einzelfall auf einige Dutzend oder vielleicht einige Hundert Operationen logischer oder arithmetischer Art. Alle Programme müssen daher letztendlich in viele kleine Teilschritte zerlegt und auf diesen grundlegenden Befehlsvorrat zurückgeführt werden. Der wichtigste Schritt bei Erstellung eines Programmes ist daher die logische Analyse der Befehlsabfolge. Bei Ausführung eines Programms müssen alle sich ergebenden Entscheidungsmöglichkeiten vorhergeplant und entsprechende Verzweigungen vorgesehen werden. In den meisten Fällen wird das Programm Daten von externen Speichern oder Eingabegeräten lesen und verarbeiten. Auch hier muss der Programmierer auf alle sich ergebende Möglichkeiten Bedacht nehmen. Einfachheit ist ein sich wandelnder Begriff. Versuche einmal, einem Schulanfänger die Addition von Dezimalzahlen "einfach" zu erklären; es wird nur möglich sein, wenn du die Bedeutung von ganzen Zahlen erklärst, dann die Addition ganzer Zahlen, dann die Dezimalzahlen und schließlich deren Addition. Das Kind wird im Normalfall weit überfordert sein. Es aber vermutlich verstehen, dass 1+1=2 ist. "Lernen" ist solch ein Prozess. Als Kleinkind lernen wir einfache Zusammenhänge des täglichen Lebens: wenn ich ins Feuer greife, tut es weh; wenn ich von zwei Rippen Schokolade eine esse, so bleibt mir nur mehr eine übrig. Später lernen wir kompliziertere Zusammenhänge zu verstehen, indem wir sie gedankenschnell auf einfache zurückführen: wenn ich das Bügeleisen nicht ausschalte, so habe ich morgen vielleicht kein Haus mehr; wenn ich in ein teures Hotel ziehe, so kann ich nur zwei Wochen Urlaub machen. Wir könnten unsere Aussagen in viele kleine Teilschritte zerlegen, aber wir finden so eine Zerlegung überflüssig und umständlich. Etwas, was wir kaum unserem Kind klar machen könnten, kann uns "einfach" erscheinen. 15.11.2005 14:01 Computer und Physik 2 of 14 http://physik.uni-graz.at/~cbl/C+P/admin/mk_combined_fi... Zerlegung in kleinste Teile, die Maschinenbefehle eines speziellen Computers, ist heute den Systemprogrammierern der Produktionsfirma und anderen Spezialisten vorbehalten. Wir wollen in größeren Schritten denken, Manipulationen mit Dezimalzahlen sind für uns eine Selbstverständlichkeit, die wir nicht in viele kleine Einzelschritte zerlegen wollen. Unsere Sprache soll solch eine Zerlegung nicht notwendig haben, wenn wir sagen "Addiere 3.14 und 5.27", so wissen wir genau, was gemeint ist. Wir verwenden eine "höhere Sprache" als die Maschinensprache es ist. Ein Wissenschaftler verwendet für seine Probleme wiederum Spezialausdrücke, die über die Alltagssprache hinausgehen, und selbst innerhalb einer Wissenschaftsdisziplin kann es weitere Spezialisierungen geben. Diese Hierarchie von Sprachen gibt es auch bei den Programmiersprachen. Es gibt verschiedene Niveaus von Sprachen: Level 1: Maschinensprachen und Assemblersprachen Bei Maschinensprachen entspricht ein Maschinenbefehl einem "Satz" dieser Sprache. Da man sich nur ungern alle Zahlenverschlüsselungen von Befehlen merkt, gibt es mnemotechnische Wortbefehle, die genau den verschiedenen Maschinenbefehlen entsprechen. Dies führt zu den sogenannten Assemblersprachen. Ein Programm in einer Assemblersprache ist für den Programmierer wesentlich einfacher "lesbar", er kann oft mehrere Befehle durch ein Wort abkürzen, aber es gibt immer einen unmittelbar erkennbaren Zusammenhang zwischen seinen Befehlszeilen und der Folge von Maschinenbefehlen. Level 2: Höhere Sprachen Viele Befehlsfolgen kommen in bestimmten Problemen immer wieder vor und es ist zweckmäßig, sie durch zusammenfassende Befehle abzukürzen. Dies führt zu den höheren Sprachen wie C, C++ oder FORTRAN. Level 3: Anwendungsorientierte Sprachen. Die Weiterentwicklung der höheren Sprachen und die Spezialisierung auf bestimmte Problemkreise führt zu diesen Sprachen, zu denen man zum Beispiel Maple oder MATHEMATICA zählen kann, Sprachen, mit deren Hilfe man algebraische Rechnungen durchführen kann, bei denen man nicht nur Zahlen, sondern exakte analytische Ergebnisse erhält. Was ist eine Programmiersprache? Was ist eine Sprache? Nun, von ihrer Struktur her betrachtet besteht sie auf einer endlichen Menge von Symbolen und Regeln, die es erlauben, diese Symbole miteinander zu Sätzen zu verknüpfen. Die Symbole können Ziffern, Buchstaben, Sonderzeichen, aber auch Kombination davon wie etwa bestimmte Befehlsworte sein. Das Regelsystem bestimmt die Syntax der Sprache. Die ersten Programmiersprachen wurden von Praktikern entworfen und folgten in ihrer Struktur dem Gesetz der Stunde. Erst später machte man sich über die Logik der Sprache mehr Gedanken. Sprachwissenschaftler wie Noam Chomsky haben über die wichtigen Eigenschaften der Sprachen nachgedacht: Wie kann man aus einem einfachen Subjekt-Verb-Objekt Satz ein syntaktisch richtiges Satzmonstrum bilden? Aus "Ich lese ein Buch" wird "Wenn ich Zeit und Lust dazu habe, lese ich abends nach den Nachrichten ein Buch über Tauchen, da dies eines meiner Hobbys ist". Die meisten Umgangssprachen kann man in kein einfaches Strukturkonzept zwängen. Es gibt zwar einige Regeln, aber zu fast allen Regeln gibt es Ausnahmebestimmungen und Fußnoten. 15.11.2005 14:01 Computer und Physik 3 of 14 http://physik.uni-graz.at/~cbl/C+P/admin/mk_combined_fi... Sätze des täglichen Lebens können vieldeutig wie das Orakel von Delphi sein. Selbst wenn keine Vieldeutigkeit beabsichtigt ist, kann zum Beispiel eine Aussage eines Juristen einen Physiker durchaus verwirren - und umgekehrt. Solche Unklarheiten sollen bei Computersprachen ausgeschlossen werden. Die Mitteilung eines Satzes soll eine eindeutige Information über eine wohldefinierte Befehlsfolge sein. Dazu gehört als Voraussetzung eine wohldefinierte Syntax. Bei Maschinensprachen ist diese Forderung einfach zu erfüllen, da ja einem Satz der Sprache genau ein Maschinenbefehl entspricht. In der Praxis bestehen diese "Sätze" daher oft nur aus einigen Zeichen. Der große Nachteil ist dabei die Abhängigkeit von dem speziellen Prozessor. Bei jedem neuen Prozessortyp haben sich die Konstrukteure neue Maschinenbefehle ausgedacht, die neue Aufgaben erfüllen oder alte Aufgaben erleichtern sollen. Es ist aber ziemlich aufwendig, für einen neuen Computer völlig neue Programme zu schreiben. Daher kam schnell der Wunsch nach "höheren" Sprachen auf. Eine höhere Sprache soll eine weitgehende Unabhängigkeit von dem speziellen Prozessortyp gewährleisten. Sie soll eine genaue Kenntnis der jeweiligen Maschinensprache oder spezieller Ausstattungsmerkmale unnötig machen. Und schließlich soll sie rationell sein, man sollte nach Möglichkeit eine lange Kette von Maschinenbefehlen durch einige wenige Befehl in der höheren Sprache ersetzen können. Die sich aus diesen Bedingungen ergebenden Vorteile sind vielfältig. Die höhere Sprache ist einfacher zu lernen. Nach Möglichkeit ist der Aufbau der Befehle einer Umgangssprache verwandt, im Idealfall ist ein Befehl in einer höheren Sprache also im Klartext verständlich: "If X less than 2 then goto Label". Ein Programm in dieser Sprache ist einfacher zu strukturieren, man kann es leichter entwerfen, ändern, und andere können es leichter verstehen. Dieser letzte Aspekt wird immer wichtiger. Viele Programmsysteme bestehen aus vielen Hundert Programmteilen (Unterprogrammen), die von verschiedenen Programmierern geschrieben und getestet wurden. Durch die angestrebte Unabhängigkeit vom Maschinentyp kommt es zu Programmbibliotheken, in denen Programme und Programmteile, die immer wieder benötigt werden, bis auf Abruf wie Bücher aufbewahrt werden. Compiler oder Interpreter? Wo Licht ist, ist auch Schatten; natürlich haben höhere Sprachen auch Nachteile. Einer davon ist, dass die in einer höheren Sprache geschriebenen Programme natürlich letztendlich irgendwie in die dem Computer einzig verständliche Maschinensprache übersetzt werden müssen. Dazu allerdings kann man wiederum Programme verwenden, da diese Übersetzung aufgrund der klaren logischen Struktur automatisiert werden kann. Programme, welche in höheren Sprachen geschriebene Programme in Maschinensprache übersetzen, heißen Compiler. Wenn die Übersetzung nur aus einer einfachen Umschreibung besteht, wie es bei den Level-1 Sprachen der Fall ist, so nennt man die Übersetzungsprogramme Assembler, daher der Ausdruck Assemblersprachen. Eine Möglichkeit ist es, mit Hilfe eines Compilers aus dem vollständigen Programm in einer höheren Sprache zuerst eines in Maschinensprache zu machen, und dieses dann ausführen zu lassen. Eine andere Möglichkeit ist es, diesen Vorgang Schritt für Schritt durchzuführen, also Befehl für Befehl in der höheren Sprache zuerst zu übersetzen und sofort auszuführen. Programme, die diese Vorgangsweise erlauben, nennt man Interpreter, und es gibt Sprachen wie etwa BASIC oder LOGO, die speziell dafür gedacht sind. Der Vorteil dabei ist, dass man etwaige logische Programmfehler besser erkennen kann, der Nachteil ist, dass dabei wiederholt durchlaufene Befehle auch wiederholt neu übersetzt werden müssen, was Computerzeit kostet. Das MATHEMATICA-System etwa kann sowohl interpretieren als auch kompilieren, je nach Wunsch. In diesem Zusammenhang tauchen noch einige Begriffe auf. Der lesbare Text, also die in der jeweiligen Sprache geschriebenen Zeilen, wird Quellentext oder Quellencode (nach dem englischen Begriff Source Code) genannt. Häufig übersetzt das Compilerprogramm dieses Quellenprogramm zuerst nicht sofort in ein ausführbares Programm in Maschinensprache 15.11.2005 14:01 Computer und Physik 4 of 14 http://physik.uni-graz.at/~cbl/C+P/admin/mk_combined_fi... (welches die Bezeichnung Executable-Code oder ausführbarer Code trägt), sondern in eine Art Zwischenprogramm, in dem noch nicht die tatsächlichen Speicheradressen eingetragen sind. Programme oder Programmteile in diesem Zwischencode ( früher Relative Binary Code oder Relocatable Binary Code, heute meist Object Code genannt) können, obwohl unabhängig voneinander übersetzt, dann relativ leicht zu einem gemeinsamen Maschinenprogramm kombiniert und unmittelbar vor der Ausführung mit den tatsächlichen Speicheradressen versehen werden. Häufig sind gerade Programmbibliotheken, in denen man immer wieder benötigte Unterprogramme findet, schon in dem teilübersetzten Object Code angelegt. Dies hilft dem Hersteller der entsprechenden Software bei der Sicherung vor Raubkopien; er stellt oft überhaupt nur diese Version seiner Programme zur Verfügung, die dann nicht ohne weiteres auf andere Maschinentypen oder, bei geeigneter Programmierung, selbst andere Maschinen gleichen Typs übertragen werden kann. Zurück zu möglichen Nachteilen des Einsatzes von höheren Sprachen. Die automatische Übersetzung mit Compilern kann meist nicht optimal sein, also nicht auf alle Eigenheiten des jeweiligen Computertyps oder der speziellen Problemstellung Rücksicht nehmen. Wie groß dieser Nachteil ist, hängt de facto vom Compiler-Programm ab, und diese werden in zunehmendem Masse "intelligenter". Ein guter Compiler hat eine Optimierungsphase, in der das Programm oft noch deutlich verbessert werden kann. Diese Verbesserung kann oft Fehler des Programmierers reparieren. Sollte er zum Beispiel eine bestimmte Rechenoperation unnötig wiederholt ausführen, so kann dies der Compiler erkennen, und auf die Wiederholung verzichten. Auch bei der Fehlersuche kann die vorhandene Software wichtige Hilfe leisten. Programme Ein Nachteil ist evident: die Programmiersprache ist eine eigene Sprache, die man erlernen muss, um sie richtig zu gebrauchen. Ideal wäre es, wenn man dem Computer die Problemstellung und den vorgeschlagenen Lösungsweg in der Umgangssprache mitteilen könnte. Von diesem Ziel sind wir weit entfernt, und in dieser Form wird es wohl nie erreichbar sein. Die Umgangssprache ist dazu viel zu vieldeutig. Dennoch ist noch viel Raum für Schritte in diese Richtung. Ein Programm ist ähnlich einem Handbuch zur Bedienung oder Reparatur einer komplizierten Maschine. Es besteht aus Teilprogrammen, die den Kapiteln und Absätzen entsprechen. Diese wiederum bestehen aus Befehlen, dem Äquivalent von Sätzen. Die logische Form der Befehle ist durch die Regeln der Sprache, die Syntax vorgeschrieben; die Befehle müssen also zumindest der Form nach richtig sein. Die Anordnung dieser Sätze unseres Buches muss Sinn machen, wenn man den Befehlen des Programmes folgt und alle Anweisungen richtig ausführt, so muss das Ergebnis unserer Bemühungen das gewünschte sein, also zum Beispiel die erfolgreiche Reparatur der Maschine. In einem Bedienungshandbuch finden wir verschiedene Typen von Anweisungen. Denke an einen Fotokopierapparat. Es könnte notwendig sein, Zähler der Maschine abzulesen oder Messungen durchzuführen. Man könnte aufgefordert werden, einige Zahlen auf einer Tastatur einzugeben. Man muss vielleicht auch bestimmte Handgriffe tätigen. Entsprechend gibt es verschieden Typen von Anweisungen in einem Programm. Sie alle folgen der Struktur der Von-Neumann Maschine. Man kann (a) Daten lesen oder speichern, (b) Daten in Registern manipulieren, also zum Beispiel zwei Datenregister addieren oder subtrahieren, (c) die Abfolge der Befehle beeinflussen, also zum Beispiel die Adresse des nächsten Befehls in Abhängigkeit von Werten bestimmter Daten verändern. Je höher das Niveau einer Programmiersprache ist, umso vielfältiger werden die Befehle wirken. Oft werden sie Kombinationen der drei genannten Gruppen sein. Zusätzlich zu diesen ausführbaren Anweisungen ist es aber notwendig, zunächst festzulegen, wie die Daten, die man bearbeiten möchte, denn eigentlich beschaffen sind. Soll ich die Ziffern eines Zähler meiner Fotokopiermaschine von oben nach unten oder von unten nach oben ablesen? 15.11.2005 14:01 Computer und Physik 5 of 14 http://physik.uni-graz.at/~cbl/C+P/admin/mk_combined_fi... Handelt es sich um eine Dezimalzahl und wenn ja, wo ist der Dezimalpunkt? Gibt der Zähler die Anzahl der Kopien oder die Dicke der Papierblätter an? All das hat Analogien in einem Programm, und daher gibt es die Gruppe der nicht ausführbaren Anweisungen, der sogenannten Vereinbarungsanweisungen. Sie sind im Grunde nur für den Compiler wichtig, da bei der Übersetzung in die Maschinensprache entschieden werden muss, welcher Art die zu verarbeitenden Daten sind. Aufgabe 2.2.1.A1 Aufgabe 2.2.1.A2 Aufgabe 2.2.1.A3 Aufgabe 2.2.1.A4 Aufgabe 2.2.1.A5 Hier is ein Link zu einer grafischen Übersicht zur Entwicklung der Computersparchen " Es ist dies ein Auszug aus dem Original: Computer Languages History from http://www.levenez.com/lang/ . Timeline ". 2.2.2 Maschinensprache und Assembler Die Vielfalt der Befehle eine Maschinensprache hängt von den Ausstattungsmerkmalen des Prozessors ab. Je mehr Bits für die interne Darstellung von Befehlen vorgesehen wurden, desto mehr Befehle kann der Hersteller einplanen. In der ersten Generation von Personalcomputern fand man häufig Prozessorchips des Typs M-6502 oder INTEL-8085A, das sind 8-Bit Prozessoren. Beide haben einen ähnlichen Satz von Befehlen und wir wollen den Befehlsvorrat am Beispiel des M-6502 vorstellen. Im Intel 80386 Reference Programmer's Manual findet man eine Liste der Maschinenbefehle des Intel 80386 Prozessors und hier ist das Reference Manual zum Motorola 68000 Prozessor .. Befehle des einfachen 8-Bit Prozessors Motorola 6502 (Mitte der 80er Jahre) Befehl Bedeutung Lade- und Speicherbefehle LDA A-Register laden LDX X-Register laden LDY Y-Register laden STA A-Register speichern STX X-Register speichern STY Y-Register speichern TAX A nach X übertragen TAY A nach Y übertragen TSX Stackzeiger nach X übertragen TXS X nach Stackzeiger übertragen TXA X nach A übertragen TYA Y nach A übertragen Stackbefehle Befehl Bedeutung Rechenbefehle ADC Addieren mit Übertrag SBC Subtraktion mit Übertrag SEC Setze Übertragsbit CLC Übertragsbit löschen SED Setze Dezimal-Modus CLD Dezimalindikator löschen CLV Überlauf löschen BIT Bit testen CMP mit A-Register vergleichen CPX mit X-Register vergleichen CPY mit Y-Register vergleichen Verzweigen, wenn Übertragsbit BCC gelöscht 15.11.2005 14:01 Computer und Physik 6 of 14 PHA PUSH: A-Register ins Stack bringen POP: Untersten Stack-Wert ins PLA A-Register bringen PUSH: Befehlsadresse ins Stack PHP bringen Sprungbefehle JMP Unbedingte Verzweigung JSR Verzweigung in ein Unterprogramm RTS Rückkehr aus Unterprogramm Interruptbefehle RTI Rückkehr nach Interrupt Interrupt-Möglichkeit wird SEI abgeschaltet CLI Interruptindikator löschen BRK Unterbrechung NOP Leerbefehl (keine Aktion) http://physik.uni-graz.at/~cbl/C+P/admin/mk_combined_fi... BCS BVC BVS BEQ BMI BNE BPL DEC DEX DEY INC INX INY AND ORA EOR ASL LSR ROL ROR Verzweigen, wenn Übertragsbit gesetzt Verzweigen, wenn Überlauf gelöscht Verzweigen, wenn Überlauf gesetzt Verzweigen, wenn Ergebnis gleich Null Verzweigen, wenn Ergebnis negativ Verzweigen, wenn Ergebnis ungleich Null Verzweigen, wenn Ergebnis positiv Speicher dekrementieren X-Register dekrementieren Y-Register dekrementieren Speicher inkrementieren X-Register inkrementieren Y-Register inkrementieren Logisches UND Logisches ODER Exklusives ODER Arithmetisches Verschieben nach links Logisches Verschieben nach rechts zyklisch nach links verschieben zyklisch nach rechts verschieben (a) Vom Speicher zum Prozessor und zurück Das ist eine Gruppe von Befehlen, die mit den Worten LOAD (LD) und STORE (ST) abgekürzt werden. Wie im ersten Teil besprochen, gibt es ja neben dem eigentlichen Speicher, in dem Daten und Programme liegen, auch Register, das sind die schnellen Rechenspeicher im Prozessor. Mit dieser Befehlsgruppe kann ein bestimmtes Speicherwort in ein Register gebracht werden, oder, umgekehrt, der Inhalt eines Registers an eine bestimmte Stelle im Speicher. Es gibt verschiedene Variationen in dieser Befehlsfamilie. So kann beispielsweise auch ein als Teil des Befehls angegebenes Datenwort direkt in ein Register geladen werden. Eine weitere Möglichkeit sind indirekte Adressangaben: der Registerinhalt soll an die Speicheradresse gebracht werden, die an einer anderen Speicheradresse angegeben ist. Die Zieladresse kann auch indiziert werden, also automatisch um den in einem weiteren Register gefundenen Wert erhöht werden. Die Transferbefehle gehören auch zu dieser Gruppe; dabei wird der Inhalt eines Registers in ein anderes Register kopiert. Diese Befehle können direkt im Prozessor ausgeführt werden, und sie gehören daher zu den schnellsten. (b) Arithmetische und logische Rechenbefehle Hier werden vorwiegend Registerinhalte manipuliert. Man kann eine Zahl von der in einem Register gespeicherten Zahl abziehen oder sie dazu zählen, man kann beim M-6502 auch Zahlen, die auf bestimmten Speicheradressen zu finden sind, zu 15.11.2005 14:01 Computer und Physik 7 of 14 http://physik.uni-graz.at/~cbl/C+P/admin/mk_combined_fi... Registern addieren oder davon subtrahieren. Daneben gibt es die Gruppe von Befehlen, bei denen Bit für Bit zweier Datenworte miteinander durch die logischen AND ("logisches Und"), OR ("logisches Oder") und XOR ("exklusives Oder") Operationen verknüpft werden. Auch Verschiebung oder zyklische Vertauschung der Bits in einem Register ist möglich. Schließlich gehören in diese Gruppe die Vergleichsbefehle, in denen ein Registerinhalt mit einem Datenbyte verglichen wird und je nach Ergebnis bestimmte Registerbits gesetzt oder gelöscht werden. Zusammen mit der Gruppe der Sprungbefehle erlaubt dies die Programmierung von Entscheidungsschritten und Verzweigungen im Programm. (c) Stackbefehle Das "Stack" ist eine besondere, reservierte Reihe von Speicherplätzen, die als erweiterter Adressspeicher eingesetzt wird. Bei Programmbeginn wird es gelöscht, oder mit bestimmten Befehlsadressen belegt. Während der Programmausführung kann es sich nützlich erweisen, die momentane Befehlsadresse abzuspeichern, sich also "zu merken", um dann später wieder an dieser Stelle fortsetzen zu können. Das ist immer dann der Fall, wenn man eine Zwischenrechnung ausführen möchte, also zwar das Ergebnis braucht, aber den entsprechenden Rechnungsablauf in einem Unterprogramm angegeben hat. Man speichert die momentane Adresse im Hauptprogramm an die letze Stelle des Stacks und arbeitet im Unterprogramm weiter. Am Ende des Unterprogramms holt man sich die letzte Stackadresse, um dort, im Hauptprogramm weiterzumachen. Sollte es sich in dem Unterprogramm notwendig erweisen, ein weiteres Unterprogramm aufzurufen, so wiederholt man das Spiel. Das Stack wird um eine Stelle nach oben verschoben (die oberste Adresse geht dabei verloren), und an unterster Stelle wird wieder die Adresse der Verzweigungsstelle gespeichert und dann ins Unterunterprogramm verzweigt. Nach Erledigung dieser Zwischenrechnung wird wieder auf die an unterster Stelle im Stack angegebene Adresse (also zurück ins Unterprogramm) gewechselt, und das Stack entsprechend um eine Stelle nach unten verschoben. Auf diese Weise hält man automatisch Buch über die Verzweigungsstruktur. Die entsprechenden Prozessorbefehle heißen PUSH und POP, sie bewirken das Verschieben des Stacks um eine Stelle hinauf oder hinunter. Bei PUSH wird die der momentane Inhalt des Befehlsadressregisters dem Stack hinzugefügt, bei POP wird die an unterster Stelle im Stack stehende Adresse ins Befehlsadressregister geladen und aus dem Stack gelöscht. (d) Sprungbefehle Bei ihnen wird einfach der Inhalt des Befehlsadressregisters verändert und so der nächste auszuführende Befehl bestimmt. Bei den bisher genannten Befehlen wird das Adressregister einfach um eine Stelle erhöht, also als nächstes der folgende Befehl im Speicher ausgeführt. Nun kann auch ein beliebiger Wert in dieser spezielle Register gebracht werden. Eine Variante der einfachen Sprungbefehle (JUMP) sind bedingte Sprungbefehle, bei denen die Befehlsadresse nur dann geändert wird, wenn beispielsweise ein bestimmtes Bit in einem Register gesetzt ist. Zusammen mit den Vergleichsbefehlen erlaubt dies eine vergleichsabhängige Verzweigung im Programm, also einen echten Entscheidungsschritt. Sprungbefehle, bei denen gleichzeitig die Ursprungsadresse an einem bestimmten Platz abgespeichert wird, erlauben die spätere Rückkehr an diese Stelle ("Return-Jump" und "Return"). Diese Kombination wird oft bei Verzweigung in und Rückkehr aus einem Unterprogramm verwendet. 15.11.2005 14:01 Computer und Physik 8 of 14 http://physik.uni-graz.at/~cbl/C+P/admin/mk_combined_fi... (e) Interrupt- und I/O-Befehle Neben dem Programm- und Datenspeicher gibt es noch andere "Speicher", das können sowohl echte externe Speicher wie Diskettenstation oder Magnetbandspeicher sein, aber auch Eingabe- und Ausgabemedien wie Tastatur, Maus oder andere Systeme. Die Kommunikation mit diesen Medien erfolgt oft über bestimmte, dafür reservierte Speicheradressen und über Interruptleitungen. Mit Befehlen dieser Gruppe kann die Reaktion des Programms auf einen Interrupt, der das Anliegen von externen Signalen anzeigt, gesteuert werden. Es ist dann Aufgabe des Programms, durch entsprechende Aktionen auf diesen Interrupt zu reagieren, also etwa Daten zu übertragen oder andere Aktionen einzuleiten. Interessant ist ein eigenartiger Befehl, den man in allen Prozessoren braucht und der nichts bewirkt: NOP (die Abkürzung für "no operation")! Dieser Befehl setzt den Prozessor für genau einen Rechentakt in Ruhe. Dies kann zum Beispiel dann wichtig sein, wenn man zeitabhängige Programmschritte durchführt. Es kann sich als notwendig erweisen, eine Reihe von Rechentakten "zu schlafen", um etwa auf das Eintreffen eines Interrupt-Signals zu warten. Prozessoren mit größerer Bandbreite haben einen größeren Befehlsvorrat. Der Motorola-68000 etwa, als 16-Bit Prozessor, hatte bei der Gruppe der arithmetisch-logischen Operationen auch Multiplikationsbefehle. Beim einfachen M-6502 mußte eine Multiplikation noch auf eine Reihen von Additionen zurückgeführt werden. Auch weitere logische Befehle und raffiniertere Abfragen waren beim 68000-er eingebaut, ebenso die Möglichkeit der Manipulation von mehreren Registern mit einem Befehl. Zusätzliche "Co-Prozessoren" erlaubten die schnelle Verwendung von Gleitkommazahlen (Floating Point Accelerators) und speziellen mathematischen Funktionen. Neuere Intel-Prozessoren (seit Intel 80386) haben eine Breite von 32 Bit und in jeder Generation kommen ein paar spezieller Befehle dazu. Die in größeren Computern eingesetzten Prozessoren sind Spezialanfertigungen, die nur für den entsprechenden Computertyp gebaut werden und haben dementsprechend oft spezielle Befehle, die der zur Verfügung stehenden Hardware angepasst sind. So ist auf dem Niveau der Maschinensprache keinerlei Standard in Sicht, im Gegenteil, jeder neue Prozessortyp bringt neue Befehlssätze und es herrscht bereits eine babylonische Sprachverwirrung, die nur durch die Dominanz eines Herstellers (Intel) gemindert wird. Eine kleine Hilfe bieten Assemblersprachen, die zumindest die wichtigsten Befehlstypen einheitlich zu bezeichnen erlauben, und diese dann in die entsprechende Maschinensprache übersetzen. Dennoch müssen bei jedem neuen Prozessortyp viele Systemprogramme neu geschrieben werde. Man ist daher dazu übergegangen, auch Systemprogramme in einer etwas höheren Sprache wie etwa C zu schreiben. Diese beiden Sprachen sind in der Grauzone zwischen eigentlichen Maschinensprachen und höheren Sprachen angesiedelt. Sie sind maschinennahe, aber nicht an eine bestimmte Hardware gebunden und so universell verwendbar. Man muss dann für einen neuen Prozessor nur mehr ein Programm schreiben, das zum Beispiel den C-Compiler in die Maschinensprache übersetzt. Ja, man kann sogar noch raffinierter vorgehen und auch den größten Teil des C-Compilers in C schreiben, und nur einen kleinen Teil (der dann später die Übersetzung des Rests besorgt) in Maschinensprache zu programmieren. Dieser Trick erinnert an Freiherr von Münchhausen, der sich an seinen Schnürsenkeln aus dem Sumpf zog - oder es zumindest behauptete - und wird daher "Bootstrap" (Schnürsenkel) genannt. Das verbreitete Betriebssystem UNIX ist in C geschrieben. Aufgabe 2.2.2.A1 2.2.3 Höhere Sprachen 15.11.2005 14:01 Computer und Physik 9 of 14 http://physik.uni-graz.at/~cbl/C+P/admin/mk_combined_fi... Zunächst ein kurzer Überblick. Es gibt Sprachen für die unterschiedlichsten Anwendungsbereiche. Die für uns wichtigsten Gebiete sind wohl numerisches und symbolisches (auch: algebraisches) Rechnen. Numerische Rechnungen arbeiten mit Zahlen und die Ergebnisse sind Zahlen, wie etwa 1+2.55 ergibt 3.55. Symbolische Rechnungen arbeiten mit Symbolen, also zum Beispiel formalen Ausdrücken mit Variablennamen wie etwa (a+b) 2 ergibt a2 + 2 a b + b2. In der Tabelle findet man höhere Sprachen, die vorwiegend für numerisches Programmieren vorgesehen sind. Sprache Erstentwicklung Kommentar Ada Reference manual July 1980 Approved implementations 1983, 1995 Sponsored by US Dept. Defense ALGOL 1960 1968 (much modified) Mostly Europe; relatives like Pascal, little used nowadays SIMULA 1967 ALGOL extension APL Iverson's 1962 book APL/360-1967 Concise, very symbolic, little used nowadays BASIC 1965 Developed for student use at Dartmouth College. COBOL Initial design-1959 First running versions -1960 Primarily for business applications, outdated FORTRAN 1957; 1977, 1990, 1995 FORmula TRANslator, Standards f77, f90, f95; vector operations since f90; first successful "algebraic" language LISP 1960 List programming language, basis for computer algebra languages (e.g. Reduce) and artifical intelligence projects LOGO 1967 Junior high level; turtle geometry Pascal 1971, ISO Standard General purpose, ALGOL relative; promoted through Borlands TurboPascal, later offspring: Borland Delphi PL/I 1968 (IBM compiler) Attempts at a comprehensvie language, outdated SNOBOL 1962 Symbolic (string manipulation) language, outdated C 1978 (Kernigham, Ritchie) General purpose, closer to machine language than e.g. FORTRAN, excellent for string processing, but included arithmetic, heavily used C++ 1985 (AT&T, Strostrup; no ANSI standard yet) C-based and Object oriented language (Class structure) Daneben gibt es zahlreiche special-purpose Sprachen wie TeX/LaTex (für Publikationen) POSTSCRIPT als Druckdefinitionssprache HTML für Hypertext Seiten C(Shell), PHP, PERL etc. als Scripting Sprachen usw.usw. Diese gehören aber nicht im engeren Sinn zu den in der Physik relevanten höheren Sprachen. Sehr wohl relevant sind die sogenannten "symbolischen Sprachen", über die weiter unten noch Informationen folgen. Wozu höhere Sprachen verwenden? Mir fallen folgende Vorteile ein: Einfachere Strukturierung und kürzere Programme: Einzelne Befehle (z. B. do...while, 15.11.2005 14:01 Computer und Physik 10 of 14 http://physik.uni-graz.at/~cbl/C+P/admin/mk_combined_fi... print usw.) entsprechen meist umfangreichen "Unterprogrammen". Ein möglicher Nachteil ist, dass die allgemeinere Art dieser standardisierten "Unterprogramme" die Ausführung verlangsamt. Andrerseits wird im Design viel mehr Zeit in die Optimierung dieser "Unterprogramme" investiert. Einfacheres Debugging: In einer klaren Programmiersprache treten weniger Fehler auf und wenn doch, sind sie leichter zu finden Höhere Produktivität: Durch die modulare Bauweise können große Projekte einfacher und schneller abgewickelt werden Falls du dich mit ein der der Programmiersprachen beschäftigen willst, sie zum Beispiel lernen willst: hier sind ein paar Links für ein Selbststudium. Brian Brown: C-course on the web (Vienna mirror) A. D. Marschall: Programming in C (Vienna mirror) MAN-T&EC: F90 Kurs fü F77-Programmierer: Original oder Karlsruhe Mirror F90 Kurs Univ Liverpool: F90 Course World Lecture Hall: e.g. Courses on Computer Science and on Physics Auch bei uns wird ein Kurs zu C und C++ angeboten: U. Hohenester: Programmierung in C und C++ Natürlich gibt es noch mehr Sprachen, die man als höhere Sprachen bezeichnen kann. Das hier sollte nur eine Liste der im Bereich der Physik häufig verwendeten sein. Hier nicht vergessen, sondern unter "Symbolische Sprachen" diskutiert sind Mathlab, Maple und Mathematica. Sprachen sollten nicht mit Programmbibliotheken verwechselt werden. OpenGL oder DirectX sind Sammlungen von Programmen, die einen hardwareunabhängigen Zugriff auf die Grafik erlauben. 2.2.4 Unterschiede zwischen den Programmiersprachen Obwohl das Konzept des Computers seit den 30-er Jahren in mathematisch-abstrakter Formulierung vorgelegen war, wurden Programmiersprachen zuerst doch eher ad hoc eingeführt, als Abkürzungen, die dem "an der Basis arbeitenden" Programmierer das Leben erleichtern sollten. Die ersten Sprachen waren daher zunächst nicht sehr klar durchdacht und wurden in darauf folgenden Jahren immer wieder in ihrer Struktur verändert oder um neue Befehle erweitert. Erst Mitte der 50-er Jahre begann man, sich Gedanken zur Syntax einer Programmiersprache zu machen und Noam Chomsky, einer der bekanntesten Linguisten, hat wohl die wesentlichsten Beiträge dazu geliefert. Man versuchte, die Sprache mathematisch sauber als formale Sprache zu definieren und diese Definition in eine sogenannte "Normalform" zu bringen. So gibt es etwa seit 1960 die Backus-Naur-Normalform für die Programmiersprache ALGOL. Es ist in dieser Formulierung möglich, eindeutig und mit kleinstmöglichem Aufwand die formale Richtigkeit von Sätzen der jeweiligen Sprache zu überprüfen. Bei einer Klassifikation von Programmiersprachen kann man vier Aspekte anführen. 1. 2. 3. 4. Das Alphabet der Sprache, die zulässigen Befehle und Variablennamen Die Struktur und Art der zulässigen Daten und nicht ausführbaren Anweisungen Die Art und Form der arithmetischen und anderen Ausdrücke und Anweisungen. Die Möglichkeiten der Programmstruktur Um einen groben "Geschmack" für die unterschiedlichen Programmiersprachen zu vermitteln, gebe ich hier einige wenige typische Beispiele für (ausführbare und nicht-ausführbare) Anweisungen in F90 und C. 15.11.2005 14:01 Computer und Physik 11 of 14 http://physik.uni-graz.at/~cbl/C+P/admin/mk_combined_fi... Fortran 90 C Alphabet Alle Buchstaben und Ziffern sowie eine Reihe von Zeichen wie etwa =+-~/(),.%': _!"%&;<>? dazu noch []|^ Beispiele für Variablennamen High, Low, what_a_mess, a(20), num23 High, Low, what_a_mess, a[20] Vereinbarungsanweisungen integer :: i,j,k real, dimension(0:100) ::x int i,j,k; float x[101]; Ausführbare Anweisungen a=a+5.3*b+sin(c) a=a+5.3*b+sin(c); i=i+1; i++; Abfragen if (a<0) then a=27.3 b=a*a endif if(a<0) {a=27.3; b=a*a;} Schleifen do i=1,100 a=a+i enddo for (i=1; i<101; i++) a=a+1; Programmbeispiele C und Fortran90 Am Beispiel der Erstellung einer Liste von Werten und der anschließenden Sortierung soll nun eine Idee davon gegeben werden, wie C und F90 Programme aussehen. Programmierbeispiel F90 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Sortieren der Werte von sin(i) für i=0 bis 9 (in Schritten von 1) !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Vereinbarungsanweisungen: real, dimension(0:9) real integer :: a :: b :: i,j ! Berechnung der Werte in eine Liste do i=0,9 a(i)=sin(float(i)) enddo ! Printout der unsortierten Liste 15.11.2005 14:01 Computer und Physik 12 of 14 http://physik.uni-graz.at/~cbl/C+P/admin/mk_combined_fi... print *,"Unsortierte Liste: ", a ! Sortieren (der jeweils kleinste der restlichen Werte ! wird nach vorne gebracht) do i=0,8 do j=i+1,9 if(a(j).lt.a(i)) then b=a(i) a(i)=a(j) a(j)=b endif enddo enddo ! Printout der sortierten Liste print *,"Sortierte Liste: stop end ", a Programmierbeispiel C /********************************************************************** * Sortieren der Werte von sin(i) f0r i=0 bis 9 (in Schritten von 1)* **********************************************************************/ /* Header Files einlesen: */ #include <stdio.h> #include <math.h> void main() { /* Vereinbarungsanweisungen: */ int i,j; float a[10],b; /* Berechnung der Werte in eine Liste */ for(i=0; i<10; i++) a[i]=sin((float) i); /* Printout der unsortierten Liste */ printf("Unsortierte Liste:\n"); for(i=0; i<10; i++) printf("%10.5f\n",a[i]); /* Sortieren (der jeweils kleinste der restlichen Werte wird nach vorne gebracht) */ for(i=0; i<9; i++ ) for(j=i+1; j<10; j++ ) if( a[j] < a[i] ) { b=a[i]; a[i]=a[j]; a[j]=b;} 15.11.2005 14:01 Computer und Physik 13 of 14 http://physik.uni-graz.at/~cbl/C+P/admin/mk_combined_fi... /* Printout der sortierten Liste */ printf("Sortierte Liste:\n"); for(i=0; i<10; i++) printf("%10.5f\n",a[i]); } 2.2.5 Softwareprojekte Wie bei Kriminalromanen, geht es bei der Planung von (größeren) Softwareprojekten um die berühmten W: Was Wann Wieviel Wo Wer Ziel des Projekts, Randbedingungen, Prioritäten; Arbeitsplan Budget, Aufwand Welche Computer, Arbeitsräume usw.? Wer soll die Arbeit durchführen, welche Organisationsstrukur Von Beginn darf man nicht auf die Dokumentation vergessen. Einerseits ist sie am Weg hilfreich, andrerseits wird man vermutlich auch nach Fertigstellung immer wieder Fragen haben, deren Antwort man dort finden kann: Proposal (Schriftliche Darstellung der großen W), Arbeitsverlauf, interne Dokumentation, Manual. Das Programm solls sich selbst dokumentieren, also ausreichend viel Erklärungstext enthalten. Tips zur effizienten Programmerstellung Für Variablen mnemonische Namen verwenden (Temperatur=37.5), ebenso für Programmnamen und Datenfiles, u.U. mit einer Versionsbezeichnung versehen (GaussIntegral_01.c) "Selfdocumenting programs": Dokumentation direkt ins Programm einbauen. Die verwendeten Algorithmen klar bezeichnen, u,U, mit Literaturhinweis. Versionsbezeichnung am Beginn. Alle Variablen explizit definieren Text durch Einrückung strukturieren Strukturiert programmieren Der Begriff "strukturiert programmieren" Das muss etwas erläutert werden. Strukturiert bedeutet eine Trennung von Aufgaben in wohldefinierte Blöcke (Unterprogramme mit klarem Input und Output). Das erlaubt ein unabhängiges Testen dieser Blöcke und auch die Weiterverwendung in anderen Programme. Früher wurde oft ein sogenannte Flussdiagramm verwendet, dessen Bedeutung aber überschätzt wird. Wichtig ist, ein grobes Schema des Programmes, möglichst auf einer Seite, zu haben. Die wichtigsten Teilaufgaben tauchen dort zum Beispiel als Kästchen mit Kurzbeschreibung auf. Man kann diese Kästchen dann auf getrennten Blättern weiter ausarbeiten und detaillieren (Aufgabe, Methode, Parameter und Variable etc.). Ich beginne meist mit einem einfachen Hauptprogramm, in dem die Aufgaben der Unterblöcke zunächst nur in Kommentarform auftauchen. Schrittweise wird dann dieses Skelett mit Fleisch gefüllt, also die Unterblöcke entworfen, getestet und eingefügt. Kompliziertere Teile kann man in einem Zwischenschritt durch "Dummy-Programme" ersetzen, die nichts tun, aus die Funktion der 15.11.2005 14:01 Computer und Physik 14 of 14 http://physik.uni-graz.at/~cbl/C+P/admin/mk_combined_fi... tatsächlichen Unterprogramme (die noch nicht fertig sind) "vorzugeben, also zum Beispiel zufällige Testdaten liefern. So "wächst" das Programm bis zur Fertigstellung. Es wird also nicht Stein auf Stein "gebaut", sondern es "wächst" - in der Tat - durch schrittweise Erweiterung. Gleiches gilt für die Optimierung. Es empfiehlt sich, zuerst sehr einfache, vor allem übersichtliche, Methoden der Rechnung zu programmieren. Wenn diese Programme funktionieren, kann man einzelne Teile schrittweise optimieren, zum Beispiel durch Umorganisation von Schleifen und ähnlichem ("progressive refinement"). Aus all dem wird klar, dass man wichtige Strukturänderungen später kaum mehr durchführen kann. Daher soll vor Beginn vor allem die allgemeine Programm Struktur die Struktur der wichtige Daten genau planen. Ein amüsantes Buch (und ein Klassiker) zum Thema Projektplanung und Programmentwicklung ist F. P. Brooks: The mythical man-month (Addison-Wesley, 1995) 15.11.2005 14:01