Teil 27 „Organization of memory“

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

Teil 27 „Organization of memory“

Beitrag von bodo » 02.01.2011, 18:24

C für BASIC-Programmierer

Arbeiten mit dem z88dk Cross-Compiler

von Jens Sommerfeld und Bodo Wenzel

Teil 27

Bisher haben wir auf unserem Weg von BASIC nach C keine richtig revolutionär neuen Konzepte kennengelernt. Dies soll sich heute ändern, denn wir werden unsere Kenntnisse des Typkonzepts in C erweitern. Und weil Datentypen für Variablen verwendet werden, die wiederum Speicher belegen, passt das gut zum Kapitel 27 von Vickers' Buch mit dem Namen „Organization of memory“.

Strukturen

Nehmt an, ihr wollt von einer Person den Vor- und Nachnamen und das Alter abspeichern. Bisher musstet ihr dafür je eine Variable anlegen. Wie einfach wäre es, wenn es einen Datentyp gäbe, der für alle drei Daten Speicherplatz besäße! Und genau das geht, indem ihr eine Struktur definiert, die für jedes der drei Daten je eine Komponente (auch Feld oder Element genannt) besitzt. Wer jetzt an eine Datenbank denkt, liegt gar nicht so verkehrt!

Eine solche Struktur wird mit dem Schlüsselwort „struct“ eingeleitet. Danach kann optional ein Strukturname folgen, muss aber nicht. Ihr braucht den Strukturnamen, wenn ihr später mehrere Variablen desselben Typs definieren wollt.

Danach kommt ein Block in geschweiften Klammern, der für jede Komponente einen Datentyp und einen Namen enthält. Diese Komponentennamen haben nichts mit Variablennamen zu tun, sondern es sind wirklich nur die Namen der Komponenten!

Ihr könnt nach der Strukturdeklaration auch gleich einen Variablennamen angeben, wie das folgende Beispiel zeigt:

Code: Alles auswählen

#include <stdio.h>
#include <string.h>

int main(void) {
    struct {
        char vorname[30];
        char nachname[30];
        unsigned char alter;
    } person;

    strcpy(person.vorname, "BODO");
    strcpy(person.nachname, "WENZEL");
    person.alter = 46;

    printf("%s %s IST %u JAHRE ALT.\n",
           person.vorname, person.nachname, person.alter);

    return 0;
}
Auf die einzelnen Komponenten wird zugegriffen, indem nach dem Variablennamen ein Punkt und der Komponentenname geschrieben wird. Natürlich sind sowohl lesende als auch schreibende Zugriffe möglich!

Leider kann das z88dk keine geschachtelten Strukturen, also Strukturen, die wiederum Komponenten mit einem Struktur-Datentyp besitzen. Sie lassen sich zwar deklarieren, aber die Experimente damit waren... ähm... enttäuschend. „Normale“ C -Compiler haben damit aber keine Probleme.

Überlagerte Strukturen (Unionen)

Manchmal ist es sinnvoll, im selben Speicher Daten verschiedenen Typs abzulegen. Das kann z.B. der Fall sein, wenn ihr in einer Variablen einmal die Größe eines Rechtecks (Breite und Höhe), ein anderes Mal aber den Durchmesser eines Kreises speichern wollt.

Ein einfacheres Beispiel zeigt das folgende Programm, das herausbekommt, in welcher Reihenfolge Werte mit mehr als einem Byte abgespeichert werden. Dies ist die sogenannte „Endianness“ (http://de.wikipedia.org/wiki/Byte-Reihenfolge, der Name geht auf „Gullivers Reisen“ zurück. Dort gibt es ein Land, dessen Volk in zwei Lager gespalten ist, die Frühstückseier am stumpfen oder am spitzen Ende aufschlagen.):

Code: Alles auswählen

#include <stdio.h>

int main(void) {
    union {
        unsigned short s;
        unsigned char c[2];
    } test;

    test.c[0] = 0x12;
    test.c[1] = 0x34;
    if (test.s == 0x1234) {
        puts("BIG ENDIAN (MSB ZUERST)");
    } else if (test.s == 0x3412) {
        puts("LITTLE ENDIAN (LSB ZUERST)");
    } else {
        puts("ENDIANNESS NICHT BESTIMMBAR");
    }

    return 0;
}
Eine solche Union wird genauso deklariert wie eine Struktur. Nur belegen alle Komponenten denselben Speicherplatz, die Union ist nur so groß, wie es für die größte Komponente nötig ist.

Typdefinitionen

Wenn wir mehrere Variablen desselben Datentyps anlegen wollen, müssen wir z.B. Strukturen oder Unionen Namen geben und das dann auch ausführlich hinschreiben. Auch ist ab und zu „unsigned long“ viel zu lang, oder „int“ ist uns zu maschinenabhängig. Dann können wir uns eigene Datentypen erzeugen, indem wir das Schlüsselwort „typedef“, einen Datentyp, und dann einen Typnamen hinschreiben. Damit ist keine Variable definiert, sondern ein neuer Datentyp!

Dies ist auch dann interessant, wenn ihr eine Bibliothek schreibt, aber deren Nutzer vor den Interna schützen wollt. Die Details brauchen sie nicht zu wissen, und sie sollen eigentlich auch nicht damit herumspielen. Es wird auch gerne ein Datentyp wie „uint_8“ definiert, der einen vorzeichenlosen Ganzzahltyp mit 8 Bits darstellt:

Code: Alles auswählen

typedef unsigned char uint_8;
Im folgenden Beispiel werden zwei neue Datentypen PUNKT und RECHTECK definiert. Diese können dann für Variablen und Parameter verwendet werden:

Code: Alles auswählen

#include <stdio.h>

typedef struct {
    unsigned int x;
    unsigned int y;
} PUNKT;

typedef struct {
    unsigned int l;
    unsigned int o;
    unsigned int r;
    unsigned int u;
} RECHTECK;

static int ist_punkt_in_rechteck(PUNKT *punkt, RECHTECK *rechteck) {
    return punkt->x >= rechteck->l && punkt->x <= rechteck->r &&
           punkt->y >= rechteck->o && punkt->y <= rechteck->u;
}

int main(void) {
    static RECHTECK rechteck = { 30, 40, 150, 200 };
    static PUNKT punkte[] = {
        { 10, 10 }, { 60, 90 }, { 110, 170 }, { 160, 250 }
    }; /* Cool, wie dieses Array initialisiert wird! */
    int index;

    printf("RECHTECK (%u,%u) - (%u,%u):\n",
           rechteck.l, rechteck.o, rechteck.r, rechteck.u);
    for (index = 0; index < 4; index++) {
        printf("PUNKT (%u,%u) IST ",
               punkte[index].x, punkte[index].y);
        if (ist_punkt_in_rechteck(punkte + index, &rechteck)) {
            puts("INNERHALB.");
        } else {
            puts("AUSSERHALB.");
        }
    }

    return 0;
}
Hier wäre eine geschachtelte Struktur für RECHTECK sinnvoll gewesen, aber das z88dk hat keinen korrekten Code dafür erzeugt.

Wie ihr seht, können auch Zeiger auf Strukturen deklariert werden. Gerade bei der Übergabe an Funktionen spart das Code und Speicher, weil ein Zeiger oft kleiner ist als die eigentliche Struktur. Ihr dürft aber nicht vergessen, dass die Funktion dann über den Zeiger den Inhalt der originalen Struktur verändern kann!

Der Zugriff auf die Komponenten einer Struktur, zu der ein Zeiger vorliegt, kann zunächst vollkommen logisch konstruiert werden:

Code: Alles auswählen

(*zeiger_auf_struktur).komponente
Aber es gibt eine nette Abkürzung, die optisch wie ein Zeiger aussieht. Diese ist auch im Beispiel verwendet worden:

Code: Alles auswählen

zeiger_auf_struktur->komponente
Bitfelder

Eine weitere Art, Komponenten zu deklarieren, sind die sogenannten Bitfelder. Dies ist wieder interessant für Leute mit dem Ohr an der Hardware, denn damit können einzelne Bits in einem Byte schön in Hochsprache deklariert werden:

Code: Alles auswählen

struct CONTROL_WORD {
    unsigned int global_enable:1;
    unsigned int selector:3;
    unsigned int unused:2;
    unsigned int enable_a:1;
    unsigned int enable_b:1;
}
Dies könnte beispielsweise ein Byte folgenden Aufbaus deklarieren:

Bit 7 global_enable
Bit 6 selector (Bit 2)
Bit 5 selector (Bit 1)
Bit 4 selector (Bit 0)
Bit 3 (unbenutzt)
Bit 2 (unbenutzt)
Bit 1 enable A
Bit 0 enable B

Wie ihr seht, wird für ein Bitfeld der int-Datentyp verwendet, der am besten noch durch „unsigned“ oder „signed“ genauer spezifiziert wird. Nach dem Komponentennamen folgt ein Doppelpunkt und die Anzahl Bits, die diese Komponente besitzen soll.

Leider ist im C-Standard nicht festgelegt, in welcher Ausrichtung und in welcher Reihenfolge die einzelnen Bitfelder z.B. in Bytes zusammengefasst werden! Wenn ihr das also nutzt, um z.B. auf Steuerbits einer PIO zuzugreifen, solltet ihr durch Experimente sicherstellen, dass euer Compiler das in eurem Sinne macht.

Leider unterstützt das z88dk (noch) keine Bitfelder...

Rückgabetypen

Schließlich erlaubt C auch die Rückgabe eines selbstdefinierten Datentyps, einer Struktur oder einer Union. Dummerweise beherrscht das z88dk das auch nicht...

Hausaufgaben
  1. Deklariert eine Struktur namens „zeit“, die drei Ganzzahlen als Komponenten besitzt. Diese sollen Stunde, Minute und Sekunde speichern.
  2. Schreibt ein Programm, in welchem eine Struktur definiert ist, welche den Namen, Vornamen, das Geburtsdatum und 5 Noten eines Schülers speichern kann. Darüber hinaus soll ein Array definiert werden, welches 15 Schüler einer Schulklasse speichern kann. Die Daten und Noten eines jeden Schülers sollen eingegeben werden. Danach kann der Benutzer entscheiden, ob er alle Daten oder alle Noten der Schüler als Tabelle ausgegeben haben möchte.
B0D0: Real programmers do it in hex.

Antworten