S. d. I.: Programieren in C Folie 5 - 1 5 Datenstrukturen 5.1 • • Basis-Typen C unterstützt die folgenden grundlegenden Typen Integral Types Floating-Point Types Other char int short long signed unsigned enum float double long double void const volatile die Schlüsselwörter signed und unsigned können mit Ausnahme von enum jedem Integral Type vorangestellt werden • der Aufzählungstyp enum wird in einem eigenen Kapitel behandelt • void kann an drei Stellen verwendet werden: 1) als "Rückgabewert" einer Funktion, die keinen Wert zurückliefert (entspricht in Pascal einer Prozedur) 2) als "Parameterliste" einer Funktion, die keine formalen Parameter besitzt 3) zur Kennzeichnung eines Zeigers, dessen Typ zur Zeit noch nicht festgelegt ist Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C • Folie 5 - 2 die Typangaben können außerdem durch die folgenden Angaben modifiziert werden - const - nach der Initialisierung darf die Variable nicht mehr verändert werden - Beispiele: const long double pi = 3.1415926535897932385; const int a = 3, b = 4; int x, y; const int *ptr; variabler Zeiger auf eine Konstante (ptr = &a; ptr = &b; ist erlaubt) int *const ptr konstanter Zeiger auf Variable (ptr = &x; ptr = &y; ist nicht erlaubt. ptr = &x; x = 3; x = 5; ist erlaubt) - - Vorteil: Der Compiler kann einen effizienteren Code generieren, da er den Wert direkt einsetzen kann und nicht erst aus dem Speicher laden muß (wie bei einer Variablen) volatile - die Variable kann auf eine Art und Weise verändert werden, auf die der Compiler keinen Einfluß hat (z.B. über eine Unterbrechungsroutine) ⇒ der Compiler muß alle Ausdrücke / Anweisungen, die diese ⇒ Variable betreffen genau in der vorgeschriebenen Reihenfolge durchführen, d.h. er darf keine Umorganisationen zur Optimierung durchführen Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C - Folie 5 - 3 Beispiele (KEYBOARD sei ein Geräte-Register): 1) for (i = 0; i < 10; ++i) { c = KEYBOARD; copy (c); }; Der Compiler darf die Schleifen-Invariante in der Optimierungsphase nicht vor die Schleife ziehen ⇒ volatile ist notwendig, damit das obige Programm für den Compiler nicht nur ineffizient programmiert ist. 2) extern const volatile int real_time_clock; "real_time_clock" kann auf eine Art und Weise verändert werden, auf die der Compiler keinen Einfluß hat, aber ihr darf weder ein Wert zugewiesen werden noch darf ihr Wert geändert werden. Die Variable ist außerhalb dieser Datei definiert. Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C • • • Folie 5 - 4 Größe und Wertebereich der Basistypen Name Alternative Größe (Bits) Wertebereich char signed char 8 -128 - 127 unsigned char - 8 0 - 255 int signed int signed mindestens 16 mindestens -32.768 - 32.767 unsigned int unsigned mindestens 16 mindestens 0 - 65.535 short int signed short int signed short short 16 -32.768 - 32.767 unsigned short int unsigned short 16 0 - 65.535 long int signed long int signed long long 32 -2.147.483.648 2.147.483.647 unsigned long int unsigned long 32 0 - 4.294.967.295 enum - wie int wie int float - 32 mindestens 1.0 E-37 - 1.0 E+37 double - mindestens 32 mindestens 1.0 E-37 - 1.0 E+37 long double - mindestens 32 mindestens 1.0 E-37 - 1.0 E+37 (unsigned) int hat normalerweise die Größe, die eine CPU in einem Befehl bearbeitet und ist damit implementierungs-abhängig (die aktuelle Größe kann der Datei limits.h entnommen werden) double und long double sind implementierungs-abhängig (die aktuellen Größen können der Datei float.h entnommen werden) Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C • Folie 5 - 5 Anfangsinitialisierung 1) direkt bei der Deklaration 2) eigene Anweisung nach der Deklaration ⇒ wird vom Compiler u.U. unterschiedlich gehandhabt ⇒ Beispiel: ; ; ; ; ; ; ; ; ; Programmausschnitt: static int a = 3, b; /* Fall 1 */ b = 5; /* Fall 2 */ Microsoft C Compiler. Übersetzung mit: cl /Fc <Dateiname> liefert hierfür die folgenden Assembleranweisungen: ; ; $S165_a EQU $S166_b EQU $S165_a DW $S166_b DW 01H ; Adresse *** 000005 *** 000008 a b 03H DUP (?) Code b8 05 00 a3 00 00 ; Fall 1 ; Fall 2 Anweisung mov ax,5 mov WORD PTR $S166_b,ax ⇒ im ersten Fall wird der Wert direkt einer Speicherstelle zugeord⇒ net, während er im zweiten Fall einer Speicherstelle zugewiesen wird ⇒ falls das Programm später im (E)PROM laufen soll, dürfen die ⇒ Anfangswerte i.a. nicht bei der Deklaration zugewiesen werden, da sie in diesem Fall verloren gehen können ! Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C 5.2 • Folie 5 - 6 Felder Sammlung von Objekten desselben Typs, die im Speicher zusammenhängend abgelegt werden • jedes Element des Feldes kann über den Namen des Feldes plus einem Indexausdruck adressiert werden • • das erste Element des Feldes hat den Index 0 bei der Anfangsinitialisierung muß das Feld entweder global definiert werden oder der Speicherklasse static angehören, wenn kein ANSI-C Compiler verwendet wird • Syntax: 1) Typ Feldname [ konstanter Ausdruck ] {= Anf. init.}; - der konstante Ausdruck muß eine positive ganze Zahl liefern - und gibt die Anzahl der Feldelemente an als Typ ist jeder Typ erlaubt, der von "void" verschieden ist - die Feldelemente dürfen keine Funktionen sein - Beispiel: 1) int xyz [10]; Feld für 10 ganze Zahlen 2) char *string [20]; Feld für 20 Zeiger auf Zeichenfolgen 3) struct {float re, im;} complex [100]; 4) int xyz [4] = {1, 2, 3, 4}; vollständige Initialisierung 5) int xyz [4] = {1, 2}; ⇒ automatische Initialiunvollständige Initialisierung ⇒ sierung von xyz[2] und xyz[3] mit dem Wert 0 Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C 2) Folie 5 - 7 Typ Feldname [ ] {= Anfangsinitialisierung}; - diese Form kann nur für initialisierte Felder, für formale Parameter von Funktionen oder für Verweise auf Felder, die an anderer Stelle definiert sind, verwendet werden - Beispiel: 1) int xyz [] = {1, 2, 3, 4}; aufgrund der Initialisierung kann der Compiler die erforderliche Größe des Feldes selbst berechnen 3) 2) const char meldung [] = "Fehler: ..."; 3) void quick_sort (int liste [], int lg, int rg); 4) extern char name []; Typ Feldname [ konst. Ausdr. ][ konst. Ausdr. ]...; - Definition mehrdimensionaler Felder - jeder Index erhält seine eigenen Klammern (siehe auch "häufige Fehler neuer C Programmierer" im Kapitel 1) - der konstante Ausdruck vom ersten Index kann bei initialisierten Feldern und bei Verweisen auf Felder entfallen - Beispiel: 1) float matrix [4][4]; 2) int x [3][4] = { {1, 2, 3, 4}, {5, 6} }; liefert: • x00 = 1, x01 = 2, x02 = 3, x03 = 4, x10 = 5, x11 = 6, x12 = 0, x13 = 0, x20 = 0, x21 = 0, x22 = 0, x23 = 0, mehrdimensionale Felder werden zeilenweise gespeichert, d.h. der letzte Index ändert sich am schnellsten Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C • • Folie 5 - 8 Zugriff auf einzelne Feldelemente: 1) xyz = abc [3]; 2) xyz = abc [3][4]; 3) abc [3] 4) abc [3][4] = 2.78; = 3.0; in C werden i.a. keine Feldgrenzen überprüft, so daß bei Programmierfehlern u.U. der ganze zugreifbare Speicherinhalt überschrieben wird 5.3 Zeiger • Zeiger sind Objekte, deren Wert die Adresse eines anderen Objekts ist • Syntax: Typ * {const, volatile} name; • Zeiger können auf beliebige Objekte eines bestimmten Typs zeigen (inkl. Felder, Strukturen, Funktionen, andere Zeiger, gar nichts (void), ...) • Zeiger vom Typ void können auf jedes Objekt zeigen Falls mit solchen Zeigern eine Operation durchgeführt werden soll, muß der aktuelle Typ des Zeigers vorher mit Hilfe der cast-Operation festgelegt werden, damit die entsprechende Operation korrekt ausgeführt wird (z.B. muß bei p++ zwei addiert werden, wenn p auf ein Objekt vom Typ short int zeigt und vier beim Typ long int) Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C • Folie 5 - 9 const und volatile können wieder verwendet werden, um dem Compiler mitzuteilen, daß der Zeiger im Programm nicht verändert wird bzw. durch etwas verändert werden kann, was außerhalb der Kontrolle des Programms ist • Zeiger auf Objekte vom Typ struct, union oder enum können definiert werden, bevor das Objekt selbst definiert ist (vor einer Operation in einem Ausdruck muß das Objekt allerdings definiert worden sein) • Zeiger benötigen nur den Speicherplatz für eine Adresse • Beispiel: • 1) char *string = "Dies ist eine Zeichenfolge"; 2) int *ptr [10]; ptr ist ein Feld von Zeigern auf Typ int 3) int (*ptr) [10]; ptr ist ein Zeiger auf ein Feld vom Typ int 4) const int *ptr; int const *ptr; ptr ist ein Zeiger auf einen konstanten Wert 5) int *const ptr; aber: ptr ist ein konstanter Zeiger auf int 6) int *const volatile ptr; ptr ist ein konstanter Zeiger auf int, der außerhalb des Programms modifiziert werden darf Operationen mit Zeigern Deklaration: int a[10], *p1, *p2; char *p3; &a[3] - &a[0] p1 = &a[0]; liefert 3 (die Anzahl der Elemente) weist p1 die Adresse des Elements a[0] zu *p1 p2 = p1 + 3; p2 - p1 liefert den Wert von a[0] (dereferencing, indirekter Zugriff) weist p2 die Adresse des Elements a[3] zu p3 = p1 + 2; Fehler !!! (unterschiedliche Zeigertypen) liefert 3 Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C • Folie 5 - 10 aus dem obigen wird deutlich: a[i] entspricht *(p1 + i) , d.h. zur Adressierung der Feldelemente kann sowohl der Feldname als auch ein Zeiger verwendet werden (während Feldnamen nicht modifiziert werden können, können die Werte der Zeiger verändert werden) • geg.: long int a[5][6]; ges.: Zeigerdarstellung und Adresse von a[2][3] a[2][3] = *(a[2] + 3) = *(*(a + 2) + 3) 1) da a ein Feld von Feldern ist, muß die 2 in *(a + 2) an die Objektgröße angepaßt werden, d.h. ein 6-elementiges Feld vom Typ long int ⇒ es werden 2 * 6 * 4 Byte zur Anfangsadresse des Feldes ⇒ addiert (Speicherbedarf für die 0-te und 1-te Zeile) 2) die Objektgröße für die 3 ist long int (Abstand innerhalb einer Zeile) ⇒ es werden noch einmal 3 * 4 Byte zur Anfangsadresse der ⇒ 2-ten Zeile addiert (Speicherbedarf für den 0-ten bis 2-ten Wert) ⇒ falls a ab Adresse 1000 gespeichert ist, befindet sich das Ele⇒ ment a[2][3] bei Adresse 1000 + 2*6*4 + 3*4 = 1060 • die obige Umwandlung ist wichtig, wenn Funktionen für mehrdimensionale Felder mit beliebigen Matrixgrößen geschrieben werden sollen Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C • Folie 5 - 11 Zeiger auf Funktionen - Zeiger können die Adressen von Funktionen aufnehmen - solche Zeiger können als Parameter an Funktionen übergeben werden (für die Bibliotheks-Routine qsort (stdlib.h) wird auf diese Weise z.B. die Adresse einer Routine zum Vergleichen zweier Datensätze übergeben) - Beispiel: char *(*ptr1)(void); ptr1 zeigt auf eine Funktion, die einen Zeiger auf char zurückliefert und keine Parameter benötigt. Aufruf einer Funktion über Zeiger: (*ptr1)() aber: ptr2 ist eine Funktion, die einen Zeiger auf char **ptr2(void); einen Zeiger auf char zurückliefert und keine Parameter benötigt - es können auch Felder von Zeigern auf Funktionen definiert werden • Kombinationen von "*", "++" und "--"-Operatoren bei Zeigern Deklaration: int a[2] = {0, 5}, *p1 = &a[0], *p2 = &a[1], z; Ergebnisse nach Ausführung der jeweiligen Operation (jede Operation geht von der obigen Deklaration aus ! ) Fall Operation z a[0] a[1] p1 zeigt auf 1) 2) 3) 4) 5) 6) 7) z z z z z z z 0 0 0 5 5 1 1 0 0 1 0 0 1 1 5 5 5 5 5 5 5 a[1] a[1] a[0] a[1] a[1] a[0] a[0] = = = = = = = *p1++ *(p1++) (*p1)++ *++p1 *(++p1) ++*p1 ++(*p1) Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C Folie 5 - 12 Fall Operation z a[0] a[1] p2 zeigt auf 1) 2) 3) 4) 5) 6) 7) z z z z z z z 5 5 5 0 0 4 4 0 0 0 0 0 0 0 5 5 4 5 5 4 4 a[0] a[0] a[1] a[0] a[0] a[1] a[1] - = = = = = = = *p2-*(p2--) (*p2)-*--p2 *(--p2) --*p2 --(*p2) folgende Fälle sind identisch, da die Auswerterichtung der Operatoren von rechts nach links ist und die Klammern in diesen Fällen keine andere Gruppierung des Operanden an den Operator bewirken: Fall 1 und Fall 2 Fall 4 und Fall 5 Fall 6 und Fall 7 - im Fall 3 bewirken die Klammern, daß p1 bzw. p2 stärker an den "*"-Operator gebunden wird, wodurch die Auswertereihenfolge geändert wird - Auswertung des Ausdrucks im Fall 1) beim "++"- Operator: * und ++ haben die gleiche Priorität und Auswerterichtung (von rechts nach links). Daraus folgt: a) Auswertung des post-increment-Operators ⇒ p1 wird zunächst unverändert an den nächsten Operator ⇒ übergeben ⇒ es wird vermerkt, daß p1 erhöht werden muß, sobald der ⇒ ganze Ausdruck ausgewertet ist b) Auswertung des "*"-Operators und Wertzuweisung an z c) erhöhen von p1 (und damit beenden der Operation) Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C • Folie 5 - 13 Beispiel: /* Das Programm kopiert einen Speicherbereich auf verschiedene Arten * um und misst die jeweiligen Kopierzeiten. * * Datei: memcpy.c Autor: S. Gross * Datum: 30.08.1993 * */ #include #include #include #include #include <stdio.h> <stdlib.h> <stddef.h> <string.h> <time.h> #define BUF_SIZE #define NUM_COP #define ODD(x) 16 * 1024 64 ((x) & 1 ? 1 : 0) /* Puffergroesse /* Anzahl Kopiervorgaenge */ */ /* Test, ob x ungerade ist */ char *memcpy_1 (char *, char *, size_t); char *memcpy_4 (char *, char *, size_t); char *memcpy_16 (char *, char *, size_t); /* 1-Byte-Transfer /* 4-Byte-Transfer /* 16-Byte-Transfer */ */ */ char dest [BUF_SIZE], source [BUF_SIZE]; /* Zielpuffer /* Quellpuffer */ */ int main (void) { clock_t duration; int i; /* Zeitdauer /* Schleifenvariable */ */ printf ("Zeitdauern fuer \"memcpy\" zum Kopieren von %ld KByte:\n\n", (long) NUM_COP * BUF_SIZE / 1024); /* !!!!!!!!!!!!!!!!!!!!! 1-Byte-Transfer !!!!!!!!!!!!!!!!!!!!! */ duration = clock (); for (i = NUM_COP; i > 0; --i) { memcpy_1 (dest, source, (size_t) BUF_SIZE); }; duration = clock () - duration; printf ("Byte-Transfer: %0.2f Sekunden\n", (double) duration / CLOCKS_PER_SEC); ... /* !!!!!!!!!!!!!!!!!!!!! Bibliotheks-Routine !!!!!!!!!!!!!!!!!!!! duration = clock (); for (i = NUM_COP; i > 0; --i) { memcpy (dest, source, (size_t) BUF_SIZE); }; duration = clock () - duration; printf ("Bibliotheks-Routine: %0.2f Sekunden\n", (double) duration / CLOCKS_PER_SEC); return 0; } Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß */ S. d. I.: Programieren in C Folie 5 - 14 /* Umkopieren eines Speicherbereichs in Byte Portionen. * * Parameter: source Adresse des Quellpuffers * dest Adresse des Zielpuffers * count Anzahl Bytes, die kopiert werden sollen * * Ergebnis: char * Zeiger auf Anfang des Zielpuffers * */ char *memcpy_1 (char *dest, char *source, size_t count) { register char *dest_b, *src_b; long int cnt; dest_b = dest; src_b = source; cnt = (long int) count; while (--cnt >= 0) { *dest_b++ = *src_b++; }; return (char *) dest; } /* Umkopieren eines Speicherbereichs in 4-Byte Portionen. * * Parameter: source Adresse des Quellpuffers * dest Adresse des Zielpuffers * count Anzahl Bytes, die kopiert werden sollen * * Ergebnis: char * Zeiger auf Anfang des Zielpuffers * */ char *memcpy_4 (char *dest, char *source, size_t count) { register unsigned long *dest_l, *src_l; register char *dest_b, *src_b; int remain; /* remainder */ long int cnt; cnt = (long int) count; if (ODD ((ptrdiff_t) source ) || ODD ((ptrdiff_t) dest)) { /* bei ungeraden Puffer-Adressen: Byte Transfer src_b = source; dest_b = dest; while (--cnt >= 0) { *dest_b++ = *src_b++; }; } else { /* bei geraden Puffer-Adressen: Langwort Transfer und nur den * Rest im Byte Transfer */ remain = (int) cnt % 4; cnt = cnt / 4; src_l = (unsigned long *) source; dest_l = (unsigned long *) dest; Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß */ S. d. I.: Programieren in C Folie 5 - 15 while (--cnt >= 0) { *dest_l++ = *src_l++; }; src_b = (char *) src_l; dest_b = (char *) dest_l; while (--remain >= 0) { *dest_b++ = *src_b++; }; }; return (char *) dest; } ... Aufgabe: 1) Schreiben Sie eine Routine, die in einem Puffer mit der Anfangsadresse buf und der Größe n das erste Vorkommen des Zeichens chr findet. Falls das Zeichen im Puffer vorkommt, liefert sie einen Zeiger auf das Zeichen zurück. Andernfalls liefert sie einen NULL-Zeiger zurück. Die Funktion sei durch folgenden Prototypen definiert: char *memchr (const char *buf, int chr, size_t n); Zur Lösung dürfen keine Routinen der C Bibliothek verwendet werden, wie z.B. strchr, strstr, memchr usw. 2) Schreiben Sie eine Routine, die die ersten count Bytes eines Speicherbereichs mit der Anfangsadresse buf1 mit den ersten count Bytes eines Speicherbereichs mit der Anfangsadresse buf2 vergleicht. Als Ergebnis liefert die Funktion einen Wert kleiner als Null, wenn buf1 lexikographisch kleiner als buf2 ist, gleich Null, wenn buf1 gleich buf2 ist und einen Wert größer als Null, wenn buf1 lexikographisch größer als buf2 ist. Die Funktion sei durch folgenden Prototypen definiert: int memcmp (const char *buf1, const char *buf2, size_t count); Zur Lösung dürfen keine Routinen der C Bibliothek verwendet werden, wie z.B. strcmp, strncmp, memcmp usw. Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C 5.4 • Folie 5 - 16 Strukturen Sammlung von Objekten, die eigene Namen und u.U. verschiedene Typen haben • Syntax: 1) 2) struct [tag] {member declaration;} [decl [, decl [, ...]]]; - optional kann ein Typname (tag) festgelegt werden - optional können bereits Variablen definiert werden struct tag decl [, decl [, ...]]; - eine Struktur mit dem entsprechenden Typnamen muß vorher deklariert worden sein - • es werden Variablen dieses Typs definiert Strukturen können analog zu Feldern Anfangswerte bei der Definition zugewiesen werden • die Komponenten der Struktur werden sequentiell gespeichert • im Gegensatz zum Verbund werden die einzelnen Komponenten nicht überlagert ⇒ der Speicherbedarf einer Struktur ergibt sich aus der Summe des ⇒ Speicherbedarfs aller Komponenten plus der Summe aller Bytes, die ggf. für die Ausrichtung der Komponenten an Wortgrenzen (alignment) notwendig sind • die Komponenten einer Struktur folgen im Speicher u.U. nicht direkt aufeinander (ggf. Lücken aufgrund der Ausrichtung an Wortgrenzen) Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C Folie 5 - 17 • eine Struktur kann sich selbst nicht als Komponente enthalten • eine Struktur kann einen Zeiger auf sich selbst als Komponente enthalten (rekursive Datenstruktur) • Beispiel: 1) struct {double re, im; } zahl; Definition einer Variablen zahl, die aus zwei Komponenten vom Typ double besteht. Die Struktur hat keinen Typnamen und ist daher namenlos. 2) struct complex {double re, im; }; Deklaration eines Typnamens complex, der aus zwei Komponenten vom Typ double besteht. Da kein Variablenname angegeben ist, wird noch kein Speicherplatz reserviert (nur Typdeklaration). 3) struct complex zahl; Definition einer Variablen vom Typ struct complex. 4) struct complex {double re, im; } zahl; Definition eines Typnamens complex und einer Variablen zahl. Für die Variable wird Speicherplatz reserviert. 5) struct complex zahl = {3.1, 4.0}; Definition einer Variablen vom Typ struct complex mit Anfangsinitialisierung. Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C • die Komponenten einer Struktur werden mit den Operatoren "." (bei normalen Variablen) • Folie 5 - 18 oder "->" (bei Zeigern auf strukturierte Variablen) selektiert. struct complex zahl = {3.1, 4.0}, *ptr = &zahl; zahl.re = 5.7; ptr->re = 5.7; • entspricht (*ptr).re = 5.7; verkettete Listen ... next ... next ... next NIL Anfang typedef struct element { int key; char *info; struct element *next; } LISTE; #define NIL ((LISTE *) NULL) LISTE *Anfang, *Ende; 1) leere Liste initialisieren Anfang = NIL; Ende = NIL; Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß Ende S. d. I.: Programieren in C Folie 5 - 19 2) Element am Ende der Liste einfügen LISTE *tmp; tmp = (LISTE *) malloc (sizeof (LISTE)); if (tmp == NIL) { fprintf (stderr, "Kein Speicher verfuegbar!"); exit (1); }; tmp->key = ...; tmp->info = ...; tmp->next = NIL; if (Anfang == NIL) /* leere Liste ? { Anfang = tmp; } else { Ende->next = tmp; }; Ende = tmp; */ − "Anfang", "Ende" und "Ende->next" sind Speicherzellen, die die Adresse eines Listenelements aufnehmen können − die Unterscheidung zwischen leerer und nicht leerer Liste kann in C entfallen, wenn es eine Speicherzelle gibt, die die Adresse der Speicherzelle enthält, die die Adresse des Listenelements aufnehmen soll LISTE **ptr; ptr = &Anfang; for (...) { *ptr = (LISTE *) malloc (sizeof (LISTE)); if (*ptr == NIL) { fprintf (stderr, "Kein Speicher verfuegbar!"); exit (1); }; ... /* Daten erstellen (*ptr)->key = ...; (*ptr)->info = ...; (*ptr)->next = NIL; ptr = ???; }; Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß */ S. d. I.: Programieren in C − Folie 5 - 20 Ablauf des Listenaufbaus Adresse Speicherinhalt Bemerkung &Anfang 20 40 108 60 60 68 80 − 68, 88, Anfang 108 ? info next 1. Element der Liste key 100 100 108 40, key 80 88 ptr info next 2. Element der Liste key NIL info next 3. Element der Liste Realisierungsmöglichkeiten für eine Initialisierungsroutine 1) LISTE *init (void) Aufruf: Anfang = init (); 2) void init (LISTE **ptr) Aufruf: init (&Anfang); Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C Folie 5 - 21 Aufgaben: 1) Gegeben sei der folgende Programmausschnitt: #define NUM_EL 10 /* Anzahl Elemente der Liste */ /* Ende der Liste */ typedef struct elem { int key; char *info; struct elem *next; } LIST; #define NIL ((LIST *) NULL) void init (LIST **head); void print (LIST *head); Erstellen Sie ein Programm, das eine Liste mit zufälligen Werten erzeugt und ausgibt. 2) Schreiben Sie ein Programm, das die Mandelbrotmenge für die folgenden Parametersätze ausgibt: p_min p_max q_min q_max -2.25 -0.19920 -0.713 0.75 -0.12954 -0.4082 -1.5 1.01480 0.49216 1.5 1.06707 0.71429 Algorithmus zur Ausgabe einer Mandelbrotmenge: Bildschirmauflösung: Anzahl Farben: Farbe "schwarz": MaxX mal MaxY Punkte MaxColor 0 Schritt 0: initialisiere p_min, p_max, q_min, q_max M = 100 delta_p = (p_max - p_min) / MaxX delta_q = (q_max - q_min) / MaxY Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß (Bildausschnitt) (Iterationstiefe) (Schrittweite) S. d. I.: Programieren in C Folie 5 - 22 führe die folgenden Schritte für alle Rasterpunkte (np, nq) des Bildschirms aus Schritt 1 (Anfangsinitialisierung): p0 = p_min + np * delta_p q0 = q_min + nq * delta_q k = 0 xk = 0.0 yk = 0.0 Schritt 2 ((k+1)-ten Wert bestimmen): xk1 = xk * xk - yk * yk + p0 yk1 = 2 * xk * yk + q0 k = k + 1 (aktuelle reelle Koordinaten) (Schleifenvariable) ((xk, yk): k-ter Wert) ((xk1, yk1): (k+1)-ter Wert) Schritt 3 (Farbe bestimmen): r = xk1 * xk1 + yk1 * yk1 falls r > M, wähle als Farbe Nummer k und gehe zu Schritt 4 falls k = MaxColor, wähle die Farbe "schwarz" und gehe zu Schritt 4 iteriere Schritt 2 Schritt 4: ordne dem Punkt (np, nq) die ermittelte Farbe zu und gehe zum nächsten Punkt (Schritt 1) Zur Lösung der Aufgabe dürfen nur die Dateien "gra_*.c" und "vga.h" benutzt werden, die im Rahmen dieser LVA zur Verfügung gestellt werden. Der Benutzer darf die Mandelbrotmenge auswählen, die dargestellt werden soll. Die Bildausschnitte der obigen Tabelle sollen als Feld einer geeigneten Struktur implementiert werden. Programmaufbau: Deklarationen und Bildauswahl; InitGraphic (...); Mandelbrotmenge ausgeben (SetPixel (...);) wait_for_input (); CloseGraphic (); Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C 5.5 • Folie 5 - 23 Bit-Felder ein Bit-Feld ist eine Komponente einer Struktur mit folgender Syntax: Typ {name} : konstanter Ausdruck; Der Typ kann int, signed int oder unsigned int sein. Falls der optionale Name fehlt, kann auf das Bit-Feld nicht zugegriffen werden (solche Felder werden zu Füllzwecken verwendet). Der konstante Ausdruck muß eine positve ganze Zahl liefern, die die Größe des Typs nicht überschreiten darf. • Bit-Felder hängen von der jeweiligen Implementierung ab und sind i.a. nicht portabel. Folgende Details werden vom Standard nicht festgelegt: 1) der Typ int kann als signed int oder unsigned int implementiert werden 2) falls zwei aufeinanderfolgende Bit-Felder in einem Wort vom Typ int untergebracht werden können, müssen sie in dieses Wort gepackt werden aber: falls sie nicht in einem Wort vom Typ int untergebracht werden können, hängt es von der Implementierung ab, ob das zweite Bit-Feld teilweise im ersten und teilweise in einem zweite Wort oder ob es vollständig in einem eigenen Wort untergebracht wird 3) die Bitfelder können von rechts nach links oder von links nach rechts angeordnet werden Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C • • • Folie 5 - 24 ein namenloses Bit-Feld der Länge 0 erzwingt, daß die nächste Komponente der Struktur an einer int-Grenze beginnt folgendes ist nicht möglich: - Bestimmung der Adresse eines Bit-Feldes - Zeiger auf Bit-Felder (Bitfelder haben keine Adresse) - Deklaration eines Feldes von Bit-Feldern - Funktionen, die Bit-Felder als Ergebnis liefern Beispiel: 1) struct { unsigned int Zeichen unsigned int Farbe unsigned int unterstreichen unsigned int blinken } Bildschirm [25][80]; Zugriff: Bildschirm Bildschirm Bildschirm Bildschirm : : : : 8; 4; 1; 1; [3][12].Zeichen [3][12].Farbe [3][12].unterstreichen [3][12].blinken 2) struct { char name [30]; int alter; unsigned int geschlecht : 1; } person; 3) struct { unsigned int select unsigned int reset unsigned int ld_time_const unsigned int trigger unsigned int slope unsigned int range unsigned int mode unsigned int enable_int } z80_ctc_ch_ctrl_reg; Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß : : : : : : : : 1; 1; 1; 1; 1; 1; 1; 1; = = = = 'a'; 0x0a; 0; 1; S. d. I.: Programieren in C 4) struct { unsigned int member_1 unsigned int unsigned int member_2 unsigned int int member_3; }; Folie 5 - 25 : : : : 3; 5; 3; 0; drei Bit für member_1 reservieren; fünf Bit sind undefiniert; drei Bit für member_2 reservieren; int-Grenze wählen 5.6 • Verbund enthält (zu verschiedenen Zeiten) Objekte verschiedenen Typs unterschiedlicher Größe (entspricht in etwa dem variant record in Pascal) • alle Komponenten beginnen an derselben Speicheradresse, d.h. im Gegensatz zur Struktur werden die Komponenten überlagert ⇒ der Speicherbedarf eines Verbunds ergibt sich aus dem Spei⇒ cherbedarf der größten Komponente • der Programmierer muß zu jedem Zeitpunkt wissen, über welchen Komponentennamen er den Speicherbereich ansprechen darf, damit er sinnvolle Daten erhält • Syntax: 1) union [tag] {member declaration;} [decl [, decl [, ...]]]; - optional kann ein Typname (tag) festgelegt werden - optional können bereits Variablen definiert werden Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C 2) Folie 5 - 26 union tag decl [, decl [, ...]]; - ein Verbund mit dem entsprechenden Typnamen muß vorher deklariert worden sein - • es werden Variablen dieses Typs definiert bei einem Verbund kann nur der ersten Komponente ein Anfangswert bei der Definition zugewiesen werden (neu bei ANSI-C) • ein Verbund kann sich selbst nicht als Komponente enthalten • ein Verbund kann einen Zeiger auf sich selbst als Komponente enthalten (rekursive Datenstruktur) • • die Komponenten eines Verbunds werden analog zur Struktur mit den Operatoren "." (bei normalen Variablen) oder "->" (bei Zeigern) selektiert. Beispiel: 1) struct {char *name; int flags; int utype; union {int ival; float fval; char *sval; } u; } symtab [NSYM]; Definition einer Symboltabelle. Eine Variable kann nur einen Typ haben, so daß hier ein Verbund zur Aufnahme des Wertes geeignet ist. Zugriff: symtab [i].u.ival; symtab [i].u.sval [0]; *symtab [i].u.sval; Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C Folie 5 - 27 2) der Zugriff auf die Register des Prozessors ist für die verschiedenen Möglichkeiten als Verbund realisiert (siehe Datei dos.h) 3) union { struct {int i; float f; } val; char name [6]; } test = {2, 3.3}; Dies ist bei alten C Compilern nicht erlaubt ! • ein Verbund hängt u.U. von der jeweiligen Implementierung ab und ist dann nicht portabel /* Bit, Byte und Wort Zugriff auf Hardware Register. * * Folgende Compiler verarbeiten die folgenden Datenstrukturen korrekt: * * Mark Williams C fuer Motorola 68000 * * Fuer folgende Compiler muss der Byte Zugriff getauscht werden, d.h. * "lower byte" vor "higher byte": * * VAX/VMS C * Microsoft C fuer 8086/88 * Lattice C fuer 8086/88 * Turbo C fuer 8086/88 * * Fuer folgende Compiler muss der Bit Zugriff getauscht werden, d.h. * "higher bit" vor "lower bit" (Bits werden von links nach rechts * angeordnet): * * Lattice C fuer 8086/88 * * Alle anderen oben erwaehnten Compiler verarbeiten die angegebenen * Bitstrukturen korrekt, da sie Bits von rechts nach links anordnen. * * Datenstruktur fuer BIT, BYTE und WORD Zugriff definieren. * * Datei: bit_byte.h Autor: S. Gross * Datum: 30.08.1993 Stand: Compiler im Jahr 1986 * */ typedef union { struct { unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned int int int int int int int int int int bit_0 bit_1 bit_2 bit_3 bit_4 bit_5 bit_6 bit_7 bit_8 bit_9 : : : : : : : : : : 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int } bit_access; Folie 5 - 28 bit_10 bit_11 bit_12 bit_13 bit_14 bit_15 : : : : : : 1; 1; 1; 1; 1; 1; struct { unsigned char high_byte; unsigned char low_byte; } byte_access; unsigned int } BIT_BYTE_WORD; /* Bit 8 bis 15 /* Bit 0 bis 7 */ */ word_access; Aufgabe: 1) Zeigen Sie, daß die obigen Behauptungen für den Microsoft C Compiler und den Turbo C Compiler von Borland richtig sind. 2) Wie verhält sich der GNU C Compiler beim Bit-Byte-Wort-Zugriff ? Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C 5.7 • Folie 5 - 29 Aufzählungen Syntax: 1) enum [tag] {id [= konst. Ausdruck] [, id [= ...]]} [decl [, decl [, ...]]]; 2) - optional kann ein Typname (tag) festgelegt werden - optional können bereits Variablen definiert werden enum tag decl [, decl [, ...]]; - eine Aufzählung mit dem entsprechenden Typnamen muß vorher deklariert worden sein - es werden Variablen dieses Typs definiert • eine Aufzählung ist eine Menge von Konstanten vom Typ int • eine enum-Variable kann zu jedem Zeitpunkt genau einen Wert der Aufzählung speichern • enum-Variablen können wie int-Variablen in Index-Ausdrücken und als Operanden bei allen arithmetischen und relationalen Operatoren verwendet werden • jeder Name in der Aufzählungsliste repräsentiert einen Wert der Liste • dem ersten Namen wird automatisch der Wert 0 zugeordnet und jedem weiteren Namen der nächste Wert in fortlaufender Reihenfolge • die automatische Zuweisung wird unterbrochen, wenn einem Namen explizit ein Wert zugewiesen wird (der konstante Ausdruck muß einen positiven oder negativen Wert vom Typ int liefern) Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß S. d. I.: Programieren in C • Folie 5 - 30 falls der nächste Name keine explizite Zuweisung besitzt, erhält er den Wert seines Vorgängers plus 1 • eine Aufzählung kann mehrere Namen mit demselben Wert besitzen (z.B. können null und zero beide den Wert 0 erhalten) • Beispiel: 1) enum antwort { nein, ja = -5, vielleicht = 20} frage; frage = nein; 2) Test von Sortierverfahren bei verschiedenen Datenbeständen typedef struct { long key; char *info; } SATZ; /* Aufbau eines Satzes */ typedef enum { sorted, inv_sorted, block = 3, multiple, random, no_more } LISTE; ... /* zu untersuchende Listen */ /* Schleifenvariable */ int main (void) { LISTE nr; ... for (nr = sorted; nr < no_more; ++nr) { init (nr, original); /* original ist global def. ... }; return (0); } Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß */ S. d. I.: Programieren in C void init (LISTE nr, SATZ *r) { long i; switch (nr) { case sorted: for (i = 0; i < n; ++i) { r[i].key = i; r[i].info = "sorted"; }; break; ... }; Folie 5 - 31 /* Listen initialisieren */ /* n ist global definert */ } 5.8 • Definition neuer Datentypen mit Hilfe von typedef kann ein Synonym für einen Datentyp definiert werden • Syntax: typedef Typ Synonym • Beispiel: siehe oben Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß