UnterprogrammeDas wichtigste aller Strukturierungsmittel überhaupt stellen die Grundlagen: UnterprogrammeUnterprogramme dar. Nur mit ihnen ist es möglich, einzelne Befehlsfolgen, die in einem Programm mehrmals vorkommen, nur einmal im Speicher des Computers niederzuschreiben und trotzdem beliebig oft auszuführen. Im Gegensatz zu Schleifen können diese Befehlssequenzen von jeder beliebigen Stelle des Programmes aus aufgerufen werden. Programmtechnisch gesehen stellt ein Die CPU Z80: Unterprogramm-AufrufeUnterprogramm-Aufruf einen Sprung dar. Beim normalen Sprung wird der Befehlszeiger sofort mit der Sprungadresse geladen. Die Stelle, ab der der Sprung erfolgte, wird vergessen. Beim Die CPU Z80: Unterprogramm-AufrufeUnterprogramm-Aufruf wird diese Adresse aber gerettet. Bevor der Befehlszeiger mit der neuen Adresse geladen wird, wird sein alter Wert auf einem Stapel abgelegt. Dann erst wird der Befehlszeiger verändert. Danach wird, wie bei einem normalen Sprung, das Programm ab der neuen Adresse abgearbeitet. Ist die Behandlung des Grundlagen: UnterprogrammeUnterprogramms abgeschlossen, so muss ein spezieller Rücksprungbefehl abgearbeitet werden (auch ein Sprung!), der den alten Wert wieder vom Stack herunternimmt und in den Befehlszeiger zurücklaedt. Danach wird das alte Programm genau ab der Stelle fortgesetzt, an der der Die CPU Z80: Unterprogramm-AufrufeUnterprogramm-Aufruf erfolgte. Da die Rücksprung-Adressen auf einem Stapel abgelegt werden, kann man Grundlagen: UnterprogrammeUnterprogramme auch schachteln: Wird in einem Grundlagen: UnterprogrammeUnterprogramm ein weiteres Grundlagen: UnterprogrammeUnterprogramm aufgerufen, so wird diese Rücksprungadresse ebenfalls auf den Stapel gelegt. Jeder Rückkehr-Befehl nimmt immer nur die oberste Adresse vom Stapel. Ob darunter noch eine weitere Rückkehradresse schlummert, ist solange uninteressant, bis ein weiter Return-Befehl abgearbeitet wird. In Einleitung: BASIC In Assembler werden die Mnenonics 'Maschinencode über HIMEM: CALLCALL' und 'RET' gebraucht. Der Maschinenstapel der Die ICs im Überblick: Die CPU Z80 Die folgenden Programme und Grafiken zeigen das Verhalten von Grundlagen: UnterprogrammeUnterprogrammen und die Auswirkungen auf den Stapel: 100 Operationen: BD5B / 349A / 349A: FLO SUBA=10:LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL Einträge auf dem Stack: ___ ___ ___.___.__________|120|__________.___.__________|140|_________.___.___ [100] [200] [120] [200] [140] Grafische Darstellung: +-------------+ +-----|----------+ | Operationen: BD5B / 349A / 349A: FLO SUBA=10:LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL Verschachtelter Unterprogrammaufruf in Assembler: 90 100 PROG: PUSH BC ----> Reihenfolge der Bearbeitung 110 PUSH DE 120 Maschinencode über HIMEM: CALLCALL UP1 [100] [110] [120] [200] [300] [210] 130 POP DE [130] [140] [300] [150] 140 Maschinencode über HIMEM: CALLCALL UP2 150 POP BC 160 ... 200 UP1: Maschinencode über HIMEM: CALLCALL UP2 210 RET 300 UP2: RET Einträge auf dem Stack (jeweils nach dem Befehl): ___ ___ |210| ___ ___ |130| |130| |130| ___ ___ ___ | DE| | DE| | DE| | DE| | DE| ___ |150| ___ __.___.__| BC|__|_BC|__|_BC|__|_BC|__|_BC|__|_BC|__|_BC|__|_BC|__|_BC|__.___.___ [ 90] [100] [110] [120] [200] [300] [210] [130] [140] [300] [150] Grafische Darstellung: +------------+ +------------+ | | | | | UP1: Maschinencode über HIMEM: CALLCALL UP2 ----|--------+ | PROG: PUSH BC | RET <---|---+ | | PUSH DE | | | | UP2: RET Maschinencode über HIMEM: CALLCALL UP1 --+ | | | | | POP DE <--------------+ | +----+ | Maschinencode über HIMEM: CALLCALL UP2 ---------------------------+ | POP BC <---------------------------------------+ ... Grundlagen: UnterprogrammeUnterprogramme spielen bei der Programmiertechnik: ModularisierungModularisierung von Problemen eine ganz große Rolle. Die einzelnen Problem-Haeppchen werden nämlich nicht einfach hintereinander gehängt (Methode Spagetti-Code) sondern als Grundlagen: UnterprogrammeUnterprogramme gelöst, die von übergeordneten Modulen aufgerufen werden können. Das hat den Vorteil, dass einzelne Module mehrfach verwendet werden können. Als Grundlagen: UnterprogrammeUnterprogramme kann man ja von jeder beliebigen Stelle auf sie zugreifen. Vor allem die primitivsten Routinen werden am häufigsten aufgerufen. So muss man sich beispielsweise klarmachen, dass die Ausgabe von Buchstaben auf dem Bildschirm auch von einem Grundlagen: UnterprogrammeUnterprogramm erledigt wird. Ein einfacher 'Maschinencode über HIMEM: CALLCALL' auf die entsprechende Routine der Text-VDU, alles weitere erledigt dieses Modul. Kaum einer weiß, wie es geschieht, aber hinterher steht das Zeichen auf dem Bildschirm. Und das ist wieder das Kennzeichen strukturierter Programmierung: Um den Die Firmware des Schneider CPC: ÜberblickÜberblick zu bewahren, darf man sich nicht damit befassen, wie einzelne Grundlagen: UnterprogrammeUnterprogramme, die 'laufen', ein Problem bewältigen. Interessant ist nur die Schnittstellen-Beschreibung: Eingaben - Die Fließkomma-Routinen: FunktionenFunktion - Ausgaben
Auch die Befehle einer Hochsprache können als Programm-Module aufgefasst werden. Ein einfaches 'PRINT' in Einleitung: BASIC Das gilt sogar für CPU-Befehle, zumindest für die modernen Mikroprozessoren mit 'Micro-Code-ROMs': Wie innerhalb der Die ICs im Überblick: Die CPU Z80 VariablenNeben dem Programm-Ablauf ist die Speicherung von Unterprogramme: VariablenVariablen ein wichtiger Punkt. Es handelt sich dabei um die oberste Ebene der 'Datenspeicherung'. Eine der Hauptaufgaben von Hochsprachen ist es, den Programmierer gerade vom Problem der Variablen-Speicherung zu entlasten. Basic-Programmierer brauchen sich überhaupt nicht damit zu befassen. Für sie kann man Unterprogramme: VariablenVariablen einrichten, indem man einem Namen einfach einen Wert zuweist: 100 LET Operationen: BD5B / 349A / 349A: FLO SUBA=123.456
Dafür stellt Einleitung: BASIC globale VariablenAuf Variablen: globale Variablenglobale Variablen kann man, im Gegensatz zu Variablen: lokale Variablenlokalen Variablen, vom gesamten Programm aus zugreifen. Hat das Hauptprogramm beispielsweise in der Unterprogramme: VariablenVariablen 'Operationen: BD5B / 349A / 349A: FLO SUBA' einen wichtigen Wert gespeichert, so ist es programmtechnisch unmöglich zu verhindern, dass irgend ein Grundlagen: UnterprogrammeUnterprogramm diese Unterprogramme: VariablenVariable 'Operationen: BD5B / 349A / 349A: FLO SUBA' ebenfalls benutzt und verändert. Das muss man logisch in den Griff bekommen, und im Grundlagen: UnterprogrammeUnterprogramm eben nur Unterprogramme: VariablenVariablen mit anderen Namen benutzen. Es ist aber ein wesentlicher Gesichtspunkt bei der Programmiertechnik: ModularisierungModularisierung von Problemen, dass die Unterprogramme: VariablenVariablen eines Programmes erhalten bleiben, wenn es Grundlagen: UnterprogrammeUnterprogramme aufruft. Andernfalls erhielte man eher so eine Art 'Killermodule'. In Einleitung: BASIC lokale VariablenDieses Problem der 'Datenkapselung' ist nur mit Variablen: lokale Variablenlokalen Variablen zu lösen. Die aufgerufenen Programm-Module müssen sich ihre eigenen Unterprogramme: VariablenVariablen einrichten, vollkommen unabhängig von bereits bestehenden Unterprogramme: VariablenVariablen, die möglicherweise den gleichen Namen haben. Es darf den Modulen gar nicht möglich sein, Unterprogramme: VariablenVariablen der rufenden Programme zu verändern. Wenn überhaupt, dürfen dafür nur genau definierte Befehle vorgesehen sein. Variablen: lokale VariablenLokale Variablen werden deshalb immer auf einem Stapel eingerichtet. Oftmals ist das sogar direkt der Prozessorstapel, auf dem auch die Rücksprungadresse des Modul-Aufrufs abgelegt wird. Auch in Einleitung: BASIC 100 DIM stk(100):sp=0 ' Stapel einrichten und Stapelzeiger initialisieren 110 FOR i=0 TO 100 STEP 10 ' Unterprogramme: VariablenVariable 'i' wird im Hauptprogramm benutzt 120 eingabe=i ' Eingabe für Modul in 'eingabe' 130 GOSUB 200 ' Modul-Aufruf 140 PRINT ausgabe ' Ausgabe des Moduls in 'ausgabe' ausdrucken 150 NEXT 160 END 200 stk(sp)=i:sp=sp+1 ' Unterprogramme: VariablenVariable 'i' retten (PUSH i) 210 stk(sp)=j:sp=sp+1 ' Unterprogramme: VariablenVariable 'j' retten (PUSH j) 220 j=0 ' lokales 'j' kann geändert werden 230 FOR i=eingabe TO eingabe+9 ' lokales 'i' wird geändert. 'eingabe' benutzt 240 j=j+PEEK(i) 250 NEXT 260 ausgabe=j ' Ausgabe in Unterprogramme: VariablenVariable 'ausgabe' 270 sp=sp-1:j=stk(sp) ' Unterprogramme: VariablenVariable 'j' restaurieren (POP j) 280 sp=sp-1:i=stk(sp) ' Unterprogramme: VariablenVariable 'i' restaurieren (POP i) 290 RETURN Die Variablen: lokale Variablenlokalen Variablen wurden simuliert, indem das Modul alle Unterprogramme: VariablenVariablen, die es benutzte, zuerst auf den Stack gerettet hat. Danach konnten die (tatsächlich immer noch Variablen: globale Variablenglobalen) Variablen 'lokal' verändert werden. Nach Abschluss der Routine wurden die ursprünglichen Werte wieder in die veränderten Unterprogramme: VariablenVariablen zurückgeschrieben, so dass dem aufrufenden Programm keine Daten verloren gehen konnten. In Maschinensprache stehen aber bereits ein Stapel zur Verfügung, auf dem auch Die Tonausgabe: Das Kontrollregister (Reg. 7) Erklärung der Anschlussbelegung: TestTEST: LD HL,TEXT1 Maschinencode über HIMEM: CALLCALL MELDNG RET ; MELDNG: PUSH AF Maschinencode über HIMEM: CALLCALL MELD1 POP AF RET ; MELD1: LD Operationen: BD5B / 349A / 349A: FLO SUBA,(HL) INC HL AND Operationen: BD5B / 349A / 349A: FLO SUBA RET Z Maschinencode über HIMEM: CALLCALL #BB5A ; TEXT VDU: BB5A: TXT OUTPUTTXT OUTPUT JR MELD1 ; TEXT1: DEFM "Hallo Schneider User !" DEFB 0 Das Grundlagen: UnterprogrammeUnterprogramm MELD1 enthält eine Schleife, die HL als Zeiger auf einen auszudruckenden Text benutzt. Um die Zeichen auszudrucken, wird der Vektor TEXT VDU: BB5A: TXT OUTPUTTXT OUTPUT aufgerufen. Wird nicht MELD1 sondern MELDNG aufgerufen, so wird für die Dauer des Grundlagen: UnterprogrammeUnterprogramms das Doppelregister AF (Akku und Die Z80: Wirkung der Z80-Befehle auf die FlagsFlags) gerettet. TEXT VDU: BB5A: TXT OUTPUTTXT OUTPUT rettet auf entsprechende Weise alle Die Tonausgabe: Das Kontrollregister (Reg. 7) Für das Modul MELDNG gilt also folgende Schnittstellen-Beschreibung: Übergabe von Argumenten und ErgebnissenIn Einleitung: BASIC Eine Ausnahme bilden aber die vom Programmierer definierbaren Die Fließkomma-Routinen: FunktionenFunktionen: 10 DEF FNmittel(Operationen: BD5B / 349A / 349A: FLO SUBa,LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL An eine FUNCTION kann man Argumente übergeben (in diesem Fall SIN(i) und COS(i)), die in die Formel der FUNCTION anstelle der 'Platzhalter' 'Operationen: BD5B / 349A / 349A: FLO SUBa' und 'LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL Andere Programmier-Sprachen haben genau festgelegte Iterationen - Schleifen: StrukturStrukturen, mit denen an ein Grundlagen: UnterprogrammeUnterprogramm (Wort, Prozedur, Modul oder wie es auch immer genannt wird) Werte übergeben und auch wieder übernommen werden können: LOGO:to mittelwert :Operationen: BD5B / 349A / 349A: FLO SUBa :LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL Aufruf: mittelwert 100 200 [ENTER] 150 PASCAL:function mittelwert (Operationen: BD5B / 349A / 349A: FLO SUBa,LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL Aufruf: writeln (mittelwert(100,200)) FORTH:: mittelwert <-- Beginn der Definition des Wortes 'mittelwert' + 2 / <-- + -> Addiere die beiden obersten Stack-Einträge 2 -> Push die Zahl '2' auf den Stapel / -> Dividiere den vorletzten dch. den letzten Stack-Eintrag ; <-- Ende der Definition Aufruf: 100 200 mittelwert . [ENTER] 150 In Einleitung: LOGO In Übergabe von Argumenten und Ergebnissen: FORTH:FORTH, das grundsätzlich alle Die Fließkomma-Routinen: OperationenOperationen auf seinem Variablen-Stack durchführt, nehmen alle 'Worte' (Forth-Prozeduren) die Argumente von diesem Stapel und legen das Ergebnis hinterher wieder darauf ab. Vor einem Aufruf eines Wortes müssen die Basic und Maschinencode: ParameterParameter also auf dem Stapel abgelegt werden. Eine Kontrolle, ob das auch wirklich bei jedem Aufruf geschieht, gibt es nicht. Allenfalls werden vom Forth-Compiler Erklärung der Anschlussbelegung: TestTests auf Unterschreiten des Stapelbodens in die compilierten Worte eingefügt. In Assembler ist es, zumindest bei der Z80-CPU üblich, Argumente in Registern zu übergeben. Bei komplizierteren Daten-Typen, wie beispielsweise Datentypen: StringsStrings oder Fließkomma-Zahlen, werden die Die Tonausgabe: Das Kontrollregister (Reg. 7) Die Arithmetik-Routinen des CPC-Betriebssystems können hierbei als Beispiel dienen. Fast alle befolgen folgende Schnittstellen-Beschreibung: Als Beispiel wird im folgenden Programm das Mittelwert-Problem auch noch in Assembler gelöst. Eingaben sind (system-konform) das HL und DE-Register, in denen Zeiger auf zwei Fließkomma-Zahlen erwartet werden. ADD: EQU #BD58 ; CPC 664: #BD79 ; CPC 6128: #BD7C DIV: EQU #BD64 ; CPC 664: #BD85 ; CPC 6128: #BD88 ; MITTEL: Maschinencode über HIMEM: CALLCALL ADD ; Addiere FLO(HL) := FLO(HL) + FLO(DE) RET NC ; Überlauf LD DE,ZWEI ; FLO(DE) := 2 Maschinencode über HIMEM: CALLCALL DIV ; Dividiere FLO(HL) := FLO(HL) / FLO(DE) RET ; ZWEI: DEFB 0 ; '2' in der internen Fließkomma-Darstellung DEFB 0 DEFB 0 DEFB 0 DEFB 130 Eine Fließkomma-Zahl aus dem Kopf in die fünf Datentypen: Bytes 10 LET Operationen: BD5B / 349A / 349A: FLO SUBa=2 20 FOR i=@Operationen: BD5B / 349A / 349A: FLO SUBa TO @Operationen: BD5B / 349A / 349A: FLO SUBa+4 30 PRINT PEEK(i); 40 NEXT In Zeile 10 wird eine Unterprogramme: VariablenVariable mit dem gewünschten Wert definiert, worauf der Basic-Interpreter diese Unterprogramme: VariablenVariable im Speicher einrichtet und die Zahl in der internen Kodierung abspeichert. Hier können die fünf Datentypen: Bytes Rekursion - Selbst-Aufruf einer ProzedurEin bei allen Mathematikern beliebtes Verfahren stellt die Rekursion dar. Dabei löst eine Die Fließkomma-Routinen: FunktionenFunktion ein Problem dadurch, dass es das Problem verkleinert und sich wieder selbst aufruft, um von sich selbst das verkleinerte Problem lösen zu lassen. Fast so wie Muenchhausen, der sich am eigenen Zopf aus dem Sumpf zieht. Wichtig ist, dass bei jedem rekursiven Aufruf auch wirklich das Problem verkleinert wird, und dass es zu einem bestimmten Zeitpunkt direkt loesbar wird. Sonst gehen die Aufrufe solange weiter, bis der Return-Stack voll ist (und, wenn hier keine Überlauf-Kontrolle stattfindet, so lange, bis sich das Programm endgültig aufgehängt hat). Das Abbruch-Kriterium, mit dem entschieden wird, wann das Problem direkt gelöst wird, ist also der eine entscheidende Trick. Der andere ist, dass man sich mit jedem Selbst-Aufruf auch wirklich auf das Abbruch-Kriterium hin bewegt. Sonst 'löst' man das Problem doch wie Muenchhausen. An die Programmiersprache werden dabei zwei Anforderungen gestellt: Zum einen muss der Selbst-Aufruf einer Prozedur o. AE. überhaupt möglich sein (in Übergabe von Argumenten und Ergebnissen: FORTH:FORTH gibt es da leichte Schwierigkeiten) und zum anderen müssen Variablen: lokale Variablenlokale Variablen definierbar sein. Letzteres ist der Grund dafür, dass die Rekursion in Einleitung: BASIC Füll-AlgorithmusBestechend ist der folgende Basic-Einzeiler, der einen 100% funktionsfähigen Rekursion - Selbst-Aufruf einer Prozedur: Füll-AlgorithmusFüll-Algorithmus für beliebig umrandete Flächen darstellt (hier für Bildschirm-Modus 1, bei dem der Pixel-Abstand auf dem Monitor in Die verwendeten Abkürzungen bedeuten: x:X- und Y-Richtung je zwei Längeneinheiten beträgt): 10 IF Erklärung der Anschlussbelegung: TestTEST(Die verwendeten Abkürzungen bedeuten: x:x,y)>0 THEN RETURN ELSE PLOT Die verwendeten Abkürzungen bedeuten: x:x,y: Die verwendeten Abkürzungen bedeuten: x:x=x-2: GOSUB 10: Die verwendeten Abkürzungen bedeuten: x:x=Die verwendeten Abkürzungen bedeuten: x:x+4: GOSUB 10: Die verwendeten Abkürzungen bedeuten: x:x=x-2: y=y-2: GOSUB 10: y=y+4: GOSUB 10: y=y-2: RETURN Auch wenn sie hier zur übersichtlicheren Darstellung etwas auseinandergefleddert ist, handelt es sich nur um eine einzige Programmzeile. Diese Routine kommt ohne Variablen: lokale Variablenlokale Variablen aus, weil die Argumente des Aufrufs (die Die verwendeten Abkürzungen bedeuten: x:X- und Y-Koordinaten) nicht verändert werden: x-2+4-2 = Die verwendeten Abkürzungen bedeuten: x:x und y-2+4-2=y. Abbruch-Kriterium ist der Erklärung der Anschlussbelegung: TestTest, ob der Punkt (Die verwendeten Abkürzungen bedeuten: x:x,y) gesetzt ist oder nicht. Ist der Punkt gesetzt, also Erklärung der Anschlussbelegung: TestTEST(Die verwendeten Abkürzungen bedeuten: x:x,y) > 0, so ist die Umrandung erreicht. Der Punkt wird nicht noch einmal neu gesetzt, die Routine kehrt sofort zurück. Ist das Pixel aber noch nicht gesetzt, so wird auf diese Stelle geplottet und dann nacheinander die vier benachbarten Punkte ebenfalls getestet, wozu vier rekursive Aufrufe nötig sind. Dem praktischen Einsatz dieser Füll-Funktion steht leider im Weg, dass sie sich, je nach Größe der auszumalenden Fläche, mehrere tausend mal selbst aufrufen kann. Da macht leider der Return-Stack des Basic-Interpreters nicht mehr mit. FakultaetEin weiteres, beliebtes Beispiel ist die rekursive Berechnung der Rekursion - Selbst-Aufruf einer Prozedur: FakultaetFakultaet. Die Rekursion - Selbst-Aufruf einer Prozedur: FakultaetFakultaet n! einer ganzen, positiven Zahl n ist (hoffentlich bekanntermassen) das Produkt aus allen ganzen Zahlen ab 1 bis zu dieser Zahl n selbst: n! = 1*2*3*4* ... (n-2)*(n-1)*n Die Rekursion - Selbst-Aufruf einer Prozedur: FakultaetFakultaet von 0 ist per Definition 1. In Einleitung: BASIC Rekursion - Selbst-Aufruf einer Prozedur: FakultaetFakultaet mittels Iteration und mittels Rekursion
10 INPUT "eine Zahl:",n 10 INPUT "eine Zahl:",n 20 LET fak=2 20 GOSUB 40:PRINT fak:GOTO 10 30 FOR n=1 TO n:fak=fak*n:NEXT n 30 ' 40 PRINT fak 40 IF n<2 THEN fak=1:RETURN 50 GOTO 10 50 n=n-1:GOSUB 40:n=n+1:fak=fak*n:RETURN Die Rekursion basiert auf dem Gedanken, dass die Rekursion - Selbst-Aufruf einer Prozedur: FakultaetFakultaet einer Zahl n sich auf die Rekursion - Selbst-Aufruf einer Prozedur: FakultaetFakultaet der nächst-kleineren Zahl n-1 zurückführen lässt: n! = (n-1)! * n z.LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
Tatsächlich lässt sich JEDE Rekursion auf eine Programmstrukturen: Iterationen - SchleifenIteration (Schleife) zurückführen. In aller Regel gilt dabei: Die Iteration ist schneller und verbraucht weniger Speicherplatz. Der Vorteil der Rekursion ist aber, dass man hier mechanisch beweisen kann, dass die Rekursion terminiert (zu einem Ende kommt), und dass manches Problem hier schlicht viel einfacher zu lösen ist. Das Abbruchkriterium ist der Erklärung der Anschlussbelegung: TestTest in Zeile 40, ob 'n' kleiner als '2' wird. Dann ist die Rekursion - Selbst-Aufruf einer Prozedur: FakultaetFakultaet 1 weil: 0! = 1 und 1! = 1 Ist das Abbruch-Kriterium noch nicht erreicht, so wird in Zeile 50 die Rekursion - Selbst-Aufruf einer Prozedur: FakultaetFakultaet rekursiv bestimmt: 'n' wird um 1 erniedrigt, und davon die Rekursion - Selbst-Aufruf einer Prozedur: FakultaetFakultaet errechnet. Das mit 'n' multipliziert, ergibt, wie oben erklärt, 'n!'. Dabei darf 'n' selbst nicht verändert werden, weil 'n' in Einleitung: BASIC Das folgende PASCAL-Programm berechnet die Rekursion - Selbst-Aufruf einer Prozedur: FakultaetFakultaet mit Variablen: lokale Variablenlokalen Variablen: 1 function fak (n:integer):integer; 2 begin 3 if n<2 then fak := 1 4 else fak := fak(n-1)*n 5 end; In der ersten Zeile wird u.Operationen: BD5B / 349A / 349A: FLO SUBA. die lokale Übernahme-Variable 'n' definiert. Zeile 3 ist wieder der Erklärung der Anschlussbelegung: TestTest auf das Abbruch-Kriterium. Ist es noch nicht erreicht, so wird in Zeile 4 die Rekursion - Selbst-Aufruf einer Prozedur: FakultaetFakultaet rekursiv ermittelt. Dabei wird das Argument 'n-1' zur neuen, lokalen Übernahme-Variable 'n' der aufgerufenen Die Fließkomma-Routinen: FunktionenFunktion 'fak'. Wie man sieht, konnte wegen der nur lokalen Gueltigkeit der Übernahme-Variablen 'n' die zugrunde liegende Formel viel direkter übernommen werden: fak(n) := fak(n-1)*n Ebenfalls einsichtiger wird der Füll-Einzeiler, wenn man ihn in Übergabe von Argumenten und Ergebnissen: PASCAL:PASCAL schreibt: 1 procedure füll (Die verwendeten Abkürzungen bedeuten: x:x,y:integer); 2 begin 3 if Erklärung der Anschlussbelegung: Testtest(Die verwendeten Abkürzungen bedeuten: x:x,y)=0 then begin 4 plot(Die verwendeten Abkürzungen bedeuten: x:x,y); 5 füll(x-2,y); füll(Die verwendeten Abkürzungen bedeuten: x:x,y-2); 6 füll(Die verwendeten Abkürzungen bedeuten: x:x+2,y); füll(Die verwendeten Abkürzungen bedeuten: x:x,y+2) 7 end 8 end; Interrupts - UnterbrechungenMit dem programmierbaren Interrupt-Mechanismus, der beim Schneider CPC in geradezu einzigartiger Weise bis zur Hochsprache-Ebene durchgeführt ist, kann man sehr elegant mehrere Aufgaben quasi gleichzeitig vom Computer erledigen lassen. Die Fließkomma-Routinen: FunktionenFunktionen, die normalerweise mit längeren Wartezeiten verbunden sind, werden nur noch bei Bedarf aufgerufen. In der Zwischenzeit kann das Hauptprogramm ablaufen, ohne dass Rechenzeit in Warteschleifen verbraten wird. Sobald ein Interrupt-Signal auftritt, wird die zugehörige Routine wie ein normales Grundlagen: UnterprogrammeUnterprogramm aufgerufen. Das heißt, die aktuelle Position des Befehlszeigers wird auf den Return-Stapel gerettet und der Befehlszeiger mit der Adresse der Interrupt-Routine geladen. Dadurch ist der Sprung zu dieser Routine realisiert, und diese kann nachher ganz normal mit RETURN abschließen. Da Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupts jederzeit unvorhersehbar dem laufenden Hauptprogramm dazwischen funken können, dürfen sie sie keine Die Tonausgabe: Das Kontrollregister (Reg. 7) Außerdem wird bei jedem Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupt ein Die Z80: Wirkung der Z80-Befehle auf die FlagsFlag gesetzt, das weitere Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupts verhindert (EI und DI in Assembler). Der Software-Interrupt-Mechanismus des MAIN FIRMWARE JUMPBLOCK: KERNEL Sollen mit Hilfe von Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupts mehrere Aufgaben (Tasks) quasi gleichzeitig abgearbeitet werden, so muss man sein Programm wie folgt gliedern: Zunächst gibt es ein Haupt-Programm, das scheinbar ohne Unterbrechung abgearbeitet wird. Es unterscheidet sich in keiner Weise von einem Interrupt-freien Programm. Aufgaben, die mit längeren Wartezeiten verbunden sind, können parallel dazu als Interrupt-Routinen ablaufen. Dabei muss man die Aufgabe so zelegen, dass das Grundlagen: UnterprogrammeUnterprogramm mit Beginn der Wartezeit zum Hauptprogramm zurückkehrt. Das Kriterium, das das Ende der Wartezeit anzeigt, muss benutzt werden, um einen Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupt zu programmieren, der das Grundlagen: UnterprogrammeUnterprogramm erneut aufruft. Hintergrund-UhrEin Beispiel für einen sinnvollen Einsatz des 'EVERY'-Interruptbefehls in Einleitung: BASIC 10 INPUT "Uhrzeit [hh,mm,ss] = ",std,minu,sek 20 ' 30 EVERY 50,1 GOSUB 1000 : REM starten der Uhr 40 ' 50 GOTO 50 : REM Hauptprogramm 1000 sek=sek+1 1010 IF sek=60 THEN sek=0:minu=minu+1 1020 IF minu=60 THEN minu=0:std=std+1 1030 IF std=24 THEN std=0 1050 LOCATE#7,1,1 : PRINT#7,USING"##&##&##";std;":";minu;":";sek; 1070 RETURN In Zeile 30 wird ein Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupt programmiert, der die Priorität 1 hat, alle 50/50 Sekunden auftreten soll und zum Aufruf des Uhrenprogramms ab Zeile 1000 führt. Hier werden dann die Sekunden und eventuell auch Minuten und Stunden weitergestellt. Damit durch den Ausdruck der Zeit nicht die Textausgabe des Hauptprogramms gestoert wird (und auch keine weiteren, möglicherweise installierten Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupts die Textausgabe der Uhr stören), benutzt die Uhr ein eigenes Textfenster, das von keinem weiteren Programm benutzt werden darf. blinkender CursorInteressant ist auch ein Interrupts - Unterbrechungen: blinkender Cursorblinkender Cursor in einer Textverarbeitung o. AE.. Dabei wird Die Text-VDU: Der Cursorder Cursor eingeschaltet, eine Weile gewartet, Die Text-VDU: Der Cursorder Cursor ausgeschaltet und wieder gewartet, bevor der Zyklus von neuem beginnt: 90 REM Hauptprogramm: Textverarbeitung 'einfachst' 91 REM ------------------------------------------- 100 GOSUB 500 110 i$=INKEY$:IF i$="" THEN GOTO 110 120 PRINT i$; 130 GOTO 100 490 REM Cursor-Blinken: 500 Maschinencode über HIMEM: CALLCALL &TEXT VDU: BB81: TXT CUR ONBB81 : REM Erklärung zu den verwendeten Bezeichnungen: CursorCursor On 510 AFTER 30 GOSUB 550 520 RETURN 550 Maschinencode über HIMEM: CALLCALL &TEXT VDU: BB84: TXT CUR OFFBB84 : REM Erklärung zu den verwendeten Bezeichnungen: CursorCursor Off 560 AFTER 15 GOSUB 500 570 RETURN Die beiden Routinen ab Adresse 500 und 550 rufen sich in stetem Wechsel gegenseitig auf und schalten dabei den Cursor-Fleck ein und wieder aus. Das Hauptprogramm (Zeilen 100 bis 130) merkt davon nichts. Dabei ist es übrigens eine gute Idee, nach jeder Tasteneingabe Die Text-VDU: Der Cursorden Cursor zwangsweise einzuschalten, damit er sichtbar wird, wenn sich die Cursor-Position verschiebt. Das wird in diesem Beispiel dadurch erreicht, dass in Zeile 130 der Sprung nicht nur bis 110 zurück geht, sondern bis Zeile 100, wo der Aufruf der Cursor-Einschalt-Routine steht. Während einem INPUT oder LINE INPUT werden die Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupts leider nicht befolgt, da ihre Ausführung vom Pollen des Basic-Interpreters abhängig ist. Zur genaueren Erklärung des Interrupt-Mechanismus im Schneider CPC folgen bei der Betrachtung der Die Firmware des Schneider CPCFirmware noch zwei eigenständige Kapitel. |