Teil 6 „Variables“

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

Teil 6 „Variables“

Beitrag von bodo » 07.12.2008, 20:50

C für BASIC-Programmierer

Arbeiten mit dem z88dk Cross-Compiler

von Jens Sommerfeld und Bodo Wenzel

Teil 6

Dieser Teil ist den Variablen gewidmet, denn bei Vickers heißt es auch „Variables“.

Variablen anlegen

Zunächst hat eine Variable in C einen Typ und einen Namen. Wir kommen später noch darauf, aber bereits jetzt machen wir euch den Mund wässrig mit dem Konzept, dass es nicht nur die sogenannten „primitiven“ Datentypen wie Ganzzahlen gibt. Ihr könnt sogar die gesamte Datenbasis eines Programms als Datentyp vereinbaren. Aber für den Moment bleiben wir bei den bekannten Datentypen...

Für die Namen von Variablen gilt dasselbe wie für die Namen von Funktionen; generell gelten diese Regeln für alle von euch deklarierten Objekte. Wir werden später noch mehr Objektarten außer Variablen und Funktionen kennenlernen.
  • Groß- und Kleinbuchstaben werden unterschieden.
  • Der Unterstrich zählt als Buchstabe. Umlaute sind dagegen keine Buchstaben. (Erst moderne Sprachen wie Java erlauben fast beliebige Zeichen in Namen.)
  • Ein Name beginnt immer mit einem Buchstaben.
  • Der ANSI-Standard schreibt vor, dass mindestens die ersten 31 Zeichen unterschieden werden. (Dies gilt alles für die sogenannte „interne“ Bindung. Wenn wir anfangen, unser Programm in getrennt übersetzte Module zu teilen, bekommen wir auch „externe“ Bindungen. Dort schreibt der ANSI-Standard nur noch 6 Zeichen vor, und es müssen auch nicht mehr Groß- und Kleinbuchstaben unterschieden werden. Die meisten C-Systeme sind aber nicht so beschränkt.)
Ähnlich wie im ZX81-BASIC müssen Variablen deklariert sein, bevor sie benutzt werden. Dazu geben wir ihre Typen und ihre Namen an. Ein Beispiel:

Code: Alles auswählen

#include <stdio.h>

int main(void) {
    int summand1;
    int summand2;
    int summe;

    summand1 = 123;
    summand2 = 468;
    summe = summand1 + summand2;
    printf("Summe von %d und %d = %d\n", summand1, summand2, summe);

    return 0;
}
Im Gegensatz zu BASIC werden in C Gültigkeitsbereiche und Lebensdauern von Variablen unterschieden. In BASIC sind alle Variablen im gesamten Programm bekannt und existieren ab dem Moment, in dem ihnen der erste Wert zugewiesen wurde.

Bei C kommt es auf den Ort an, an dem eine Variable definiert wurde, und auf zwei Schlüsselworte namens static und auto. Das folgende Beispiel demonstriert das einmal:

Code: Alles auswählen

#include <stdio.h>

int main(void) {
    int bekannt;

    printf("bekannt = %d\n", bekannt);

    bekannt = 123;

    {
        auto int automatisch;
        static int statisch1;

        printf("automatisch = %d", automatisch);
        automatisch = 987;
        printf(", %d\n", automatisch);

        statisch1 = statisch1 + 1;
        printf("statisch1 = %d\n", statisch1);

        bekannt = bekannt + 1;
    }

    printf("bekannt = %d\n", bekannt);

    {
        auto int automatisch;
        static int statisch2;

        printf("automatisch = %d", automatisch);
        automatisch = 987;
        printf(", %d\n", automatisch);

        statisch2 = statisch2 + 2;
        printf("statisch2 = %d\n", statisch2);

        bekannt = bekannt + 2;
    }

    printf("bekannt = %d\n", bekannt);

    return 0;
}
Lasst dieses Programm dreimal laufen und notiert euch die Ausgaben.

Zunächst seht ihr, dass innerhalb des Programms noch weitere Blöcke durch geschweifte Klammern abgesetzt sind. Wie bereits gesagt, ist die Art und Tiefe der Einrückung und die Anzahl und Anordnung der Leerzeilen und Leerzeichen reine Geschmackssache, es soll nur deutlich werden, was gemeint ist.

Im äußeren Block wird eine Variable „bekannt“ definiert, die offenbar auch innerhalb der inneren Blöcke bekannt ist und benutzt werden kann. Wenn ihr dort ein neuer Wert zugewiesen wird, bleibt dieser auch beim Verlassen eines inneren Blocks erhalten.

In den inneren Blöcken werden jeweils zwei Variablen definiert. Die erste, „automatisch“, hat die Speicherklasse auto, und die zweite, „statisch“, hat die Speicherklasse static. Die Ausgaben des Programms beweisen ihre Eigenschaften:
  • Variablen können nur am Anfang eines Blocks definiert werden.
  • static-Variablen haben direkt mit ihrer Definition einen definierten Wert, bei dem alle Bits 0 sind.
  • auto-Variablen haben direkt nach ihrer Definition (dem Beginn ihrer Lebensdauer) einen zufälligen Wert.
  • Die Lebensdauer von static-Variablen beginnt mit der Existenz des Programms. Sie leben bis zum Ende des Programms. Beim z88dk gibt es noch eine Besonderheit: auch wenn das Programm mit der Rückkehr aus der Funktion main() beendet wird, bleiben die Werte der static-Variablen erhalten.
  • Die Lebensdauer von auto-Variablen endet am Ende des Blocks, in dem sie definiert wurden. Wenn der Block erneut durchlaufen wird, werden diese Variablen neu „erzeugt“.
  • Der Gültigkeitsbereich von Variablen reicht vom Anfang bis zum Ende des Blocks, in dem sie definiert sind, einschließlich darin untergeordneter Blöcke. Damit können die Namen auch in anderen (nicht untergeordneten) Blöcken für andere Variablen benutzt werden, ohne dass es zu Konflikten kommt. (Dies gilt bei z88dk leider nur für auto-Variablen! Der Compiler ist nicht ganz ANSI-konform, aber für unsere Zwecke reicht es. Die beiden Variablen statisch1 und statisch2 hätten eigentlich beide „statisch“ heißen sollen, aber das ergab einen Fehler.)
  • Variablen ohne Speicherklasse sind innerhalb von Blöcken auto-Variablen.
Umwandlung zwischen Datentypen

Wie versprochen, kommen wir jetzt auf die Umwandlung zwischen Datentypen zurück. Dafür gibt es ein Konzept, das cast genannt wird. Dieses englische Wort bedeutet in diesem Zusammenhang „Gussform“, und das beschreibt ganz gut, worum es geht.

Wenn wir einen Ausdruck eines bestimmten Typs haben, aber einen anderen Typ benötigen, wenden wir einen Cast an:

Code: Alles auswählen

    short kurzer_wert;
    long langer_wert;

    kurzer_wert = (short)langer_wert;
Vor den Ausdruck wird also ganz einfach der gewünschte Datentyp in runde Klammern geschrieben. Der Compiler baut eventuell nötige Umwandlungen in den Maschinencode ein. Dies kann im entstehenden Maschinencode von ganz einfach, nämlich keiner Anweisung, bis zu ganz kompliziert, nämlich dem Aufruf einer Umwandlungsfunktion, übersetzt werden.

Umwandlungen zwischen Ganzzahlen nimmt der Compiler bei ungleichen Typen automatisch vor, ihr braucht das eigentlich nicht hinzuschreiben. Dieses implizite Casting ist aber schlecht zu sehen, wenn man den Quelltext liest. Schreibt deshalb bitte immer einen Cast explizit hin, der erzeugte Maschinencode ist identisch – aber der nächste Leser des Programms wird es euch danken!.

Hausaufgaben
  1. Macht euch einmal Gedanken, warum ein Name mit einem Buchstaben beginnen muss.
  2. Schreibt ein Programm, das acht Variablen mit den möglichen verschiedenen Ganzzahltypen (siehe Teil 4) definiert. Es soll der Variablen des Typs unsigned long den Wert „(200UL << 24) + (200U << 8) + 200“ zuweisen, und danach den anderen Variablen den Wert dieser Variable. Lasst euch alle acht Variablen ausgeben. Was beobachtet ihr?
  3. Ändert das Programm so, dass zuerst der Variablen des Typs signed char der Wert „-100“ zugewiesen wird, und danach den anderen Variablen der Wert dieser Variablen. Was beobachtet ihr jetzt?
B0D0: Real programmers do it in hex.

zx81fan
User
Beiträge: 86
Registriert: 30.10.2008, 08:26

Re: C für BASIC-Programmierer - der z88dk-Cross-Compiler

Beitrag von zx81fan » 08.12.2008, 08:53

Jup, guter Kursus. Gefällt mir sehr.

mfg
peter

Benutzeravatar
siggi
User
Beiträge: 2346
Registriert: 06.12.2005, 08:34
Wohnort: D, Hessen, tiefste Werreraa
Kontaktdaten:

Re: Teil 6 von "C für BASIC-Programmierer"

Beitrag von siggi » 08.12.2008, 09:48

bodo hat geschrieben: [*]Die Lebensdauer von static-Variablen beginnt mit der Existenz des Programms. Sie leben bis zum Ende des Programms. Beim z88dk gibt es noch eine Besonderheit: auch wenn das Programm mit der Rückkehr aus der Funktion main() beendet wird, bleiben die Werte der static-Variablen erhalten.
Static-Variablen können beim Zeddy ein Fallstrick oder ein nettes Feature sein. Bei Static-Variablen muß man beachten:

Die Initialisierung von Static-Variablen findet zur Compile-Zeit(*) statt, nicht zur Laufzeit!

Dies ist keine Eigenheit des Z88DK-Compilers, sondern das machen auch andere so.
Man kann das an so einem Beispiel sehen:

Code: Alles auswählen

void test(void)
{
   static int i = 1;
   printf("i = %d\n", i);
   i++;
}
Läßt man das Programm, das diese Funktion aufruft, erstmalig laufen, dann wird korrekt 1 ausgegeben. Beim nächsten RUN dann aber 2. Und beim nächsten RUN dann 3 usw.

Auf anderen Systemen (DOS, UNIX, OS-9 ...) fällt das meist nicht auf, da bei EXECUTE eines Progrrammes dies normalerweise von Festplatte geladen und im Speicher augeführt wird. Und startet man es nochmal, dann wird wieder das Original von der Festplatte geholt.

Beim Zeddy wird ein LOAD aber meist nur einmal durchgeführt (und damit nur einmalig das Original geladen). Jedes RUN startet dann aber das im Ram liegende Programm, das von vorherigen Lauf aber schon modifiziert wurde!

Das kann man sich zunutze machen: ich habe mal ein XMODEM-Programm auf den Zeddy portiert. Das fragt zum Programmstart einige Parameter ab. Stirbt das Programm durch einen Fehler und wird neu gestartet, dann hat es diese Parameter noch und ich spare mir Tipparbeit :-)

Problem: wie kriegt man dann auf dem Zeddy die static-Variablen sauber initialisiert, auch wenn ein Programm mehrmals ge-RUN-t wird?
Lösung: man muß dies selbst tun.
Aber: zwar liegen die static-Variablen im gleichen Speicherbereich wie globale Variablen (nicht auf dem Stack, wie die auto-Variablen), aber sie sind nur innerhalb der Funktion, die sie deklariert hat, bekannt. Also kann auch nur diese Funktion darauf zugreifen und diese initialisieren:

Obige Funktion könnte man also so schreiben:

Code: Alles auswählen

void test(int reset)
{
   static int i;
   if (reset)
   {
      /* hier werden nur die Variablen initialisiert */
      i = 1;
   }
   else
   {
     /* das ist die eigentliche Aufgabe */
     printf("i = %d\n", i);
     i++;
   }
}
Das Hauptogramm muß dann nach Start einmalig die Funktion mit Parameter 1 aufrufen, wodurch die Initialisierung erfolgt:

test(1);

Im weiteren Verlauf, wenn die Funktion test ihre Arbeit machen soll, wird sie mit Parameter 0 aufgerufen:
test(0);

Wenn man also C-Programme von anderen Systemen auf den Zeddy portiert, dann sollte man überprüfen, ob das Programm initialisierte static-Variablen nutzt. Ggf. muß dann das Programm modifiziert werden, damit man es mehrmals RUN-nen kann.

Gruß,
Siggi


(*) Der Compiler erzeugt keinen Code zur Intialisierung. Es wird lediglich der von der static-Variable genutzte Speicherbereich beim Assembleren mit dem Initialwert gefüllt.

Benutzeravatar
bodo
User
Beiträge: 319
Registriert: 14.02.2007, 17:21
Kontaktdaten:

Re: C für BASIC-Programmierer - der z88dk-Cross-Compiler

Beitrag von bodo » 08.12.2008, 17:12

Vielen Dank, Siggi, für deine wunderbare Erklärung!
Dies ist keine Eigenheit des Z88DK-Compilers, sondern das machen auch andere so.
Jedenfalls alle, die kein (EP)ROM für das Programm benutzen. Das funktioniert natürlich nicht, wenn ein ROM benutzt wird, denn dann wären initialisierte Variablen nicht beschreibbar. Deshalb besteht bei solchen Systemen der Startcode auch aus einer Kopierroutine, die die Startwerte der initialisierten Variablen an die entsprechende Stelle im RAM kopiert. Solch ein Verfahren wäre beim z8ddk auch denkbar, und eventuell enthält der Startcode für andere Zielplattformen (Targets) auch tatsächlich eine solche Routine.

Es gibt also grundsätzlich folgende Speicherbereiche:
  • Der Maschinencode des Programms, "text" genannt.
  • Die Inhalte der initialisierten und schreibgeschützten Variablen, meist "rodata" (read-only-data) genannt. Hierzu gehören je nach Definition auch alle literalen Zeichenketten.
  • Die Inhalte der initialisierten, aber schreibfähigen Variablen, "data" genannt. Diesen Block meine ich oben.
  • Der Bereich der mit 0-Bits initialisierten und schreibfähigen Variablen, "bss" (block started by symbol) genannt. Dieser Bereich wird natürlich nur über seine Größe definiert, das Löschen besorgt im Normallfall eine Löschroutine im Startcode.
B0D0: Real programmers do it in hex.

t0m
Site Admin
Site Admin
Beiträge: 580
Registriert: 04.03.2004, 13:32
Wohnort: CH-5506 Mägenwil
Kontaktdaten:

Re: C für BASIC-Programmierer - der z88dk-Cross-Compiler

Beitrag von t0m » 07.01.2009, 15:11

Hallo C-Programmierer,

ich habe da ein Problem! Ich brauche eine Möglichkeit zu wissen, ob gerade eine Taste gedrückt wird.

Code: Alles auswählen

c = getchar()
funktioniert zwar, wartet aber auf eine Taste. Ich möchte aber, dass es sofort zurückkehrt (z.B. mit 0), wenn keine Taste gedrückt wurde und dass nicht auf eine Taste gewartet wird.

Ich hab's mit folgendem probiert:

Code: Alles auswählen

int keypressed(void)
{
  #asm
  call 0x02BB
  inc HL
  #endasm
}
aber irgendwie wird da manchmal eine Taste gefressen (ich kann mit einem nachfolgenden c=getchar() die Taste nicht mehr auslesen). Gibt's da in C eine andere (bessere) Lösung?
t0m

There are 10 types of people in this world: those who understand binary and those who don't.

t0m
Site Admin
Site Admin
Beiträge: 580
Registriert: 04.03.2004, 13:32
Wohnort: CH-5506 Mägenwil
Kontaktdaten:

Re: C für BASIC-Programmierer - der z88dk-Cross-Compiler

Beitrag von t0m » 08.01.2009, 18:54

Hallo zusammen,

neben dem Problem mit dem Tastatur-Auslesen habe ich noch ein anderes Problem. Wie kann ich in C den Bildschirm löschen (ein simples CLS) ? Das gibt's doch bestimmt in C, oder? Es kann ja nicht sein, dass ich hierfür den Inline-Assembler brauche, um die ROM-Routine zum Bildschirmlöschen aufzurufen, oder doch? Habe vermutlich das gesammte WIKI zum Z88DK durchforstet, aber dort gibt's CLS-Routinen nur für den HRG-Modus, aber nicht für den SLOW-Modus (-startup=2)....
t0m

There are 10 types of people in this world: those who understand binary and those who don't.

Benutzeravatar
siggi
User
Beiträge: 2346
Registriert: 06.12.2005, 08:34
Wohnort: D, Hessen, tiefste Werreraa
Kontaktdaten:

Re: C für BASIC-Programmierer - der z88dk-Cross-Compiler

Beitrag von siggi » 09.01.2009, 10:28

Hi Tom
CLS habe ich im Web-Server so gemacht:

Code: Alles auswählen

/* clear screen */

void cls (void)
{
   putchar(12);
}
Das schreibt das ASCII(!)Zeichen 12 direkt an den Bildschirm-Handler, der daraus ein Clear Screen macht.
Nicht vergessen: der Z88DK verwendet intern ASCII-Codierung der Zeichen. Wenn sie auf dem Bildschirm landen sollen, werden sie dann erst in den Zeddy-Zeichensatz konvertiert!

Und falls Du auch ein PRINT AT brauchst: das habe ich per ROM -Call gemacht:

Code: Alles auswählen


#define ROM_PRINT_AT $8F5

void PRINT_AT(line, column)
short line, column;
{
#asm
    pop     hl      ; caller address
    pop     de      ; get column
    pop     bc      ; get line
    push    bc      ; recover stack
    push    de
    push    hl
    ld b,c  ; line into b
    ld c,e  ; column into c
    JP ROM_PRINT_AT
#endasm
}
Aber: man kann das Programm auch so compilieren, daß die Zeddy-Bildausgabe ein ANSI-Terminal emuliert (zx81ansi oder so). Dann kann man über ANSI-Escape-Sequenzen den Bildschirm ansteuern. Und das ist dann wieder einfach portierbar auf andere Systeme ..

HTH SIggi

t0m
Site Admin
Site Admin
Beiträge: 580
Registriert: 04.03.2004, 13:32
Wohnort: CH-5506 Mägenwil
Kontaktdaten:

Re: C für BASIC-Programmierer - der z88dk-Cross-Compiler

Beitrag von t0m » 09.01.2009, 11:55

Hallo siggi,

das mit dem CLS klappt nun super! Danke Dir, wusste nicht, dass es so einfach ist, habe immer nach einer Library-Funktion gesucht. Für die Tastatur-Abfrage habe ich nun kbhit() genommen (aus conio.h), welche besser funktioniert als die ROM-Routine, wenn auch nicht 100%ig. Manchmal erscheint ein Zeichen einfach nicht in getchar()... ?
t0m

There are 10 types of people in this world: those who understand binary and those who don't.

Benutzeravatar
siggi
User
Beiträge: 2346
Registriert: 06.12.2005, 08:34
Wohnort: D, Hessen, tiefste Werreraa
Kontaktdaten:

Re: C für BASIC-Programmierer - der z88dk-Cross-Compiler

Beitrag von siggi » 09.01.2009, 12:19

Hallo Tom,
in dem Spielprogrämmchen DSTAR.C (in EXAMPLES) wird zur Keyboard-Abfrage "getk()" verwendet.
Vielleicht klappt's damit besser?

Siggi

t0m
Site Admin
Site Admin
Beiträge: 580
Registriert: 04.03.2004, 13:32
Wohnort: CH-5506 Mägenwil
Kontaktdaten:

Re: C für BASIC-Programmierer - der z88dk-Cross-Compiler

Beitrag von t0m » 09.01.2009, 13:19

Hallo siggi,

Du bist unglaublich! Genau das habe ich gesucht! Eine nicht wartende Zeicheneingabe. Habe es gerade ausprobiert! mit getk() klappt es nun SUPER! Man kann jetzt auf dem Zeddy auch deutlich flüssiger schreiben, und kein Zeichen geht verloren. Wenn man länger auf einem Zeichen bleibt, hat man sogar eine Art Tasten-Repeat!
t0m

There are 10 types of people in this world: those who understand binary and those who don't.

zx81fan
User
Beiträge: 86
Registriert: 30.10.2008, 08:26

Re: C für BASIC-Programmierer - der z88dk-Cross-Compiler

Beitrag von zx81fan » 09.01.2009, 17:07

Also mit dem z88dk zu spielen macht richtig spass.
Man damit auch seine eigenen Modulchen basteln mit ASM und ohne.

Und mit dem z88dk wird der zx81 richtig gefordert.

mfg

Benutzeravatar
bodo
User
Beiträge: 319
Registriert: 14.02.2007, 17:21
Kontaktdaten:

Re: C für BASIC-Programmierer - der z88dk-Cross-Compiler

Beitrag von bodo » 11.01.2009, 21:01

@t0m: Deine Fragen greifen kommenden Teilen des Kurses voraus - und Siggi hat dieselben Lösungen gefunden...

Ich schreibe gerade an den letzten paar Teilen, die vielleicht erst in zwei Jahren veröffentlicht werden! :o Ich will hier aber dem Magazin nichts wegnehmen, daher erscheinen diese Teile des Kurses im Forum erst nach dem Abdruck im Heft.

Für ganz Neugierige habe ich überlegt, bald eine Inhaltsangabe aller Teile zu veröffentlichen.

Das soll jetzt aber niemanden davon abhalten, nach seinen Neigungen und Ideen mit dem z88dk umzugehen. Und jeder bekommt bei Fragen und Problemen auch Hilfe, ist doch klar!
B0D0: Real programmers do it in hex.

Benutzeravatar
dhucke
User
Beiträge: 822
Registriert: 21.03.2009, 14:50
Wohnort: in der Nähe von Göttingen

Re: C für BASIC-Programmierer - der z88dk-Cross-Compiler

Beitrag von dhucke » 11.09.2010, 21:39

Respekt! Hab das mit dem Hello Welt mal ausprobiert, bin beeindruckt wie toll das geht!
Bin am experimentieren, wie das mit dem compilieren von -sam -zx und -ace läuft

Viele Grüße von dieter!
"Hardware nennt man die Teile eines Computers die man treten kann."
Jeff Pesis

Antworten