C und C++ (CPP) 5. Modulare Programmierung Prof. Dr. Marc Rennhard Institut für angewandte Informationstechnologie InIT ZHAW Zürcher Hochschule für angewandte Wissenschaften marc.rennhard@zhaw.ch Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 1 1
Ablauf Zusammenspiel von Präprozessor, Compiler und Linker beim Generieren eines ausführbaren Programms Modulare Programmierung mit Aufteilung des Quellcodes in mehrere Dateien Gebrauch von Libraries make Utility, um ein Programm aus mehreren Modulen zu generieren Kurzübersicht der kompletten C Standard Library Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 2 2
Ziele Sie kennen die Aufgaben von Präprozessor, Compiler und Linker und können diese erklären Die kennen die Konzepte der modularen Programmierung und können diese selbst anwenden Sie wissen, wozu das make Utility gebraucht wird, wie es funktioniert und wie ein Makefile aufgebaut ist Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 3 3
Präprozessor, Compiler und Linker (1) Um aus Quellcode ein lauffähiges Programm zu generieren, sind drei Komponenten involviert: Der Präprozessor sucht im Quellcode nach speziellen Befehlen (z.b. #include, #define, #ifdef, ), um dann im Wesentlichen textuelle Ergänzungen durchzuführen Der Compiler wandelt dann den Quellcode in Objektcode um Aus einer Quellcodedatei wird eine Objektcodedatei generiert Die einzelnen Objektcodedateien können noch offene Aufrufe enthalten z.b. auf Funktionen, die nicht innerhalb der eigenen Quellcodedatei definiert sind oder auf globale Variablen in einer anderen Quellcodedatei Der Linker verbindet die noch offenen Aufrufe und generiert ein ausführbares Programm Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 4 4
Präprozessor, Compiler und Linker (2) Editor hello.c Quellcodedatei Makefile Headerdatei hello.h stdio.h gcc -c hello.c Präprozessor Compiler Objektdatei hello.o gcc -o hello hello.o Linker Libraries (direkt: gcc -o hello hello.c) Ausführbares Programm hello libc Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 5 Mit gcc S hello.c kann Assembler-Code generiert werden; Assembler ist im Wesentlichen eine für den Menschen lesbare Version von Objektcode. 5
Präprozessor (1) Der Präprozessor hat folgende Aufgaben: Daten von anderen Dateien in den Quelltext einbinden (#include) Mit #define definierte Konstanten durch die entsprechenden Werte zu ersetzen Selektiv Teile des Quellcodes ein- oder auszuschliessen (mit den Befehlen #if, #elif, #else, #endif, #ifdef, #ifndef) Präprozessorbefehle beginnen immer mit # Mit gcc E kann der Output nach der Präprozessorstufe betrachtet werden Es gibt zwei Arten um Daten von anderen Dateien einzubinden: #include <Dateiname> um Headerdateien der Standard Library einzubinden, z.b. #include <stdio.h> #include "Dateiname" um projektspezifische (d.h. selbst generierte) Headerdateien einzubinden, z.b. #include "hello.h" Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 6 6
Präprozessor (2) Konstanten werden mit #define definiert #define MAXLOOP 1000 jedes Auftreten von MAXLOOP im Quellcode wird durch 1000 ersetzt int counter = MAXLOOP; int counter = 1000; Achtung: man kann mit #define auch Fehler einbauen: #define K 10... int K = 20; /* daraus wird int 10 = 20; dies wird vom Compiler entdeckt weil 10 kein gültiger Variablenname ist */ Konstanten können auch keinen Wert haben und können un -definiert werden; dies macht aber im Zusammenhang mit selektiver Einbindung von Code nur Sinn: #define DEBUG /* Definieren einer Konstante DEBUG ( Flag setzen) */... #undef DEBUG /* DEBUG existiert nicht mehr ( Flag nicht gesetzt) */ Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 7 7
Präprozessor (3) Selektiv Code ein- oder ausschliessen; z.b. um ein Programm für Debugging oder Production Mode zu kompilieren: #define DEBUG /* Konstante; ohne zugehörigen Wert */... #if defined DEBUG /* ist eine Konstante DEBUG definiert? */ printf("program Version 1 (Debugging)\n"); /* ja */ #else printf("program Version 1 (Production)\n"); /* nein */ #endif Kurzform für #if defined: #ifdef #ifdef DEBUG und #if defined DEBUG sind also identisch Alle Präprozessor-Befehle: #if, #elif, #else, #endif, #ifdef, #ifndef Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 8 Man beachte: Der Präprozessor wird keine if-statements aus den obigen Konstrukten generieren, welche dann zur Laufzeit ausgewertet werden müssen. Ist DEBUG gesetzt, so wird das komplette #if... #endif Konstrukt mit der einzigen Zeile printf( Program Version 1 (Debugging)\n ); ersetzt. Selektives Ein- oder Ausschliessen von Code wird häufig auch dazu verwendet, wenn ein Programm auf verschiedenen Systemen kompilierbar sein soll. Oft wird dabei je nach Zielsystem, für welches ein ausführbares Programm kompiliert werden soll, eine eigene Headerdatei eingebunden, welche dann wiederum systemspezifische #includes enthalten kann. Das aktuell verwendete System kann mit Präzessorbefehlen erkannt werden, indem getestet wird, ob und auf welchen Wert gewisse Umgebungsvariablen gesetzt sind. 8
Compiler Der eigentliche Compiler erhält vom Präprozessor den überarbeiteten Quellcode und generiert Objektcode Der Objektcode ist bereits maschinennaher Code, der aber noch nicht ausführbar ist, da er noch offene Aufrufe enthalten kann Aufgaben des Compilers Prüfung des Quellcodes auf syntaktische Korrektheit Statische Typprüfung (überprüft die Datentypen bei der Zuweisung von Variablen) Gibt Errors oder Warnungen aus test.c:16: error: 'p' undeclared (first use in this function) test.c:16: warning: assignment makes integer from pointer without a cast Treten keine Errors auf (Warnungen sind erlaubt, sollten aber vom Programmierer genau untersucht werden!), wird der Objektcode erzeugt Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 9 9
Linker Generiert ein vollständiges, ausführbares Programm aus: dem vom Compiler generierten Objektcode Code aus der Standard Library oder aus anderen Libraries (welche auch nichts anderes als Objektcode enthalten) Die wichtigste Aufgabe des Linkers ist die Auflösung der noch offenen Aufrufe: Es wird geprüft, ob die aus anderen Objektdateien oder aus libraries verwendeten Funktionen dort auch wirklich vorhanden sind Es wird geprüft, ob mit extern deklarierte Variablen auch wirklich irgendwo definiert werden Der verwendete Objektcode wird in einer ausführbaren Datei zusammengesetzt und die Funktionsaufrufe und die zugehörigen Funktionen werden zusammengehängt (die Adressen der Funktion werden bei den Aufrufen richtig gesetzt) Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 10 10
Modulare Programmierung (1) Aufteilen des Quellcodes auf mehrere Module Verschiedene Module werden in verschiedenen Dateien abgelegt Weiter werden ein oder mehrere Headerdateien verwendet Enthalten zb Konstanten (#define), Funktionsdeklarationen und Strukturen dadurch kommen die entsprechenden Daten nur einmal im Quellcode vor Beispiel: einfaches Programm mit einem #define und einer Funktion doit main.c: #define MAX 1000 int doit(int b); /* Funktionsdeklaration */ int main(void) { return MAX + doit(10); } int doit(int b) { /* Funktionsdefinition */ return MAX + b; } Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 11 In Java wird die modulare Programmierung schon fast erzwungen mit einer Datei pro Klasse. In C kann wenn man will der komplette Source-Code in ein potentiell riesiges File geschrieben werden. Sinnvollerweise soll man den Code aber aufteilen. Nicht unbedingt ein File pro Funktion, aber doch so, dass logisch zusammengehörende Programmteile in je einem separaten File untergebracht sind. Die Auslagerung der Funktionsdeklarationen und Ähnlichem in Headerdateien ist jedoch etwas C (und auch C++) spezifisches und hat kein Äquivalent in Java. 11
Modulare Programmierung (2) 1. Schritt: Aufteilung in zwei Dateien (Module): main.c: #define MAX 1000 int doit(int b); /* Funktionsdeklaration */ int main(void) { return MAX + doit(10); } doit.c: #define MAX 1000 int doit(int b); /* Funktionsdeklaration; optional, weil doit(...) in diesem Modul nicht aufgerufen wird */ int doit(int b) { /* Funktionsdefinition */ return MAX + b; } Vorteile: übersichtlich, Wiederverwendbarkeit von Modulen in anderen Prg. Nachteile: Konstanten, Funktionsdeklarationen, Strukturen etc. sind mehrmals vorhanden Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 12 12
Modulare Programmierung (3) 2. Schritt: Verwendung einer Headerdatei header.h: main.c: #define MAX 1000 int doit(int b); /* Funktionsdeklaration */ #include "header.h" /* alles, was in header.h steht, wird hier eingefügt */ int main(void) { return MAX + doit(10); } doit.c: #include "header.h" /* alles, was in header.h steht, wird hier eingefügt */ int doit(int b) { /* Funktionsdefinition */ return MAX + b; } Vorteil: Konstanten und Funktionsdeklarationen nur einmal vorhanden Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 13 13
Modulare Programmierung (4) Generieren des ausführbaren Prg.: gcc -o prog main.c doit.c Zuerst generiert der Präprozessor folgendes: aus doit.c: int doit(int b); /* Funktionsdeklaration, aus header.h */ int doit(int b) { /* Funktionsdefinition */ return 1000 + b; } aus main.c: int doit(int b); /* Funktionsdeklaration, aus header.h */ int main(void) { return 1000 + doit(b); } Anschliessend generieren Compiler und Linker das ausführbare Programm Einbinden von Headerdateien die nicht im aktuellen Verzeichnis liegen mit der Option -I: gcc Idir -o prog main.c doit.c Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 14 Der Präprozessor fügt den Code der Headerdateien einfach in das Programm ein und zwar dort, wo das entsprechende #include steht. Wenn mit der I Option mehrere Verzeichnisse eingebunden werden sollen, wird die Option einfach wiederholt verwendet. 14
Bemerkungen zu Libraries Die C Standard Library (Unix: /usr/lib/libc.a) enthält Dateien mit Objektcode, z.b. stdio.o, stdlib.o,... Die entsprechenden Standard Headers sind unter /usr/include/ zu finden, z.b. stdio.h, stdlib.h,... Mit #include <stdio.h> werden vom Präprozessor die Funktionsdeklarationen, Konstanten etc. des Objektcodes in stdio.o in den Quellcode eingefügt (genau gleich wie mit eigenen Headerdateien) Ohne diese Headerdateien müsste der Programmierer diese Funktionsdeklarationen etc. selbst einfügen! Der Linker verwendet den Objektcode aus den Objektdateien in der Standard Library, fügt diesen in das ausführbare Programm ein und setzt die Adressen für die entsprechenden Funktionsaufrufe Man kann auch eigene Libraries generieren; unter Unix mit dem Tool ar ar -r libown.a doit.o Kreiert Libary libown.a, die doit.o enthält gcc Ldir lown -o prog main.c -L bestimmt das Verzeichnis der Libraries; -l bestimmt die Library libown.a Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 15 Genau wie die I Option können auch die L und l Optionen wiederholt verwendet werden, um mehrere Verzeichnisse bzw. Libraries einzubinden. 15
Übung zu #include Wie sieht main.c nach dem Abarbeiten durch den Präprozessor aus? /* Modul person.h */ struct Person { char name[20]; unsigned int alter; }; /* Modul main.c */ #include "person.h" #include "list.h" int main(void) { struct List l; struct Person p; while (1) { } } /* Modul list.h */ #include "person.h" struct List { struct Person inhalt; struct List *next; }; void insert (struct List l, struct Person p); void remove (struct List l, struct Person p); /* Modul list.c */ #include "person.h" #include "list.h" void insert (struct List l, struct Person p) {} void remove (struct List l, struct Person p) {} Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 16 16
Problem mit mehrfachem #include main.c nach Präprozessor: Problem: person.h wird einmal direkt (von main.c) und einmal indirekt (via list.h) included Die Struktur Person ist damit in main.c zweimal deklariert Compilererror Abhilfe: 1. Definieren einer Konstante, sobald ein Headerfile zum ersten Mal included wird 2. Jedesmal beim include testen, ob die Headerdatei bereits included wurde (die Konstante schon gesetzt ist) /* Modul main.c */ struct Person { char name[20]; unsigned int alter; }; struct Person { char name[20]; unsigned int alter; }; struct List { struct Person inhalt; struct List *next; }; void insert (struct List l, struct Person p); void remove (struct List l, struct Person p); int main(void) { struct List l; struct Person p; while (1) { } } Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 17 Man kann sich fragen, ob list.h überhaupt ein #include "person.h" beinhalten soll, denn beim Gebrauch von list.h in einer.c-datei wird der Entwickler person.h ja selbst auch includen. Im obigen fall würde das auch funktionieren. Dennoch gilt die Regel, dass eine Headerdatei, die z.b. Datentypen einer anderen Headerdatei verwendet (wie dies bei list.h ja mit struct Person der Fall ist), die entsprechenden Datentypen immer included werden sollen. Der Grund ist, dass list.h ja auch Dinge (z.b. weitere Funktionen) erhalten könnte, die gar nichts direkt mit Personen zu tun haben. Wenn nun ein Entwickler nur einfach eine solche Funktion benötigt, dann wäre er sich der struct Person wohl gar nicht bewusst und er würde kaum ein #include "person.h" verwenden sondern einfach nur #include "list.h" und die resultierende Fehlermeldung des Compilers (bei struct List wird dieser struct Person nicht kennen) würde für den Entwickler gar keinen Sinn machen. Aus diesem Grund soll auch eine Headerdatei alles includen, was sie verwendet. Analog kann man argumentieren, dass der Entwickler dann bei #include "list.h" in main.c oder list.c halt kein #include "person.h" verwenden soll es ist in list.c ja schon enthalten. Aber auch hier gilt: Der Entwickler soll sich dem nicht bewusst sein müssen, sondern einfach das includen, was er in einer.c-datei braucht: Wenn er etwas mit Personen verwendet, dann schreibt er #include "person.h", wenn er etwas mit Listen verwendet, dann schreibt er #include "list.h" und verwendet er beides, wo wird er auch beide includes hinschreiben. Er soll sich nicht über irgendwelche potentiellen Abhängigkeiten von Headerdateien bewusst sein müssen. Aus diesem Grund ist eine Lösung wünschenswert, die auf jeden Fall funktioniert und robust gegenüber mehrfachen includes ist dies wird mit dem Konstrukt auf der nächsten Folie erreicht. 17
Problem mit mehrfachem #include Abhilfe Mit folgendem Konstrukt in jeder Headerdatei wird jede Headerdatei maximal einmal included: #ifndef HEADERDATEI_KENNUNG #define HEADERDATEI_KENNUNG /* code */ #endif /* Modul person.h */ #ifndef PERSON_H #define PERSON_H struct Person { char name[20]; unsigned int alter; }; #endif /* Modul list.h */ #ifndef LIST_H #define LIST_H #include "person.h" struct List { struct Person inhalt; struct List *next; }; void insert (struct List l, struct Person p); void remove (struct List l, struct Person p); #endif Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 18 Als C/C++ Programmierer sollten Sie sich angewöhnen, das obige Konstrukt immer in Headerfiles zu verwenden. Damit stellen Sie sicher, dass Sie selbst und auch andere Programmierer Ihre Codeteile und die zugehörigen Headerdateien einfach verwenden können, ohne die genauen Abhängigkeiten untereinander verstehen zu müssen. 18
make Utility Jedes grössere Programm setzt sich aus einer Reihe von Dateien (Module) zusammen Schon bei Veränderungen einzelner Dateien kann es erforderlich sein, mehrere Module neu zu kompilieren Wird z.b. eine Headerdatei verändert, so sollten alle Quellcodedateien, die diese Headerdatei einschliessen, neu kompiliert werden Bei grossen Projekten wird dies sehr schnell unübersichtlich make Utility make stammt ursprünglich von Unix und hilft, ein Programm aus mehreren Modulen unter Berücksichtigung der Abhängigkeiten zu erstellen Beim Aufruf von make werden die Regeln des im aktuellen Verzeichnis liegenden Makefile rekursiv abgearbeitet Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 19 make ist keineswegs auf C/C++ beschränkt. Genausogut können Sie damit Java Programme (oder irgendetwas anderes) unter Berücksichtigung der Abhängigkeiten kompilieren. Schauen Sie einmal ein Open Source Software-Paket, von welchem Sie den Source-Code herunterladen, genauer an Sie werden bestimmt ein Makefile finden. 19
Makefile (1) Das Makefile enthält Regeln, wann, was und wie etwas auszuführen ist Es besteht primär aus Kommentaren; beginnend mit # Variablendefinitionen Expliziten Regeln Eine Regel ist wie folgt aufgebaut: target: dependencies <TAB> command target: was zu erstellen ist dependencies: geben an, wovon target abhängig ist command: das oder die abzuarbeitenden Kommandos, die immer dann abgearbeitet werden, wenn eine der dependencies ein jüngeres Modifikationsdatum hat als target Der <TAB> vor einem command ist notwendig (keine zusätzlichen Leerzeichen!) Die Abarbeitung der Regeln erfolgt rekursiv Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 20 Rekursive Abarbeitung bedeutet folgendes: wenn ein target A abhängig von einer depedencie B ist und es im gleichen Makefile ein target C mit dem Namen der dependencie B gibt, so wird vor der Abarbeitung des commands von target A zuerst geprüft, ob target C neu erstellt werden muss und dieses wenn nötig auch vor der Ausführung des command von target A neu erstellt. Dies kann rekursiv über beliebig viele Schritte gehen. Das Beispiel auf der nächsten Folie illustriert dies. 20
Makefile (2) Beispiel: Programm rechner aus Modulen: main.c, add.c, sub.c, mul.c und div.c Alle Quellcodedateien nutzen die gemeinsame Headerdatei def.h Entsprechendes Makefile (Aufruf: make [all], make rechner oder make clean): # rechner wird aus allen Objektdateien (.o) zusammengebaut; clean dient # dazu, alle Objektdateien zu entfernen; all: rechner rechner: add.o sub.o mul.o div.o main.o gcc o rechner add.o sub.o mul.o div.o main.o clean: rm f *.o rechner # so entstehen die Objektdateien (def.h und Makefile werden angegeben, um # auch bei Veraenderungen dieser Dateien die Kompilierung neu zu starten) add.o: add.c def.h Makefile gcc c add.c sub.o: sub.c def.h Makefile gcc c sub.c mul.o: mul.c def.h Makefile gcc c mul.c div.o: div.c def.h Makefile gcc c div.c main.o: main.c def.h Makefile gcc c main.c Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 21 Führt man make all aus, so geschieht folgendes: Der Einstiegspunkt des Makefiles ist das target all, dort steht, all hängt von rechner ab. Man springt zum target rechner, rechner hängt von add.o, sub.o etc. ab Man springt zum target add.o, add.o hängt von add.c, def.h, Makefile ab. Ist eines davon jünger als add.o, so wird add.o gemäss gcc c add.c kompiliert Dasselbe geschieht mit sub.o, mul.o etc. Man kehrt wieder zum target rechner zurück. Wurde eine der add.o, sub.o etc. neu kompiliert, so wird gcc o rechner add.o... ausgeführt Man kehrt zu all zurück, dort gibt es kein command, womit das Makefile verlassen und make beendet wird Wird make ohne Parameter ausgeführt, so wird der erste Eintrag im Makefile verwendet. Im obigen Beispiel entspricht make als make all. Weil das Default-Verhalten von make (ohne Parameter) make all entsprechen sollte, fügt man den all-eintrag deshalb typischerweise ganz zuoberst ein. Im Prinzip wäre hier kein target all nötig, denn mit make rechner erreicht man ja genau dasselbe (all verweist in diesem Beispiel ja einfach auf ein einziges weiteres target (hier: dependency rechner)). Bei grösseren Projekten stehen hinter dem target all aber oft mehrere Teilprogramme, deshalb macht das target all dort Sinn und man findet es deshalb aus Konsistanzgründen meist auch bei kleineren Projekten, damit ganz Allgemein mit make all in jedem Projekt alles aktualisiert wird. 21
Übung zu make Unten sei das Listing des Verzeichnisses angegeben, welches die Dateien zum Programm rechner auf der vorhergehenden Folie enthält Was wird ausgeführt, wenn Sie jetzt make [all] auf der Kommandozeile eingeben? Beachten Sie den Zeitpunkt der letzten Modifikation der Dateien! $> ls l -rw-r----- 1 rennhard users 555 Mar 18 11:07 Makefile -rw-r----- 1 rennhard users 17 Mar 18 11:14 add.c -rw-r----- 1 rennhard users 651 Mar 18 11:09 add.o -rw-r----- 1 rennhard users 17 Mar 18 11:06 def.h -rw-r----- 1 rennhard users 17 Mar 18 11:06 div.c -rw-r----- 1 rennhard users 651 Mar 18 11:09 div.o -rw-r----- 1 rennhard users 33 Mar 18 11:17 main.c -rw-r----- 1 rennhard users 693 Mar 18 11:09 main.o -rw-r----- 1 rennhard users 17 Mar 18 11:06 mul.c -rw-r----- 1 rennhard users 651 Mar 18 11:09 mul.o -rwxr-x--- 1 rennhard users 11761 Mar 18 11:09 rechner* -rw-r----- 1 rennhard users 17 Mar 18 11:06 sub.c Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 22 22
Die C Standard Library (1) Sie kennen bereits Teile der Standard Library: stdio.h, string.h, stdlib.h, stdarg.h, math.h Hier sind sämtliche zugehörigen Standard Headers aufgelistet Für umfassende Referenzen gibt es gute Webseiten, z.b. http://www.infosys.utas.edu.au/info/documentation/c/cstdlib.html http://www2.hs-fulda.de/~klingebiel/c-stdlib assert.h: Bietet ein Makro zur Diagnose von Programmen void assert(int expression) wenn expression = 0 wird eine Nachricht mit der Zeilennummer auf stderr ausgegeben und das Programm terminiert ctype.h: Testen einzelner Characters z.b. int isalnum(int c), int isdigit(int c), int islower(int c),... errno.h: Globale Variable errno, die von einigen Standard Library Funktionen gesetzt wird float.h: Bestimmung von Eigenschaften von Gleitkommazahlen-Typen z.b. grösste/kleinste darstellbare double-zahl: DBL_MAX, DBL_MIN Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 23 Generell ist die C Standard Library natürlich sehr klein im Vergleich zu der Java Standard Library. Durch die langjährige Existenz von C werden Sie im WWW allerdings grosse Mengen von weiteren Libraries finden, die von irgendwelchen C-Programmierern bereitsgestellt wurden. Eine Halb -standardisierte Library ist die GNU C Library, welche auf jedem Unix/Linux-System vorhanden ist und die ebenfalls viele zusätzliche Funktionalität bietet, zb für die Netzwerkkommunikation (sys/socket.h). 23
Die C Standard Library (2) limits.h: Bestimmung von Eigenschaften von ganzzahligen Typen z.b. grösste darstellbare int/unsigned int-zahl: INT_MAX, UINT_MAX locale.h: Auslesen von lokalen Eigenschaften, z.b. für Zahlendarstellung (Dezimalpunkt, Trennzeichen für 1000er Gruppen) math.h: Mathematische Funktionen double log(double x), double pow(double x, double y), double sqrt(double x), double tan(double x),... setjmp.h: Umgehen der normalen Funktionsaufruf/Return Sequenz (z.b. direkter Rücksprung von einem tief verschachtelten Funktionsaufruf) signal.h: Funktionalität, um Ausnahmebedingungen (Signale) des Betriebssystems zu verarbeiten (z.b. Division durch 0) Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 24 24
Die C Standard Library (3) stdarg.h: Macros, um Funktionen mit variabler Anzahl Parameter zu implementieren va_list, void va_start(va_list ap, lastarg), type va_arg(va_list ap, type), void va_end(va_list ap) stddef.h: Definition von zusätzlichen Typen und Konstanten (z.b. NULL) stdio.h: Standard Input/Output Funktionen, formatieren von Output, lesen und schreiben von Files (printf, scanf, getchar,...) stdlib.h: Utility Funktionen void* malloc(size_t size), void free(void* p), int atoi(const char* s), int abs(int n), int rand(void),... string.h: Diverse Funktionen auf Strings (strlen, strcpy, strcat,...) time.h: Zeit- und Datumsfunktionen, z.b. um aktuelle Zeit zu bestimmen Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 25 25
Zusammenfassung Um aus Quellcode ein ausführbares Programm zu generieren, werden nacheinander Präprozessor, Compiler und Linker ausgeführt Der Präprozessor macht textuelle Ergänzungen im Quellcode Der Compiler wandelt dann den Quellcode in Objektcode um Der Linker verbindet offene Aufrufe und generiert ein ausführbares Programm Bei der modularen Programmierung wird der gesamte Quellcode sinnvoll auf mehrere Dateien verteilt; Funktionsdeklarationen, Konstanten (#define) und Deklarationen von Strukturen gehören in Headerdateien Die Standard Library stellt diverse Funktionen für ganz verschiedene Aufgaben zur Verfügung; die Standard Library enthält nichts anderes als Objektcode (z.b. stdio.o); dazu gibt es die entsprechenden Standard Headers (z.b. stdio.h) Das make Utility erlaubt es, ein Programm, welches aus mehreren Modulen besteht, effizient zu generieren Marc Rennhard, 29.03.2010, CPP_ModulareProgrammierung.ppt 26 26