Das Schneider CPC Systembuch

Grundlagen

Unterprogramme

Das 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
Anhang: Basic
Basic
wird für den Aufruf der Befehl 'GOSUB' und für den Rücksprung 'RETURN' benutzt. Der Stapel ist dabei (außer mit PEEK und POKE) nicht zugänglich.

In Assembler werden die Mnenonics 'Maschinencode über HIMEM: CALLCALL' und 'RET' gebraucht. Der Maschinenstapel der Die ICs im Überblick: Die CPU Z80
Das Innenleben der CPC-Rechner: Die CPU Z80
Die Anschlussbelegungen der wichtigsten ICs im CPC: Die CPU Z80
CPU
wird dabei für die Rücksprungsadressen und zum Zwischenspeichern von Registern mit 'PUSH' und 'POP' benutzt. Die Return-Adressen sind deshalb sehr leicht zugänglich, weil das Grundlagen: UnterprogrammeUnterprogramm jeden Stackeintrag in ein Doppelregister 'poppen' kann. Manipulationen sind hier Tuer und Tor geöffnet.

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
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
B
=5 ---> Reihenfolge der Bearbeitung: 110 GOSUB 200 120 Operationen: BD5B / 349A / 349A: FLO SUBA=8:LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
B
=9 [100] [110] [200] [210] 130 GOSUB 200 [120] [130] [200] [210] 140 STOP [140] 200 PRINT Operationen: BD5B / 349A / 349A: FLO SUBA;"*";LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
B
;"=";Operationen: BD5B / 349A / 349A: FLO SUBA*LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
B
210 RETURN

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
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
B
=5 | | | | GOSUB 200 ----+ | PRINT Operationen: BD5B / 349A / 349A: FLO SUBA;"*";LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
B
;"=";Operationen: BD5B / 349A / 349A: FLO SUBA*LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
B
Operationen: BD5B / 349A / 349A: FLO SUBA=8:LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
B
=9 <---------|----+ RETURN GOSUB 200 ----------+ | | | STOP <---+ +-----+ | +-------------------+

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
Anhang: Basic
Basic
kann sehr komplexe Aktionen hervorrufen. Mit dem 'wie' muss sich der Programmierer aber nicht abgeben. Ausschließlich die Frage 'was' passiert, ist interessant, und was man als Eingaben (sprich: Argumente) übergeben kann.

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
Das Innenleben der CPC-Rechner: Die CPU Z80
Die Anschlussbelegungen der wichtigsten ICs im CPC: Die CPU Z80
CPU
die einzelnen Befehle realisiert werden, ist uninteressant. Wichtig ist nur, was sie bewirken.

Variablen

Neben 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
Anhang: Basic
Basic
aber auch keinen besonders hohen Komfort zur Verfügung. Einleitung: BASIC
Anhang: Basic
Basic
kennt nämlich nur Variablen: globale Variablenglobale Variablen.

globale Variablen

Auf 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 Unterprogramme aufruft. Andernfalls erhielte man eher so eine Art 'Killermodule'. In Einleitung: BASIC
Anhang: Basic
Basic
muss der Programmierer des Hauptprogrammes immer in gewissem Umfang über das Innenleben der benutzten Grundlagen: UnterprogrammeUnterprogramme bescheid wissen. Die dort vorkommenden Unterprogramme: VariablenVariablen dürfen sich nicht mit den von ihm benutzten überschneiden! So gesehen ist Einleitung: BASIC
Anhang: Basic
Basic
also eine ziemlich unstrukturierte Angelegenheit.

lokale Variablen

Dieses 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
Anhang: Basic
Basic
lassen sich Variablen: lokale Variablenlokale Variablen simulieren, wenn man sich einen Stapel bastelt. Dazu benötigt man ein Zahlenfeld und einen Zeiger:

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)
Die Tonausgabe: Die möglichen Hüllkurvenformen (Reg. 13)
Register
abgelegt werden können. Die obige Methode ist in Assembler sehr leicht zu verwirklichen:

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)
Die Tonausgabe: Die möglichen Hüllkurvenformen (Reg. 13)
Register
(AF, BC, DE und HL), bevor es selbst wieder eine andere Routine aufruft, die das Zeichen ausdruckt. Deshalb muss auch der Zeiger HL nicht vom rufenden (Unter-) Programm MELD1 zwischengespeichert werden.

Für das Modul MELDNG gilt also folgende Schnittstellen-Beschreibung:
werden befolgt (weil dies auch TEXT VDU: BB5A: TXT OUTPUTTXT OUTPUT tut).
Der Text muss mit einem Nullbyte abgeschlossen sein.

Übergabe von Argumenten und Ergebnissen

In Einleitung: BASIC
Anhang: Basic
Basic
ist es gar nicht vorgesehen, Argumente an Grundlagen: UnterprogrammeUnterprogramme zu übergeben. Man kann sich aber leicht behelfen, indem man dafür zwischen rufendem und gerufenem Programm eine Unterprogramme: VariablenVariable vereinbart. Diese Methode, mit den Unterprogramme: VariablenVariablen 'eingabe' und 'ausgabe' wurde in dem letzten Basic-Beispiel mit den 'Variablen: lokale Variablenlokalen' Variablen verwendet.

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
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
b
) = (Operationen: BD5B / 349A / 349A: FLO SUBa+LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
b
)/2 20 ' 30 FOR i=1 TO 90 40 PRINT FNmittel(SIN(i),COS(i)) 50 NEXT i 60 END

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
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
b
' eingesetzt werden. Die Unterprogramme: VariablenVariablen 'Operationen: BD5B / 349A / 349A: FLO SUBa' und 'LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
b
' sind dabei Variablen: lokale Variablenlokale Variablen.

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
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
b
<-- Definition der Prozedur und von Operationen: BD5B / 349A / 349A: FLO SUBa und LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
b
als lokale Übernahmevariablen für zwei Basic und Maschinencode: ParameterParameter. op (:Operationen: BD5B / 349A / 349A: FLO SUBa + :LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
b
)/2 <-- Ausgabe von (Operationen: BD5B / 349A / 349A: FLO SUBa+LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
b
)/2 als Ergebnis der Prozedur. end

Aufruf:

    mittelwert 100 200 [ENTER]
    150
PASCAL:
    function mittelwert (Operationen: BD5B / 349A / 349A:  FLO SUBa,LOW KERNEL JUMPBLOCK: 000B:  LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
b
:Datentypen: Realreal):Datentypen: Realreal; <-- Definition der Die Fließkomma-Routinen: FunktionenFunktion und von Operationen: BD5B / 349A / 349A: FLO SUBa und LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
b
als lokale Übernahmevariablen für begin zwei Basic und Maschinencode: ParameterParameter. mittelwert := (Operationen: BD5B / 349A / 349A: FLO SUBa+LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
b
)/2 <-- Ausgabe von (Operationen: BD5B / 349A / 349A: FLO SUBa+LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
b
)/2 als Ergebnis der end; Die Fließkomma-Routinen: FunktionenFunktion.

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
Übergabe von Argumenten und Ergebnissen: LOGO:
LOGO
und Übergabe von Argumenten und Ergebnissen: PASCAL:PASCAL bedarf es eines speziellen Syntax', um eine Die Fließkomma-Routinen: FunktionenFunktion als Parameter-nehmend und Ergebnis-liefernd zu deklarieren. Dafür kontrolliert der Interpreter (Einleitung: LOGO
Übergabe von Argumenten und Ergebnissen: LOGO:
LOGO
) bzw. Compiler (Übergabe von Argumenten und Ergebnissen: PASCAL:PASCAL) bei jedem Aufruf der Die Fließkomma-Routinen: FunktionenFunktion, dass diese Parameter-Schnittstelle auch eingehalten wird.

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 Tonausgabe: Die möglichen Hüllkurvenformen (Reg. 13)
Register
nur als Zeiger auf die Argumente verwendet. Im Prinzip ist man aber ungebunden, was die Methode der Parameter: Parameter-ÜbergabeParameter-Übergabe betrifft. Man sollte sich nur um ein möglichst einheitliches Verfahren bei allen Unterprogramm-Modulen bemühen, damit man nicht hinterher tagelang in einem Programm nach einer fehlerhaften Parameter: Parameter-ÜbergabeParameter-Übergabe suchen muss.

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
Datenbreite: Bytes
Bytes
für ihre interne Kodierung zu zerlegen, ist wohl nicht Jedermanns Sache. Man kann sich hier aber mit einem einfachen Trick behelfen:

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
Datenbreite: Bytes
Bytes
dann einfach mit Hilfe des Variablenpointers '@' aus dem Speicher ausgelesen werden.

Rekursion - Selbst-Aufruf einer Prozedur

Ein 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
Anhang: Basic
Basic
so selten angewendet wird. Einleitung: BASIC
Anhang: Basic
Basic
kennt ja nur Variablen: globale Variablenglobale Variablen. Trotzdem sind mit einigem Geschick auch hier Rekursionen möglich. Zur Not muss man sich einen privaten Stapel in einem Datenspeicherung und Datenstrukturen: ArraysArray konstruieren, auf den man die 'Variablen: lokale Variablenlokalen' Variablen rettet.

Füll-Algorithmus

Bestechend 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.

Fakultaet

Ein 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
Anhang: Basic
Basic
wird man das Problem sehr wahrscheinlich mit einer Schleife angehen:

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
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
B
.: 4! = 1*2*3*4 = (1*2*3) * 4 = 3! * 4

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
Anhang: Basic
Basic
ja nicht lokal definiert werden konnte.

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 - Unterbrechungen

Mit 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)
Die Tonausgabe: Die möglichen Hüllkurvenformen (Reg. 13)
Register
(Hardware-Interrupt in Maschinencode-Programmen) bzw. keine Unterprogramme: VariablenVariablen des Hauptprogramms (z.LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
B
. in Einleitung: BASIC
Anhang: Basic
Basic
) verändern.

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
Die Firmware des Schneider CPC: KERNEL
Kernel
bietet darüber hinaus noch die Möglichkeit, einzelne Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupts mit Prioritäten zu versehen, so dass 'dringendere' Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupts einem 'unwichtigeren' Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupt immer noch dazwischen funken können.

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-Uhr

Ein Beispiel für einen sinnvollen Einsatz des 'EVERY'-Interruptbefehls in Einleitung: BASIC
Anhang: Basic
Basic
ist eine mitlaufende Uhr. Einmal pro Sekunde soll die Uhrzeit ausgegeben werden. Danach tritt eine Wartezeit auf, die so lange dauert, bis die Zeit erneut ausgegeben werden muss, also bis zum Eintritt der nächsten Sekunde:

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 Cursor

Interessant 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.

Valid HTML   Valid CSS