Teil 8 „Computer programming“

Der C-Kurs
Antworten
Benutzeravatar
bodo
User
Beiträge: 319
Registriert: 14.02.2007, 17:21
Kontaktdaten:

Teil 8 „Computer programming“

Beitrag von bodo » 30.10.2010, 14:43

C für BASIC-Programmierer

Arbeiten mit dem z88dk Cross-Compiler

von Jens Sommerfeld und Bodo Wenzel

Teil 8

Das Kapitel 8 von Vickers' BASIC-Buch heißt „Computer programming“ und er zeigt uns, wie Programme eingegeben und editiert werden. Das haben wir ja längst hinter uns, weil ihr das mit C seit dem Teil 1 unseres Kurses macht. Einen Editor und den Compileraufruf beherrscht ihr also schon.

Daher werden wir uns in diesem Teil den sichtbaren Details der Übersetzung widmen; wen das nicht interessiert, der kann problemlos den nächsten Teil in Angriff nehmen.

Übersicht

Ein C-Programm durchläuft beim Erzeugen meistens folgende Stufen (Manche C-Systeme fassen einzelne Stufen zusammen. Das schränkt die allgemeine Gültigkeit der folgenden Aussagen aber nicht ein.):
  1. Der Quelltext wird in einem Editor eingetippt und in einer Datei gespeichert. Diese Datei nennt man Quelltext, und sie hat die Erweiterung „.c“ (mit kleinem C!).
  2. Der Präprozessor filtert den Quelltext und bereinigt ihn um die Kommentare. Dabei werden die Präprozessoranweisungen ausgeführt. Wenn das Ergebnis in einer Datei gespeichert wird, erhält sie üblicherweise die Erweiterung „.i“. Das ist sicher die Abkürzung von intermediate file.
  3. Der eigentliche Compiler übersetzt den C-Text in Assembler-Text. Auch dieser Text kann in einer Datei landen, die dann üblicherweise die Erweiterung „.s“ (mit kleinem S!) bekommt. Das ist ein source file.
  4. Jetzt wird der Assembler-Text durch den Assembler in Maschinencode übersetzt. Dieser wird in einer sogenannten Objektdatei abgelegt, die deshalb die Erweiterung „.o“ besitzt.
  5. Der Maschinencode in der Objektdatei ist noch nicht lauffähig, weil er nur die Anweisungen des C-Quelltextes realisiert. Für das vollständige Programm müssen noch die Funktionen aus den Standardbibliotheken hinzugefügt werden. Bei den meisten Zielsystemen wie unserem Zeddy müssen auch die Adressen (Sprung- und Variablenadressen) berechnet und eingesetzt werden. Diese Arbeit heißt „Binden“ und wird vom Linker durchgeführt. Damit ist das Programm prinzipiell fertig; beim z88dk erhält die entstandene ausführbare Datei (das Executable) die Erweiterung „.bin“.
  6. Als Spezialität beim z88dk muss noch ein minimales BASIC-Programm um das fertige Maschinenprogramm herumgebastelt werden. Erst dann haben wir unser Ziel erreicht: eine P-Datei!
Wie gut, dass uns die ganze Aufruferei vom sogenannten Frontend „zcc“ abgenommen wird. Daher beschränkt sich das Aufrufen für uns auf eine Kommandozeile. Wir können uns aber jeden der Schritte auch einzeln ansehen...

Editor

Diesen Schritt will ich nicht groß behandeln. Ihr benutzt hoffentlich einen gut handhabbaren Texteditor, der mehrere Dateien gleichzeitig offen halten kann. Besonders hilfreich ist es auch, wenn er die C-Syntax erkennt und farbig (aber nicht zu bunt) darstellt. Und wenn er dann noch mit einem Mausklick Befehlszeilen wie den Compileraufruf ausführen und deren Ausgabe darstellen kann, seid ihr schon fast am Optimum.

Als Beispiel dieses Teils wird folgendes Programm dienen, als Quelltext in der Datei „CfBASIC_8-1.c“ gespeichert:

Code: Alles auswählen

/* Wir benutzen Funktionen der Standard-Ein-und-Ausgabe: */
#include <stdio.h>

/* Die Konstante EXIT_SUCCESS ist hier definiert: */
#include <stdlib.h>

/* Dieses Programm gibt einfach nur
 * Hallo, Welt!
 * aus:
 */
int main(void) {
    puts("Hallo, Welt!");

    return EXIT_SUCCESS;
}

// Das war's!
Präprozessor

Der Präprozessor hat zwei wichtige Aufgaben: er entfernt alle Kommentare und führt alle seine eigenen Befehle aus. Im nächsten Teil kommen wir darauf zurück. In aller Kürze:

Kommentare beginnen mit der Zeichenkombination „/*“ und enden, eventuell erst in einer späteren Zeile, mit der Zeichenkombination „*/“.

Die Befehle des Präprozessors beginnen mit dem Zeichen '#' als erstem Zeichen der Zeile, auch nach Tabulatoren und Leerzeichen am Anfang. Das nächste Wort ist die eigentliche Anweisung; ihr kennt schon include. Bestimmte Anweisungen führen zu Ersetzungen im Quelltext – aber das sehen wir ja im nächsten Teil genau.

Mit der folgenden Kommandozeile wird die Übersetzung nach dem Präprozessor beendet; zcc ist auch so freundlich, gleich den Dateinamen für die Zwischendatei wie den Quelltext, aber mit der Erweiterung „.i“, zu bilden.

Code: Alles auswählen

zcc +zx81 -vn -Wall -E CfBASIC_8-1.c
Wenn ihr jetzt in die Datei „CfBASIC_8-1.i“ schaut, findet ihr immer noch gültigen C-Quelltext, aber sie ist viel größer als die „.c“-Datei. Der Präprozessor hat zwar alle Kommentare entfernt, aber er hat auch die include-Anweisungen ausgeführt und die Inhalte der angegebenen Headerdateien eingefügt. „Unser“ Quelltext ist am Ende zu sehen.

Interessant und in manchen Fällen zur Fehlersuche wichtig sind zwei Dinge:
  1. Die Zeilen, die mit '#' beginnen, enthalten als nächstes immer eine Zahl und manchmal einen Dateinamen in doppelten Anführungszeichen. Damit wird festgehalten, aus welcher Datei mit welcher Zeilennummer die folgenden Zeilen stammen, so dass der Compiler bei Bedarf nützliche Fehlermeldungen erzeugen kann.
  2. Der Präprozessor hat bestimmte Texte ersetzt. In unserem Beispiel ist dies die Konstante EXIT_SUCCESS, die durch die Zahl 0 ersetzt wurde.
Compiler

Wenn wir den erzeugten Assembler-Quelltext sehen wollen, lassen wir die Übersetzung mit folgender Kommandozeile laufen:

Code: Alles auswählen

zcc +zx81 -vn -Wall -a CfBASIC_8-1.c
Weil der erzeugte Assembler-Text optimiert ist, haben die z88dk-Macher entschieden, dass die Datei die Erweiterung „.opt“ haben soll. Schaut euch also die Datei „CfBASIC_8-1.opt“ an; der interessante Teil ist ziemlich am Anfang: 6 (in Worten: sechs) Zeilen Assembler...

Assembler

Wenn wir in Teil 25 zum modularen Programmieren kommen, werden wir die folgende Kommandozeile wiederfinden. Sie beendet die Übersetzung nach der Erzeugung des Maschinencodes des C-Quelltextes:

Code: Alles auswählen

zcc +zx81 -vn -Wall -c CfBASIC_8-1.c
Dabei entsteht die Datei „CfBASIC_8-1.o“. Sie enthält jetzt binäre Daten, die ihr euch mit einem entsprechenden Programm (z.B. einem Hexviewer) ansehen könnt. Genaues Hinschauen deckt auf, dass sowohl einzelne Namen wie „MAIN“ und „PUTS“ als auch der Maschinencode und unser Text darin stehen.

Linker

Jetzt soll ein lauffähiges Programm erzeugt werden. Dazu lassen wir einfach die Option „-create-app“ weg, allerdings ist es sinnvoll, einen Dateinamen für die Zieldatei anzugeben:

Code: Alles auswählen

zcc +zx81 -vn -Wall CfBASIC_8-1.c -o CfBASIC_8-1.bin
Die entstandene Datei „CfBASIC_8-1.bin“ enthält jetzt den „reinen“ Maschinencode, einschließlich der nötigen Unterprogramme für puts(). Auch diese Datei könnt ihr euch z.B. mit einem Hexviewer ansehen: die Namen der Funktionen sind nicht mehr enthalten, weil der Prozessor sie ja zur Ausführung nicht braucht. Aber wir finden unseren auszugebenden Text wieder...

P-Datei-Umsetzer

Diese letzte Stufe ist nur für den ZX81 relevant. Zwar ist die ausführbare Datei „CfBASIC_8-1.bin“ schon das fertige Maschinenprogramm, aber für eine bequeme Handhabung brauchen wir noch einen BASIC-„Rahmen“, der u.a. den binären Code in eine REM-Zeile packt. Diese Arbeit macht das Hilfsprogramm appmake, das durch zcc aufgerufen wird, wenn wir die Option „-create-app“ angeben. Und damit sind wir wieder bei der altbekannten Kommandozeile:

Code: Alles auswählen

zcc +zx81 -vn -Wall -create-app CfBASIC_8-1.c -o CfBASIC_8-1.bin
Die so entstehende P-Datei enthält nur zwei BASIC-Zeilen. Zeile 1 enthält das REM mit dem Maschinencode, Zeile 2 das RAND USR für den Aufruf. Wenn ihr ein Gesamtwerk aus C und BASIC schreiben wollt, müsst ihr also erst komplett den C-Teil schreiben, die P-Datei in einen ZX81 oder Emulator laden und dann dort den BASIC-Teil schreiben.

Aufrufe durch zcc

Wenn ihr die Option „-vn“ beim Aufruf von zcc weglasst, seht ihr alle Kommandozeilen, die zcc ausführt. Hier ist eine kurze Übersicht, teilweise sind die Zeilen umgebrochen, weil unser Heft zu schmal ist. ;-) Die markierten Worte euer_pfad, temporaer1 und temporaer2 werden sich je nach Installation und Aufruf unterscheiden. Die tatsächlichen Zeilenenden sind mit „↵“ markiert.

Ein bisschen Programm ist notwendig, um eure main-Funktion zu starten. Solch ein Stückchen heißt gerne „crt0“, wahrscheinlich von „C Runtime part 0“. Die ersten Kommandozeilen erzeugen eine temporäre Kopie:

Code: Alles auswählen

cp euer_pfad/z88dk/lib/zx81_crt0.opt temporaer1.opt ↵
cp temporaer1.opt temporaer1.asm ↵
Jetzt wird der Präprozessor aufgerufen:

Code: Alles auswählen

zcpp -I. -DZ80 -DSMALL_C -DZX81 -D__ZX81__ -DSCCZ80 -Ieuer_pfad/z88dk/include CfBASIC_8-1.c temporaer2.i ↵
Jetzt kommt der Compiler an die Reihe:

Code: Alles auswählen

sccz80 -// -Wall temporaer2.i ↵
Dessen Assembler-Quelltext ist noch nicht so toll, daher wird er in zwei Stufen optimiert:

Code: Alles auswählen

copt euer_pfad/z88dk/lib/z80rules.2 < temporaer2.asm > temporaer2.op1 ↵
copt euer_pfad/z88dk/lib/z80rules.1 < temporaer2.op1 > temporaer2.opt ↵
Das Ergebnis kann in Maschinencode übersetzt werden:

Code: Alles auswählen

z80asm -IXIY -eopt -ns -Mo temporaer2.opt ↵
Interessanterweise fungiert der Assembler auch als Linker:

Code: Alles auswählen

z80asm -a -m -Mo -oCfBASIC_8-1.bin -ieuer_pfad/z88dk/lib/clibs/ndos -ieuer_pfad/z88dk/lib/clibs/zx81_clib -ieuer_pfad/z88dk/lib/clibs/z80iy_crt0 -IXIY temporaer1.opt temporaer2.o ↵
Schließlich wird der Umsetzer für die P-Datei aufgerufen:

Code: Alles auswählen

appmake +zx81 -b CfBASIC_8-1.bin -c temporaer1 ↵
Das war's! Ihr habt es überstanden...
B0D0: Real programmers do it in hex.

Antworten