Einführung in die Programmiersprache C Problem Finde
Transcription
Einführung in die Programmiersprache C Problem Finde
C Tutorial © Mathias Waack <[email protected]> Page 1 C Tutorial © Mathias Waack <[email protected]> Einführung in die Programmiersprache C Literatur und Informationsquellen Einführung in die Programmiersprache C Allgemeines 1. Brian W. Kernighan, Dennis M. Ritchie: Programmieren in C, Hanser/Prentice Hall 2. ANSI: American National Standard Programming Language C, X3.159-1989 3. Usenet: de.comp.lang.c, comp.lang.c (comp.std.c) 4. WWW: http://www.lysator.liu.se/c/index.html 5. FAQ’s: Page 2 http://www.eskimo.com/~scs/C-faq/top.html http://www.plethora.net/~seebs/faqs/c-iaq.html http://www-info2.informatik.uni-wuerzburg.de/staff/joscho/c/dclc-faq/ 6. RRZN: Die Programmiersprache C, RRZN Hannover, SPR.C1 (vom RRZN sind auch Nachschlagewerke fuer UNIX, Windows NT und viele StandardSoftwarepakete erhältlich) Für alle frei erhältliche Software (gcc, gdb...) ist Doku im Internet verfügbar, ebenso existieren für diese Tools und die Funktionen der C-Library man-Pages Weitere Informationen liefern die gängigen Suchmaschinen im WWW - C ist case sensitiv, d.h. foo und Foo sind in C unterschiedliche Namen. - Ein Objekt ist in C ein benannter, logisch zusammenhängender Speicherbereich mit mind. 1 Byte Länge. - Die Größe einer Datenstruktur in C (d.h. die Anzahl der Bits, in der z.B ein int gespeichert wird) ist implementierungsabhängig und darf daher nicht als konstant angenommen werden! Die Größe eines Datentyps T kann mit Hilfe des Operators sizeof festgestellt werden. - In C muß jeder Bezeichner vor seiner ersten Benutzung deklariert worden sein, daß heißt dem Compiler müssen Namen und Typ des Bezeichners bekannt gemacht werden. - C ist eine statisch getypte Sprache, d.h. jede Variable, Konstante und Funktion muß genau einen Typ besitzen, in Zuweisungen und Vergleichsausdrücken müssen beide Seiten den gleichen Typ besitzen. Der Typ muß zur Compile-Zeit feststellbar sein. - Ein C-Programm besteht aus einer oder mehreren Funktionen, bei Start des Programms wird die Funktion mit Namen main aufgerufen. - Kommentare beginnen mit /* und werden mit */ beendet oder sie beginnen mit // und werden durch ein Zeilenende beendet Typen Ein Typ ist eine (endliche) Menge von Werten. Hat eine Variable den Typ T so heißt dies, das sie nur Werte aus der Menge T annehmen darf. Problem Basistypen - Integer: short int, int, long int, long long int (signed oder unsigned) Bsp: int i, long long int j; unsigned int k; - Character: char, unsigned char, signed char - Fließkomma: float, double, long double (Achtung bei Arithmetik!) - Aufzählung: enum <Name> { <Werte_Liste> } Bsp: enum Woche { Mo, Di, Mi, Do, Fr, Sa, So }; enum Woche frei = So; - Leer: void entspricht der leeren Menge Bsp: void test(int i); Zusammengesetzte Typen Finde Algorithmus zur Problemlösung Implementiere den Algorithmus Datenstrukturen und Operationen darauf Implementierung Testen Debugging - Zeiger: für einen Typ T ist T* vom Typ "Zeiger auf T" Bsp: int * k; - Felder: Menge von Werten gleichen Types, werden über Indizes referenziert Bsp: char name[20], test[] = {’a’,’b’}; Profiling/Optimierung Adresse x x+1 Vorstellen der Lösung x+2 x+3 x+4 i int i, *pi; pi C Tutorial © Mathias Waack <[email protected]> Page 3 C Tutorial © Mathias Waack <[email protected]> Einführung in die Programmiersprache C Page 4 Einführung in die Programmiersprache C Typen Ausdrücke Strukturierte Typen Ausdrücke bestehen aus einem Operanden oder aus Operanden die mit Operatoren verknüpft sind. Operanden sind Variablen, Konstanten, Funktionsaufrufe oder Ausdrücke. - Strukturen: Menge von Werten mit verschiedenen Typen, über Namen referenziert Bsp: struct bsp { int i; char k; }; - Unions: besitzt verschiedene Typen (aber nur einen zur Zeit) Bsp: union wert { int iwert; float fwert }; - Funktionstypen: <type> f() - f ist eine Funktion vom Typ <type> Bsp: float square(); Operator Assoziativität () [] -> . l ! ~ ++ -- + - * & (type) sizeof r */% l +- l Benannte Typen << >> l Typdeklarationen können benannt werden: < <= > >= l typedef <type> <bezeichner> == != l Bsp: & l typedef struct { int i; char k; } bsp; bsp a; a.i = 42; Typqualifizierer Sie schränken die Art und Weise des Zugriffs auf Variablen des Typs ein. Bsp: const int result = 42; volatile int buf_empty; Deklarationen und Definitionen Deklaration: legt die Bedeutung eines Bezeichners fest Definition: reserviert Speicherplatz für den Bezeichner Syntax: [<speicherklasse>] <typangabe> <deklarator> Speicherklassen: (auto), register, static, extern, inline Typangabe: Basistypen, strukturierte Typen oder Namen eines benannten Typs (und evtl einen Qualifizierer) Deklarator: Variablen: Name und optional ’*’ (Zeiger) und/oder ’[]’ (Felder) Funktionen: <typangabe> Bezeichner(<parameter_liste>) Eine Deklaration wird mit einem ’;’ abgeschlossen. Innerhalb von Deklarationen können Klammern benutzt werden, um die Lesbarkeit zu erhöhen. Deklarationen von Variablen sind oft besser verständlich wenn sie von rechts nach links gelesen werden (z.B. int *i[5] - ein Feld mit 5 Zeigern auf int) Beispiele: int i, *pi, a[10], *b[10]; double *g, h[10][10][10]; struct test { int i; double f } t[20], *f; ^ l | l && l || l ?: r = += *= /= -= %= &= ^= |= <<= >>= r , l Anweisungen Jede Funktion in C besteht aus einer Folge von Anweisungen. Anweisungen werden durch ’;’ abgeschlossen. Anweisungen werden streng sequentiell abgearbeitet (beachte: dies gilt nicht innerhalb von Anweisungen!). Einfache Anweisungen: leer, Ausdrücke, Block {} Schleifen: while (<ausdruck>) <anweisung> do <anweisung> while (<ausdruck>); for (<init_ausdruck>;<ausdruck>;<incr_ausdruck>) <ausdruck> Sprünge: break; continue; return [<ausdruck>]; Auswahlanweisungen: if (<ausdruck>) <anweisung; [else <anweisung>;] switch (<ausdruck>) { case <konst>: <anweisung> case <konst>: <anweisung> default: <anweisung> } C Tutorial © Mathias Waack <[email protected]> Page 5 C Tutorial © Mathias Waack <[email protected]> Einführung in die Programmiersprache C Anweisungen (Beispiele) Einführung in die Programmiersprache C Übersetzung Größtes Element in einem Feld finden: Das Kompilieren eines C-Programmes läuft in 3 Schritten ab: int size = <irgendein Wert> int a[size], i, max = -1, index; // initialisiere a mit positiven Werten 1. Version: while - Schleife i = 0; while (i<size) { if (a[i] > max) { max = a[i]; index = i; } i++; } 2. Version: for - Schleife Der Präprozessor nimmt syntaktische Veränderungen an der Quelldatei vor, Der C-Compiler übersetzt die Datei und erzeugt sogenannten Objektcode und Der Linker fügt den Objektcode mit den Code aus sog. Bibliotheken oder anderen Objektdateien zusammen und erzeugt eine ausführbare Datei. Anweisungen an den Präprozessor werden durch ein ’#’ in der 1. Spalte der jeweiligen Zeile kenntlich gemacht. Der C-Präprozessor kennt (u.a.) folgenden Anweisungen: include "filename" - ersetzt diese Zeile durch den Inhalt der Datei filename aus dem aktuellen Verzeichnis include <filename> - wie zuvor, sucht die Datei filename aber in best. Verzeichnissen for (i=0; i<size; i++) if (a[i] == search) break; Verschiedene Aktionen in Abhängigkeit von einem Wert ausführen: int i = <cmd>; switch (i) { case <akt0>: case <akt1>: case <akt2>: case <akt3>: default: <do } 1. 2. 3. Präprozessor for (i=0; i<size; i++) if (a[i] > max) { ... } Bestimmtes Element in einem Feld finden: int i = <cmd>; if (i == <akt0>) <do akt0>; else if (i==<akt1> || i==<akt2>) <do akt1.2>; else if (i==<akt3>) <do akt3>; else <do default akt>; Page 6 <do akt0>; break; <do akt1.2>; break; <do akt3>; break; default akt>; Increment-Funktion (nur für positive Werte) int incr(int i) { return (i>0) i+1 : i; } Achtung Falle: int i = 4; int f() { i = i+1; return i;}; int g() { i = i-1; return i;}; int main() { if ( f() || g() ) i = i+2; } Welchen Wert hat i vor der letzten Klammer ’}’ ? Oder: if ( f() < 5 && g() > 4 ) i = g(); Was passiert, wenn man statt && den Operator & nimmt (bzw. | statt ||) ? define name value - alle weiteren Vorkommen von name in der aktuellen Datei werden durch value ersetzt define name(arglist) ausdruck - wie zuvor aber parametrisiert ifdef name <text1> else <text2> endif - wenn name bekannt ist, wird text2 gelöscht, sonst text1 ifndef ... - das Gegenteil von ifdef Beispiel: #ifdef DOS #define DELIM ’\’ #else #define DELIM ’/’ #endif int main() { int a[size]; init(a); #ifdef DEBUG { int i; for (i=0;i<size;i++) printf("%d\n",a[i]); } #endif Praxis Der gcc fungiert als Frontend für Präprozessor, Compiler und Linker. Ein Aufruf von gcc file.c bewirkt das Ausführen aller 3 Schritte, als Ergebnis ensteht die ausführbare Datei a.out. Folgende Kommandozeilenparameter sind interessant: - E: gcc -E file.c ruft nur den Präprozessor auf - c: gcc -c file.c führt nur die ersten beiden Schritte aus, erzeugt die Objektdatei file.o - -o: gcc -o name file.c benennt die ausführbare Datei name - -Wall: überprüft den Code sehr peinlich, meldet viele eventuelle Fehlerstellen C Tutorial © Mathias Waack <[email protected]> Page 7 C Tutorial © Mathias Waack <[email protected]> Einführung in die Programmiersprache C Page 8 Einführung in die Programmiersprache C Zeiger und Felder Funktionen Eine Variable, die einen Feld-Typ hat, wird vom C-Compiler wie ein (konstanter) Zeiger auf das erste Element des Feldes behandelt: Parameter an Funktionen werden immer call-by-value übergeben, d.h. beim Aufruf werden die Werte der aktuellen Parameter berechnet und diese an die Funktion übergeben: int a[10], *b; b = a; Zeiger können mit dem Operator ’*’ dereferenziert werden: int i = 8; f(i); // void f(int) i == 8; // f kann i nicht ändern Soll die Funktion f die Variable i ändern können (call-by-reference), kann f ein Zeiger auf i übergeben werden: *b == a[0]; Umgekehrt liefert ’&’ einen Zeiger auf ein Objekt: b == &(a[0]); Zu Zeigern können Integer-Werte addiert/subtrahiert werden. Der Ausdruck (pointer + offset) inkrementiert pointer dabei um offset*sizeof(*pointer) Bytes: b + 3 == a[3] == a+3; Tatsächlich wird jeder Ausdruck der Form x[y] vom Compiler in x+y umgewandelt, bevor er übersetzt wird: b + 3 == b[3] == 3[b]; Achtung: Zeiger und Felder sind trotzdem nicht dasselbe: b = &a[7]; a = b; // das geht nicht!!!! sizeof(b); // Größe eines Zeigers sizeof(a) == 10*sizeof(int); f(&i); // void f(int *) i == ?; // f kann i geändert haben Der Rückgabewert von Funktionen kann ignoriert werden: int f(int); f(27); Zeiger auf Funktionen: int f(int i) { return 2*i; } void g() { int (*a)(int), x; a = f; x = a(2); } Beachte, daß Felder wie Zeiger behandelt werden und sie daher an Funktionen quasi by-reference übergeben werden! Zeichenketten Die main-Funktion In C gibt es keinen Datentyp für Zeichenketten. Sie werden als Felder vom Typ char dargestellt mit einer 0 (nicht Zeichen ’0’ sondern der Wert 0) als letztes Zeichen: Jedes C-Programm besitzt eine Funktion mit Namen main, diese wird beim Start des Programms aufgerufen: char s[10]; // s kann 9 Zeichen aufnehmen + ’\0’ char u[] = "Hallo";// u ist 6 Byte gross char *t = "Hallo"; // Wie gross ist t? char *v[10], **w = v; // geht das? In "" werden Zeichenketten-Konstanten eingeschlossen. Somit ist t ein Zeiger auf eine Konstante! Dagegen ist u ein Array von Zeichen die mit Konstanten initialisiert wurden. int main(int argc, char *argv[]); argc ist die Anzahl der Kommandozeilenparameter, die das Programm beim Aufruf erhalten hat (einschließlich des Programmnamens), argv ist ein Feld das diese Parameter enthält. Der Rückgabewert der main-Funktion wird an das aufrufende Programm (meist das Betriebssystem) zurückgegeben. *t = ’G’; // verboten!!! warum? u[0] = ’G’; // ok Bestimmung der Länge eines Strings: int strlen(char *s) { int len; for (len=0;s[len];len++); return len; } more text.txt datei.dat In der main-Funktion des Programmes more gilt dann: argc == 3; argv[0] === "more"; argv[1] === "text.txt"; argv[2] === "datei.dat" Es ist Konvention, daß der Rückgabewert eines Programms ein Indiz für seine erfolgreiche Ausführung ist. D.h. jedes Programm sollte den Wert 0 liefern, wenn es "normal" beendet wurde, und einen Fehlercode != 0 wenn es infolge eines Fehlers beendet wurde. C Tutorial © Mathias Waack <[email protected]> Page 9 C Tutorial © Mathias Waack <[email protected]> Einführung in die Programmiersprache C Libraries Für viele Standardprobleme gibt es bereits fertige Lösungen, diese werden in Form von Libraries dem Programmierer zur Verfügung gestellt. Zu jedem C-Compiler gehört eine (standardisierte) C-Library. Diese Libraries liegen schon kompiliert vor und werden nur noch zum Programm "gelinkt". Um die dort enthaltenen Funktionen, Datenstrukturen und Variablen dem Compiler gekannt zu machen, werde diese in sogenannten Headerfiles deklariert (und nicht definiert!). Diese Headerfiles werden mittels der #include-Anweisung des Präprozessors in das Programm eingefügt. #include <math.h> int main() { double g = exp(7.0); } Die Funktion double exp(double) ist in der Datei math.h deklariert und in der Library libm.a definiert. Um obiges Programm kompilieren zu können, muß also diese Library zum Programm gelinkt werden: gcc test.c <path>/libm.a Einfacher geht es mit der Compileroption -l: gcc test.c -lm Für die Funktionen der C-Library existieren man-pages denen man entnehmen kann was diese Funktionen bewirken, in welchen Libraries sie definiert und in welchen Headerfiles sie deklariert sind. Dynamischer Speicher Einführung in die Programmiersprache C Dynamische Datenstrukturen Felder und Strukturen bieten eine Möglichkeit, mehrere Datenelemente zu vereinigen. Allerdings wird deren Anzahl einmal festgelegt und ist anschließend nicht mehr änderbar. Damit lassen sich Strukturen wie verkettete Listen nicht implementieren. In einer Liste enthält jedes Element einen Verweis auf seinen Nachfolger: struct listentry { <irgendwelche Daten>; struct listentry *next; }; Das Element next der Struktur enthält also einen Zeiger auf das nächste Element der Liste. Die Deklaration von next ist möglich, da der Compiler den Typ "struct listentry" zu diesem Zeitpunkt schon kennt (wenn auch noch nicht komplett). struct gehtsonicht { struct gehtsonicht next; }; Der Struktur listentry kann man auch einen Namen geben: free(p); p[7]; // ist jetzt verboten!!! malloc/free werden häufig im Zusammenhang mit dynamischen Datenstrukturen (Listen, Bäumen usw.) benutzt. // so nicht!!! typedef struct listentry liste_t; Jetzt kann man eine Variable top anlegen, die auf den Anfang der Liste verweist: liste_t *top = NULL; Neue Listenelement werden mit malloc erzeugt: liste_t *neuer = malloc(sizeof(liste_t)); Dieses Element kann jetzt am Anfang der Liste eingefügt werden: neuer->next = top; top = neuer; // (*neuer).next In einer Schleife kann die Liste relativ einfach wieder gelöscht werden: Die Funktion void *malloc(size_t) erlaubt es, zur Laufzeit eines Programmes Speicherplatz anzufordern. Sie liefert einen Zeiger auf diesen Speicher zurück. Konnte kein Speicher alloziiert werden, liefert sie einen Zeiger mit dem speziellen Wert NULL. Durch Vergleich mit diesem Wert kann festgestellt werden, ob die Anforderung erfolgreich war. Um z.B. Platz für 10 int-Werte anzufordern, kann man folgenden Code benutzen: int *p; p = (int *)malloc(sizeof(int)*10); if (p == NULL) <irgendeine Fehlerbehandlung> else p[7] = 42; // wie bei statischen Feldern Mit malloc angeforderter Speicher kann mit der Funktion void free(void *) wieder freigegeben werden: Page 10 list_t *tmp; while (top) { tmp = top; top = top->next; free(tmp); } top Daten next Daten next Daten next NULL C Tutorial © Mathias Waack <[email protected]> Page 11 C Tutorial © Mathias Waack <[email protected]> Einführung in die Programmiersprache C Page 12 Einführung in die Programmiersprache C Seiteneffekte Ausgewählte Funktionen der Standard-Library - Zugriffe auf volatile Objekte - verändern eines Objektes - verändern einer Datei - Aufruf einer Funktion, die eine der obigen Aktionen ausführt An sog. sequence points wird garantiert, daß alle Seiteneffekte vorhergehender Anweisungen abgeschlossen sind. Die Auswertungsreihenfolge zwischen zwei sequence points ist nicht definiert! Alle Ausdrücke, deren Wert von der Reihenfolge der Auswertung abhängt, sind nicht erlaubt (undefined behaviour). Dateizugriff (stdio.h) Beispiele für solche Ausdrücke: int a[10], b, i=0; a[i] = i++; // a[0] oder a[1] ? i=0; b = i++ * i++; // b = 0 o. 1 o. 2? b = f(&i) + g(&i); // wird i in f/g modifiziert? Ausdrücke vermeiden , deren Bedeutung nicht beim ersten Hinschauen klar wird! Ausgewählte Funktionen der Standard-Library Eingabe: - getchar liest ein Zeichen ein, gets(char *s) liest einen String - scanf("<formatstring>",arg1,...) formatierte Eingabe Flag Typ Matching d int * Integer Zahl c char * ein Zeichen f float * reelle Zahl x int * Hexadezimal-Zahl s char * String (Achtung: muß groß genug sein!) - putchar gibt ein Zeichen aus, puts(char *) gibt einen String aus - printf("<formatstring>",arg1,...) formatierte Ausgabe Typ Matching d int Integer Zahl x int Hexadezimal-Zahl c char ein Zeichen f double reelle Zahl s char * String Mode Bedeutung r zum Lesen öffnen w zum Schreiben öffnen (Inhalt wird vorher gelöscht o. Datei neu erzeugt) a zum Schreiben ab Dateiende öffnen (append) r+ wie "r", zusätzlich auch Schreiben möglich w+ wie "w", zusätzlich auch Lesen möglich a+ wie "a", zusätzlich Lesen (ab Anfang) möglich - int fclose(FILE *) schließt eine Datei - int fflush(FILE *) "flusht" den Puffer (bei Ausgabe), nicht für Eingabe verwenden! - fprintf(FILE *,const char *,...) wie printf, nur Ausgabe in Datei - fscanf(FILE *,const char *,...) wie scanf, nur Eingabe von Datei - int feof(FILE *) liefert EOF wenn Dateiende erreicht - size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE *stream) schreibt max. n Items von jeweils size Bytes ab Adresse ptr in die Datei stream, liefert die Anzahl der geschriebenen Zeichen - size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream) liest max. n Items von jeweils size Bytes aus der Datei stream und schreibt sie in den Speicher ab Adresse ptr, liefert die Anzahl der gelesenen Zeichen Fehlerbehandlung Tritt in einer Funktion der C-Library ein Fehler auf, so wird errno gesetzt. Die Datei errno.h enthält Konstanten die angeben, welcher Fehler aufgetreten ist. Ausgabe: Flag - FILE *fopen(const char *filename, const char *mode) - void perror(const char *) gibt eine Fehlermeldung entsprechend dem Wert errno aus FILE *bsp = fopen("dateiname","r"); if (bsp == NULL) // also Fehler in fopen -> errno != 0 perror("dateiname"); C Tutorial © Mathias Waack <[email protected]> Page 13 Einführung in die Programmiersprache C C Tutorial © Mathias Waack <[email protected]> Einführung in die Programmiersprache C Ausgewählte Funktionen der Standard-Library Ausgewählte Funktionen der Standard-Library (Beispiele) Strings (string.h) Zeilenweiser Vergleich von 2 Dateien (ohne Fehlertests!) - char *strcpy(char *s1, const char *s2) kopiert s2 nach s1 - char *strncpy(char *s1, const char *s2, size_t n) wie strcpy, kopiert max. n Zeichen - char *strcat(char *s1, const *char *s2) kopiert s2 an das Ende von s1 - int strcmp(const char *s1, const char *s2) - int strncmp(const char *s1, const char *s2, size_t n) vergleicht max. n Zeichen - char *strchr(const char *s, int c) liefert Zeiger auf das erste Vorkommen von c in s - size_t strlen(const char *s) - void *memset(void *s, int c, size_t n) setzt den Speicher ab s auf den Wert c für n Zeichen - void *memcpy(void *s1,const void *s2, size_t n) kopiert n Zeichen (char’s) ab s2 nach s1 - int memcmp(const void *s1,const *void *s2,size_t n) vergleicht den Speicher ab s1 bzw. s2 n Zeichen lang, liefert einen Wert kleiner, gleich oder größer 0 wenn s1 kleiner, gleich oder größer als s2 ist Die Speicherbereiche bei den Kopierfunktionen dürfen sich nicht überlappen! #include <stdio.h> void ungleich(int i) { fprintf(stderr,"Ungleichheit in Zeile %d\n",i); exit(1); } int main(int argc, char *argv[]) { FILE *in0 = fopen(argv[1],"r"); FILE *in1 = fopen(argv[2],"r"); char buf0[255], buf1[255]; int lineno = 1; while (!feof(in0) && !feof(in1)) { fgets(buf0,255,in0); fgets(buf1,255,in1); if (strcmp(buf0,buf1)) ungleich(lineno); lineno++; } if (!feof(in0) || !feof(in1)) ungleich(lineno); return 0; } Einlesen von Zahlen bis 0 eingegeben wird, dann Summe ausgeben: double d, res = 0; do { scanf("%f",&d); res += d; } while (d != 0); printf("Summe:\t%lf\n",res); Mögliche Implementierung von fgets(): char *fgets(char buf[], int size, FILE *f) { char c; int pos = 0; while ((c=getc(f))!=EOF && c!=’\n’ && pos<size-1) buf[pos++] = c; buf[pos] = ’\0’; return buf; } Ein (schlechter) cp-Ersatz: int main(int argc,char *argv[]) { FILE *in = fopen(argv[1],"r"); FILE *out = fopen(argv[2],"w"); char c; while ((c = getc(in))!=EOF) putc(c,out); fclose(in); fclose(out); } Lesen/Schreiben binärer Daten: struct bsp { <irgendwas> } struct bsp a; fwrite(&a,sizeof(struct bsp),1,outstream); // Schreiben struct bsp *b = malloc(sizeof(struct bsp)); fread(b,sizeof(struct bsp),1,instream); // Lesen Page 14 C Tutorial © Mathias Waack <[email protected]> Page 15 C Tutorial © Mathias Waack <[email protected]> Einführung in die Programmiersprache C Page 16 Einführung in die Programmiersprache C Ausgewählte Funktionen der Standard-Library Profiling Mathematik Sammeln von Informationen über den Programmablauf - sin, cos, tan, asin, acos, atan, exp, log, log10, pow, sqrt, sinh, ... - round, trunc Beachte, daß floating point Arithmetik aufgrund endlicher Genauigkeit nicht exakt ist. Unendliche Brüche können nicht exakt dargestellt werden. Bereits einfache Dezimalbrüche (z.B. 1/10) besitzen keine endliche Binärbruchrepräsentation. - Häufigkeit von Funktionsaufrufen - Dauer von Funktionsaufrufen Auswerten der Informationen um das Laufzeitverhalten zu verbessern Floating point Werte sollen nie direkt auf (Un-)Gleichheit geprüft werden, d.h. statt - Eleminieren von toten Code - Ansatzpunkte für Optimierungen finden Übersetzen des Codes mit der Compileroption -pg if (x == y) ... schreibt man besser gcc -pg test.c Programmlauf erzeugt eine Datei "gmon.out" welche Statistiken enthält. if (fabs(x-y) < ε) ... Beachte, daß bei floating point Werten das Ergebnis eines Ausdruckes stark von der Auswertungsreihenfolge abhängen kann (Auslöschung) : (1113. + -1111.) + 7.511 = 2.000 + 7.511 = 9.511 1113. + (-1111. + 7.511) = 1113. + -1103 = 10.00 Programm gprof arbeitet diese Statistiken auf und bietet sie in lesbarer Form an - Liste der aufgerufenen Funktionen mit Timing-Statistiken - Call-Graph: für jede Funktion eine Liste aller aufgerufenen Funktionen self us/call total us/call cumulative seconds self seconds 44.44 0.08 0.08 16.67 0.11 0.03 125440 11.11 0.13 0.02 30001 0.67 11.11 0.15 0.02 1 20000.00 Zeitmessung 5.56 0.16 0.01 10000 1.00 - time_t time(time_t *) aktuelle Zeit (seit 1. Jan 1970, 0 Uhr) - clock_t times(struct tms *buf) speichert Zeitinformationen über den Prozeß in buf - int clock_gettime(clockid_t clock_id, struct timespec *tp) - int clock_getres(clockid_t clock_id, struct timespec *res) Die beiden letztgenannten Funktionen sind nicht Bestandteil des C-Standards. 5.56 0.17 0.01 3856 2.59 5.56 0.18 0.01 1 10000.00 100000.00 main 0.00 0.18 0.00 1 0.00 60000.67 qsort % time Parsing - int atoi(char *), long atol(char *) - double atof(char *) - sscanf, sprintf funktionieren wie fscanf, fprintf nur mit Strings statt mit Files calls name internal_mcount 0.24 0.24 compare 0.67 .umul 57607.94 qst 3.00 _drand48_u 2.59 .div Nach der Optimierung 57.14 0.08 0.08 7.14 0.11 0.01 125440 0.08 internal_mcount 0.00 0.14 0.00 1 0.00 0.08 compare 59998.00 main C Tutorial © Mathias Waack <[email protected]> Page 17 C Tutorial © Mathias Waack <[email protected]> Einführung in die Programmiersprache C Debugging (I) Page 18 Einführung in die Programmiersprache C Debugging (II) Zusicherungen (assertions): #include <assert.h> liste_t *ptr; ... // hier wird mit ptr gearbeitet assert(ptr != NULL); // ptr sollte != NULL sein assert ist keine Funktion sondern ein Makro. assert-Anweisungen können aus dem Code entfernt werden, indem das Makro NDEBUG definiert wird: #define NDEBUG // ab hier assert’s ignorieren Ebenso kann man in #ifdef’s gekapselte Ausgabeanweisungen in den Code einfügen: #ifndef NDEBUG printf("Bis hierher ging gut\n"); #endif Meistens werden solche Logging-Funktionen hinter Makros versteckt: #ifdef DEBUG #define LOG(x) puts(x) #else #define LOG(x) #endif Kommandozeilenoption -g für den gcc erzeugt Code mit Debugging-Information Debugger: gdb - textorientiert wichtige Kommandos: - help: Liste der Kommandos, "help <cmd>" liefert Hilfe zum Kommando <cmd> - list: "list <fkt>" listet 10 Zeilen um die Funktion <fkt>, "list" listet nächsten 10 Zeilen - break: "break <fkt>" setzt Breakpoint an Anfang von <fkt>, "break <lineno>" in Zeile - print: "print <var>" zeigt den Wert der Variablen <var> an - next: führt die aktuelle Programmzeile aus (überspringt Funktionsaufrufe) - step: wie next, aber springt in Funktionen hinein - bt: zeigt den aktuellen Aufrufstack - up, down: bewegt im Aufrufstack eine Ebene höher/tiefer - run: "run <arg1> ..." startet das Programm mit den Argumenten <arg1>... - quit: beendet gdb gdb kann auch benutzt werden, um core-File zu analysieren: "gdb --core core a.out" lädt das Corefile core welches vom Programm a.out erzeugt wurde. Mit dem Kommando bt erfährt man den Aufrufstack zum Zeitpunkt des Programmendes, mit up wechselt man in die letzte eigene Funktion und schaut sich die Variablen mit print an. (gdb) break main Breakpoint 1 at 0x103e8: file td.c, line 4. (gdb) run Starting program: /tmp/a.out Breakpoint 1, main () at td.c:4 4 int i = 0; (gdb) next 6 i = random(); (gdb) print i $1 = 0 (gdb) next 8 return 0; (gdb) print i $2 = 2078917053 (gdb) next 9 } (gdb) next 0x10370 in .nope () (gdb) next Single stepping until exit from function .nope, which has no line number information. Program exited normally. (gdb) int main() { int i = 0; i = random(); return 0; } C Tutorial © Mathias Waack <[email protected]> Page 19 C Tutorial © Mathias Waack <[email protected]> Einführung in die Programmiersprache C Page 20 Einführung in die Programmiersprache C Speicherklassen Größere Projekte - automatisch: (keine Speicherklasse angegeben), existieren lokal in einem Block - statisch: (in einem Block: static, außerhalb von Blöcken immer statisch), existieren auch außerhalb des umgebenen Blockes In größeren Projekten wird der Quelltext meist auf mehrer Dateien verteilt. Diese Dateien werden einzeln kompiliert und dann gelinkt. So muß bei Änderungen in einer Datei nicht der gesamte Sourcecode neu übersetzt werden. int i =0; static int k= 0; void func(void) { static int called = 0; int j = 0; j++; called++; i++; k++; ... } int main() { func(); func(); func(); } In jeder Datei befinden sich Funktionen und Variablen, die einer logischen Klasse zuzuordnen sind. Deklarationen und Typdefinitionen werden in Headerfiles verlagert. Beispiel: Rechner für UPN: Aufteilung: Stack für die Operanden, Rechenoperationen --> stack.c, calc.c, calc.h, außerdem Funktionen für Ein- und Ausgabe io.c, io.h stack.h: Gültigkeitsbereiche void push(int); int pop(void); int empty(); ~ eines Namens ist der Teil des Programms, in dem der Name benutzt werden darf. calc.h: Eine Variable heißt extern, wenn sie außerhalb eines Blockes definiert ist (Funktionen sind immer extern). void add(void); // und sub, mult, div Der Gültigkeitsbereich externer Variablen und Funktion reicht vom Punkt ihrer Deklaration bis zum Ende der Datei. Sollen externe Variablen vor ihrer Definition benutzt werden, so werden sie als extern deklariert. int read_stat(void); extern double i; void func() { i+= 1.0; } double i=0; int main() { func(); } // Deklaration, keine Definition // geht, da Name bekannt // Definition von i Externe Variablen dürfen nur genau einmal definiert und initialisiert werden. Werden externe Variablen oder Funktionen als static definiert, begrenzt das ihren Gültigkeitsbereich auf diese Datei. static int bsp = 0; // extern, aber in anderen Dateien nicht sichtbar! int main() { ... // hier ist bsp sichtbar } void func() { .... // hier ist bsp sichtbar } stack.h, io.h: stack.c: #include "stack.h" static int stack[256]; static int stack_ptr = 0; // Definition der Funktionen calc.c: #include "stack.h" // hier muss auf den Stack zugegriffen werden void add() { int op0, op1; if (!empty()) op0 = pop(); else <error> if (!empty()) op1 = pop(); else <error> push(op0 + op1); } // und dann sub, div, mult io.c: #include "calc.h" int read_stat() { ... ; return is_eof; } Durch die #include’s wird die Konsistenz der Datenstrukturen sichergestellt. Headerfiles werden häufig durch #ifdef’s gegen mehrfaches includen geschützt. Headerfiles sollten nur Deklarationen und keine(!) Definitionen enthalten. C Tutorial © Mathias Waack <[email protected]> Page 21 C Tutorial © Mathias Waack <[email protected]> Einführung in die Programmiersprache C Einführung in die Programmiersprache C Make Prozesse und Interprozesskommunikation (I) Werkzeug das dazu dient, automatisch Dateien, welche von anderen Dateien abhängen, neu zu generieren, wenn sich diese anderen Dateien ändern. pid_t - Datentyp zur Aufnahme von Prozessid’s (sys/types.h) In einem Makefile wird beschrieben, welche Beziehungen zwischen den Dateien bestehen und wie diese "upgedated" werden. Dies geschieht durch Regeln der Form: pid_t getpid(): PID (process ID) des aufrufenden Prozesses <target>: <depends on sources> TAB<command for update> Erzeugen eines Prozesses (unistd.h) Bsp.: io.o: io.c io.h calc.h gcc -c io.c calculator: main.o io.o calc.o stack.o gcc -o calculator main.o io.o calc.o stack.o In Makefiles können auch Variablen deklariert werden: CFLAGS = -c -g -Wall -DDEBUG #CFLAGS = -c -O2 -DNDEBUG io.o: io.c io.h calc.h gcc $(CFLAGS) io.c make sucht im Makefile nach der ersten Regel. In dieser Regel untersucht er alle Sourcen daraufhin, ob für sie irgendwelche Abhängigkeiten bestehen, falls ja wird diese Source neu generiert. Anschließend wird die Regel für das Target ausgeführt. Beim Aufruf kann make der Name eines Target angegeben werden, z.B. "make io.o" würde im o.g. Makefile nur die Datei io.o (evtl.) neu generieren. Mit "make -n" kann man sich anzeigen lassen, welche Aktionen make ausführen würde, ohne daß diese wirklich ausgeführt würden. Page 22 Prozessidentifikation (unistd.h) pid_t getppid(): PID des Vaterprozesses pid_t fork() fork erzeugt eine Kopie des aktuellen Prozesses, liefert 0 im Kindprozeß, die ID des Kindes im Vaterprozeß. Der Kindprozeß unterscheidet sich vom Vater u.a. in folgendem: - andere PID, andere PPID (parent PID) - Reaktion auf Signale ist anders sein Beispiel: if (fork() == 0) { <the child code> } else { <the parent code> } Prozeßstart (unistd.h) int int int int execl(const char *path, const char *arg0, ..., char * /* NULL */ ) execv(const char *path, char *const argv[]) execlp(const char *file, const char *arg0, ..., char * /* NULL */) execvp(const char *file, char *const argv[]); Beispiel Drucken im Hintergrund: void print_bg(char *filename) { if (fork() == 0) { execl("/bin/lp",filename,NULL); perror("exec failed"); // dieser Code wird normalerweise nicht ausgeführt! exit(0); } } Warten auf einen (Kind-)Prozeß (sys/wait.h) pid_t wait(int *stat) pid_t waitpid(pid_t pid, int *stat, int options) stat enthält Information über den Prozeß, auf den gewartet wurde: WIFEXITED(stat) !=0 falls der Kindprozeß normal beendet wurde WEXITSTATUS(stat) exit-Code des Kindprozesses WIFSIGNALED(stat) !=0 falls der Kindprozeß infolge eines Signal terminierte WTERMSIG(stat) Nummer des Signals, falls WIFSIGNALED(stat)!=0 (siehe wstat(5) ). C Tutorial © Mathias Waack <[email protected]> Page 23 C Tutorial © Mathias Waack <[email protected]> Einführung in die Programmiersprache C Einführung in die Programmiersprache C Prozesse und Interprozesskommunikation (II) Prozesse und Interprozesskommunikation (III) Signals - asynchrone Ereignisse (sys/types.h, signal.h) int kill(pid_t pid, int sig) Mögliche Reaktion auf Empfangen eines Signals: Mechanismen: - Führe die Default-Aktion für dieses Signal durch (Exit, Core, Stop, Ignore) - Ignoriere das Signal - Empfange und verarbeite das Signal Signalhändler installieren (signal.h) void (*signal (int sig, void (*disp) (int))) (int) void (*sigset (int sig, void (*disp) (int))) (int) Mit signal installierte Signalhändler werden nach dem ersten Aufruf entfernt, mit sigset gesetzte Händler bleiben erhalten. #include #include #include #include <stdio.h> <signal.h> <unistd.h> <stdlib.h> void sig_handler(int sig) { printf("\nsignal %d received\n",sig); } int main() { int i; - Signals - (unnamed) Pipes - FIFO (named Pipes) - Semaphores - shared memory - message queues - shared filesystem, lock files Named Pipes - ähnlich wie Dateien (sys/types.h, sys/stat.h) int mkfifo(const char *path, mode_t mode) mit mode werden die Zugriffsrechte gesetzt (wie bei "normalen" Dateien) #include #include #include #include #include int main() { char *tmpfilename = tmpnam(NULL); char buf[256]; int rdes,wdes; pid_t pid; if (signal(SIGINT, sig_handler) == SIG_ERR) // oder sigset { perror("SIGINT"); exit(1); } if (mkfifo(tmpfilename,0644)) exit(1); pid = fork(); if (pid > 0) { // parent if ((wdes = open(tmpfilename,O_WRONLY | O_NDELAY))==-1) exit(1); puts("sender is ready"); for (;;) { gets(buf); printf("process %d sends msg [%s] to fifo\n",getpid(),buf); write(wdes,buf,256); } } else if (pid == 0) { // child if ((rdes = open(tmpfilename,O_RDONLY))==-1) exit(1); puts("receiver is ready"); for (;;) { read(rdes,buf,256); printf("process %d got msg [%s] from fifo\n",getpid(),buf); sleep(1); } } else { puts("fork failed"); exit(1); } return 0; for (i=0; ;i++) { printf("%d\n",i); sleep(1); } } verwendet signal: verwendet sigset: mathias@ed a.out 0 1 ^C signal 2 received 2 ^C mathias@ed mathias@ed a.out 0 ^C signal 2 received 1 ^C signal 2 received 2 ^C signal 2 received 3 <stdio.h> <sys/types.h> <sys/stat.h> <unistd.h> <fcntl.h> } Page 24 C Tutorial © Mathias Waack <[email protected]> Page 25 C Tutorial © Mathias Waack <[email protected]> Einführung in die Programmiersprache C Page 26 Einführung in die Programmiersprache C Sockets (II) Sockets (I) Kommunikation über Rechnergrenzen, Client-Server-Paradigma SERVER create a socket socket( ... ) CLIENT create a socket socket( ... ) Akzeptieren einer Verbindung (sys/types.h, sys/socket.h) int accept(int socket, struct sockaddr *addr, int *addrlen) blockiert wenn keine Verbindungen in der Queue sind addr enthält die Adreßinformationen des Clients, addrlen die Länge der Struktur Erstellen einer Verbindung auf Client-Seite (sys/types.h, sys/socket.h) int connect(int socket, struct sockaddr *name, int namelength) #include #include #include #include #include #include #include #include #include #include assign a name to the socket bind( ... ) establish a queue for connections listen( ... ) <stdio.h> <string.h> <ctype.h> <stdlib.h> <unistd.h> <sys/types.h> <sys/socket.h> <netdb.h> <netinet/in.h> <arpa/inet.h> #define ERROR(x) { perror(x); exit(1); } short PORT = 7777; static char buf[BUFSIZ]; extract a connection from the queue accept( ... ) established initiate a connection connect( ... ) communicate read( ... ) write( ... ) communicate write( ... ) read( ... ) int main(int argc, char *argv[]) { int orig_sock, new_sock, clnt_len; struct sockaddr_in clnt_adr, serv_adr; int len; if (argc == 2) PORT += atoi(argv[1]); if ((orig_sock = socket(AF_INET,SOCK_STREAM, 0))<0) ERROR("socket"); memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); serv_adr.sin_port = htons(PORT); if (bind(orig_sock,(struct sockaddr *)&serv_adr,sizeof(serv_adr))<0) ERROR("bind"); if (listen(orig_sock,5) <0) ERROR("listen"); Socket erstellen (sys/types.h, sys/socket.h) int socket(int family, int type, int protocol) family == PF_INET, type == SOCK_STREAM, protocol == 0 dem Socket einen Namen zuweisen (sys/types.h, sys/socket.h) int bind(int socket, const struct sockaddr *name, int namelen) clnt_len = sizeof(clnt_adr); if ((new_sock = accept(orig_sock,(struct sockaddr *)&clnt_adr, &clnt_len)) < 0) ERROR("accept"); do { memset(buf,0,BUFSIZ); len = read(new_sock, buf, BUFSIZ); if (!strncmp("quit",buf,4)) { close(new_sock); close(orig_sock); exit(0); } printf("got msg: %s\n",buf); } while(1); struct sockaddr { u_short sa_family, char sa_data[14]; } struct sockaddr_in { short sin_family; // == AF_INET u_short sin_port; // 16Bit port-nr > 1024 struct in_addr sin_addr; // ref to address structure char sin_zero[8]; // for future extension } Erstellen der Queue für die Verbindungen (sys/types.h, sys/socket.h) int listen(int socket, int backlog) backlog gibt die max. Größe der Queue an } C Tutorial © Mathias Waack <[email protected]> Page 27 C Tutorial © Mathias Waack <[email protected]> Einführung in die Programmiersprache C Page 28 Einführung in die Programmiersprache C Debugging (III) Posix-Threads (I) Debugger mit grafischer Oberfläche Klassiker: xxgdb ~ sind unabhängige Befehlssequenzen innerhalb eines Prozesses ~ teilen sich alle Resourcen (Speicher, Filedeskriptoren,...) eines Prozesses moderner: ddd Vorteile: - Fenster für aktuellen Sourcecode, - grafische Darstellung von Datenstrukturen - erhöhter Durchsatz, - Ausnutzung von Shared-Memory-Systemen mit mehreren Prozessoren, - vermeiden von Deadlocks ... Deklarationen in pthread.h Erzeugen eines Threads: int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_fkt)(void *), void *arg); thread liefert die Thread-ID des gestarteten Threads, attr enthält Attribute des Threads (wenn NULL, werden Default-Werte angenommen) Beenden eines Threads: void pthread_exit(void *status) status kann von einen wartenden Thread verarbeitet werden Ein return aus der Startroutine beendet Threads ebenso Warten auf die Beendigung eines Threads: int pthread_join(pthread_t thread, void **status); Weitere Funktionen: pthread_t pthread_self(); // liefert eigene ID int pthread_equal(pthread_t t1,pthread_t t2); // vergleicht zwei Threads Synchronisation Zugriff auf knappe Resourcen, atomare Operationen, gegenseitiger Ausschluß Bsp. Producer/Consumer: #define next(x) (( (x) + 1) % BUF_SIZE; data buf[BUF_SIZE]; int rdptr = 0, wrptr = 0; Producer Consumer while (work_to_do) { buf[wrptr] = produce(); while (next(wrptr)==rdptr) ; wrptr = next(wrptr); } while (work_to_do) { while (wrptr == rdptr) ; consume(buf[rdptr]); rdptr = next(rdptr); } Problem: Consumer sieht schon den "neuen" wrptr aber noch die alten Daten (weakly ordered Speicherzugriffe) --> Synchronisation notwendig C Tutorial © Mathias Waack <[email protected]> Page 29 Einführung in die Programmiersprache C C Tutorial © Mathias Waack <[email protected]> Einführung in die Programmiersprache C Posix-Threads (II) Posix-Threads (III) Mutex - garantiert exclusive Ausführung kritischer Code-Sequenzen Condition Variable - warten auf Ereignisse pthread_mutex_t lock; pthread_cond_t cond; Initialisierung: Initialisierung: int pthread_mutex_init(pthread_mutex_t *mp, const pthread_mutexattr_t *attr); oder statisch: pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER löschen eines Mutex: int pthread_mutex_destroy(pthread_mutex_t *mp); Lock/Unlock: int pthread_mutex_lock(pthread_mutex_t *mp); int pthread_mutex_unlock(pthread_mutex_t *mp); Ein Thread welcher einen Mutex lockt, ist sein Besitzer. Nur der Besitzer kann einen Mutex "unlocken". Bsp. Consumer/Producer mit Mutexen: pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER Page 30 int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); oder statisch: pthread_cond_t cond = PTHREAD_COND_INITIALIZER; löschen einer Condition-Variable: int pthread_cond_destroy(pthread_cond_t *cond); Warten auf ein Ereignis: int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); blockiert den aufrufenden Thread bis ein anderer Thread das Eintreten des Ereignisses cond signalisiert. Der Mutex mutex wird unlocked bei Aufruf von pthread_cond_wait und wieder gelockt, bevor die Funktion beendet wird. Producer Consumer int pthread_cond_signal(pthread_cond_t *cond); Condition-Variablen vermeiden ein busy-wait: while (work_to_do) { pthread_mutex_lock(&mutex); buf[wrptr] = produce(); while (next(wrptr)==rdptr) ; wrptr = next(wrptr); pthread_mutex_unlock(&mutex); } while (work_to_do) { pthread_mutex_lock(&mutex); while (wrptr == rdptr) ; consume(buf[rdptr]); rdptr = next(rdptr); pthread_mutex_unlock(&mutex); } for(;;) { pthread_mutex_lock(mutex); if (cond) break; pthread_mutex_unlock(mutex); } <hier ist die Bedingung erfüllt> pthread_mutex_unlock(mutex); Nachteile: verbraucht sehr viel Resourcen, verlangt Programmierdisziplin Version mit Condition-Variablen: Vorsicht bei der Verwendung automatischer Variablen als Argumente bzw. Ergebnisse von Threads: pthread_t t; void *g(int *i) { int res; /* berechnet irgendwas, benutzt dabei *i, Eregebnis in res */ return &res; // das geht nicht gut! Warum? } void f() { int a = 7; pthread_create(&t, NULL, g, &a); } // auch das geht nicht gut! Warum? pthread_mutex_lock(mutex); pthread_cond_wait(cond,mutex); <hier ist die Bedingung erfüllt> pthread_mutex_unlock(mutex); Es ist möglich, daß ein pthread_cond_signal mehr als einen Thread aufweckt, ebenso kann ein pthread_cond_wait scheitern. Daher sollte nach dem Aufruf von pthread_cond_wait getestet werden, ob die Bedingung auch wirklich erfüllt ist (siehe Bsp. ) C Tutorial © Mathias Waack <[email protected]> Page 31 C Tutorial © Mathias Waack <[email protected]> Einführung in die Programmiersprache C Einführung in die Programmiersprache C Posix-Threads (IV) Posix-Threads (V) Beispiel: FIFO-Queue für Datenpointer. Es soll für beliebig viele Thread möglich sein, Pointer in das FIFO einzufügen bzw. zu entnehmen. void put_cb_data(circ_buf_t *cbp, void *data) { pthread_mutex_lock(&cbp->buf_lock); /* wait while the buffer is full */ while (cbp->num_full == QSIZE) pthread_cond_wait(&cbp->notfull, &cbp->buf_lock); cbp->data[(cbp->start_idx + cbp->num_full) % QSIZE] = data; cbp->num_full += 1; pthread_cond_signal(&cbp->notempty); pthread_mutex_unlock(&cbp->buf_lock); } #include <unistd.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define QSIZE 10 typedef struct { pthread_mutex_t buf_lock; int start_idx; int num_full; pthread_cond_t notfull; pthread_cond_t notempty; void *data[QSIZE]; } circ_buf_t; /* number of pointers in the queue */ /* /* /* /* /* /* lock the structure */ start of valid data */ # of full locations */ full -> not full condition */ empty -> notempty condition */ Circular buffer of pointers */ void *get_cb_data(circ_buf_t *cbp) { void *data; pthread_mutex_lock(&cbp->buf_lock); /* wait while there's nothing in the buffer */ while (cbp->num_full == 0) pthread_cond_wait(&cbp->notempty, &cbp->buf_lock); data = cbp->data[cbp->start_idx]; cbp->start_idx = (cbp->start_idx + 1) % QSIZE; cbp->num_full -= 1; pthread_cond_signal(&cbp->notfull); pthread_mutex_unlock(&cbp->buf_lock); return (data); circ_buf_t *new_cb() { circ_buf_t *cbp; cbp = (circ_buf_t *) malloc(sizeof (circ_buf_t)); if (cbp == NULL) return (NULL); pthread_mutex_init(&cbp->buf_lock, NULL); pthread_cond_init(&cbp->notfull, NULL); pthread_cond_init(&cbp->notempty, NULL); cbp->start_idx = 0; cbp->num_full = 0; return (cbp); } void delete_cb(circ_buf_t *cbp) { pthread_mutex_destroy(&cbp->buf_lock); pthread_cond_destroy(&cbp->notfull); pthread_cond_destroy(&cbp->notempty); free(cbp); } new_cd legt einen neuen Puffer an und initialisiert den Mutex buf_lock und die Condition-Variables notfull und notempty. delete_cb löscht den Mutex und die Condition-Variables und anschließend den Puffer. Page 32 } put_cb_data fügt einen Datenpointer data in den Puffer cbp ein. Dazu wird wie folgt vorgegangen: Zuerst wird der Puffer gelockt. Dann wird gewartet, bis Platz im Puffer ist. Anschließend wird das Datum in den Puffer eingefügt, evtl. wartenden Threads signalisiert, daß der Puffer jetzt nicht leer ist, und schließlich wird der Lock auf den Puffer wieder freigegeben. get_cb_data liefert das älteste Datum im Puffer. Dazu wird zuerst der Puffer gelockt. Dann wird gewartet, bis der Puffer nicht leer ist. Anschließend wird das Datum dem Puffer entnommen, evtl. wartenden Threads signalisiert, daß der Puffer jetzt nicht voll ist, und zuletzt wird der Puffer freigegeben und das Datum zurückgeliefert. C Tutorial © Mathias Waack <[email protected]> Einführung in die Programmiersprache C Weitergehend Literatur Prozesse und -kommunikation: John Shapley Gray: Interprocess Communications in Unix. Prentice Hall, 1997 Threads: Nichols, Buttlar, Farrell: Phtreads Programming. O’Reilly, 1996 Kleiman, Shah, Smaalders: Programming with Threads. SunSoft Press, 1996 Lex/Yacc: Helmut Herold: lex und yacc. Addison-Wesley, 1992 Grundlagen: Betriebssysteme: Tanenbaum Compilerbau (u.a. Scanner, Parser): Aho, Sethi, Ullmann: Compilerbau. Algorithmen: Segdewick: Algorithmen Page 33