Systemprogrammierung Teil 1: Einführung
Erfolgreiche ePaper selbst erstellen
Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.
<strong>Systemprogrammierung</strong><br />
<strong>Teil</strong> 1: <strong>Einführung</strong><br />
Prof. Dr. H. Drachenfels Version 3.0<br />
Hochschule Konstanz 26.7.2013<br />
Systemsoftware versus Anwendungssoftware<br />
Systemsoftware dient dem Betrieb von Rechnern<br />
• Verwaltung der Hardware-Ressourcen und Steuerung der internen Abläufe<br />
Prozessor- und Speicherverwaltung, Kommunikation mit angeschlossenen Geräten usw.<br />
• Bereitstellen einer komfortablen Ablaufumgebung für Anwendungssoftware<br />
Verbergen von speziellen Hardware-Eigenschaften usw.<br />
• Beispiele:<br />
Betriebssysteme, Datenbanksysteme, Firmware, JVM (Java Virtual Machine), ...<br />
Anwendungssoftware stellt Funktionalität für Endbenutzer eines Rechners bereit<br />
• Verwaltung und Verarbeitung von Anwenderdaten<br />
• Bereitstellen komfortabler Bedienoberflächen<br />
• Beispiele:<br />
Browser, Textverarbeitung, Computerspiele, ...<br />
Die Grenze zwischen Systemsoftware und Anwendungssoftware ist fließend.<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 1-1<br />
Hochschule Konstanz
Systemsoftware und Hardware<br />
Prozessor<br />
(CPU)<br />
Hauptspeicher<br />
(RAM)<br />
• Systemsoftware muss<br />
die Hardware-Ressourcen<br />
möglichst optimal nutzen<br />
E/A-Geräte<br />
• Tastatur (Keyboard)<br />
• Maus<br />
• Bildschirm (Display)<br />
• Netzwerkanschluss<br />
• ...<br />
Bus<br />
Peripherie-Geräte<br />
Hintergrundspeicher<br />
• Festplatte (Disk)<br />
• CD-ROM<br />
• USB-Stick<br />
• ...<br />
sie muss insbesondere die<br />
Begrenztheit der Ressourcen<br />
beachten<br />
• die Programmiersprache<br />
darf deshalb nicht zu stark<br />
von der Hardware abstrahieren<br />
das gilt insbesondere<br />
für die Ressource Speicher<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 1-2<br />
Hochschule Konstanz<br />
Systemsoftware und Programmiersprachen<br />
Ursprünglich wurde Systemsoftware vollständig in Assemblersprachen erstellt:<br />
• eine Assemblersprache bietet lediglich lesbare Namen für Maschinenbefehle<br />
• Software ist dadurch an den Befehlssatz einer Prozessorfamilie gekoppelt<br />
• Programmierung mühsam und fehleranfällig<br />
Heute wird Systemsoftware überwiegend in der Hochsprache C erstellt:<br />
• übersichtliche Sprache mit sehr guter Werkzeugunterstützung<br />
• in den 1970er-Jahren als Programmiersprache von Unix entstanden<br />
1978: Kernighan & Ritchie - "The C Programming Language"<br />
1989: ANSI-C, bis heute der am breitesten unterstützte Standard<br />
1990: C90, ISO-Standard, der bis auf Kleinigkeiten ANSI-C entspricht<br />
1995: C95, kleinere Ergänzungen<br />
1999: C99, Angleichungen an C++ und einige Erweiterungen<br />
2011: C11, unter anderem bessere Unicode-Unterstützung, Bibliothekserweiterungen<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 1-3<br />
Hochschule Konstanz
<strong>Systemprogrammierung</strong>: Inhalt der Lehrveranstaltung<br />
<strong>Einführung</strong> in die Sprache ANSI-C<br />
• Sprachkonzepte: Datentypen, Anweisungen, Funktionen, Übersetzungseinheiten<br />
• Standardbibliothek: Speicherverwaltung, Ein-/Ausgabe, Dateien, ...<br />
Werkzeuge<br />
• Compiler: gcc<br />
• Debugger: ddd, valgrind<br />
• Automatisierung der Programmerstellung: make<br />
Programmorganisation<br />
• statische und dynamische Bibliotheken<br />
• ausführbare Dateien<br />
• Archive<br />
• Prozesse<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 1-4<br />
Hochschule Konstanz
<strong>Systemprogrammierung</strong><br />
<strong>Teil</strong> 2: ANSI-C Daten<br />
Literale, Variablen, Typen<br />
Prof. Dr. H. Drachenfels Version 7.0<br />
Hochschule Konstanz 20.2.2014<br />
ANSI-C Literale: Ganze Zahlen<br />
Schreibweisen für ganze Zahlen (Integers):<br />
• dezimal 1 23 456 7890<br />
• oktal 01 023 045670<br />
• hexadezimal<br />
0x1 0x23 0x456 0x789a 0xbcdef0<br />
Typ des Literals ist je nach Schreibweise der jeweils kleinste passende Typ:<br />
• dezimal<br />
• oktal oder<br />
hexadezimal<br />
• mit Suffix L<br />
z.B. 12345L<br />
• mit Suffix U<br />
z.B. 12345U<br />
int, long int, unsigned long int<br />
int, unsigned int, long int, unsigned long int<br />
long int, unsigned long int<br />
unsigned int, unsigned long int<br />
Nicht vergessen: der Compiler wandelt alle Schreibweisen in Binärzahlen!<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-1<br />
Hochschule Konstanz
Beispiel-Programm Zahlen-Literale<br />
• Quellcode<br />
#include <br />
int main()<br />
{<br />
printf("%x\n", 12);<br />
}<br />
printf("%d\n", 012);<br />
printf("%o\n", 0x12);<br />
return 0;<br />
%x ist hexadezimales Format<br />
%d ist dezimales Format<br />
%o ist oktales Format<br />
\n ist Zeilenwechsel<br />
Konsolenausgabe<br />
des Programms:<br />
c<br />
10<br />
22<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-2<br />
Hochschule Konstanz<br />
ANSI-C Literale: Gleitkomma-Zahlen<br />
Schreibweisen für Gleitkomma-Zahlen (Floating Point Numbers):<br />
• nur dezimal 1. .23 0.456 78.9 .789e2 789e-1<br />
.789e2 steht für 0,789 ⋅ 10 2<br />
Typ des Literals abhängig vom Suffix:<br />
• ohne Suffix<br />
double<br />
• mit Suffix L<br />
z.B. 1.2345L<br />
• mit Suffix F<br />
z.B. 1.2345F<br />
long double<br />
float<br />
Nicht vergessen: Gleitkomma-Zahlen sind ungenau!<br />
Auch bei Gleitkomma-Literalen wandelt der Compiler alle Schreibweisen in ein Binärformat<br />
(je nach Zielhardware z.B. IEEE 754)<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-3<br />
Hochschule Konstanz
Beispiel-Programm Gleitkomma-Literale<br />
• Quellcode:<br />
#include <br />
int main()<br />
{<br />
printf("%g\n", (1e-30 + 1e30) - 1e30);<br />
}<br />
printf("%g\n", 1e-30 + (1e30 - 1e30));<br />
printf("%f\n", 12.3456789);<br />
printf("%f\n", 1234567.89);<br />
printf("%e\n", 12.3456789);<br />
printf("%e\n", 1234567.89);<br />
return 0;<br />
Konsolenausgabe<br />
des Programms:<br />
%g ist Fest- oder Gleitkommaformat nach Bedarf<br />
%f ist Festkommaformat<br />
%e ist Gleitkommaformat<br />
0<br />
1e-30<br />
12,345679<br />
1234567,890000<br />
1.234568e+01<br />
1.234568e+06<br />
Ausgabe bei %f und %e<br />
standardmäßig mit<br />
6 Nachkommastellen<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-4<br />
Hochschule Konstanz<br />
ANSI-C Literale: Einzelzeichen (1)<br />
Schreibweisen für Einzelzeichen (Characters):<br />
• in Einfach-Hochkommas<br />
'a' 'A' '1' '.' ' ' Buchstaben, Ziffern, Satzzeichen, Leerstelle, ...<br />
'\0' das NULL-Zeichen (Code-Nummer 0)<br />
'\ooo' Codenummer oktal (1 bis 3 Oktalziffern o)<br />
'\xhh' Codenummer hexadezimal (mindestens eine Hex-Ziffer h)<br />
'\c' Ersatzdarstellung für Steuerzeichen (c ist a, b, f, n, r oder t)<br />
'\''<br />
'\"'<br />
'\\'<br />
das Einfach-Hochkomma<br />
das Doppel-Hochkomma<br />
der Backslash<br />
Typ des Literals abhängig vom Präfix:<br />
• ohne Präfix char<br />
• mit Präfix L wchar_t<br />
z.B. L'x'<br />
Der Compiler wandelt alle Schreibweisen<br />
in binäre Zeichencode-Nummern<br />
(je nach Plattform z.B. ASCII).<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-5<br />
Hochschule Konstanz
ANSI-C Literale: Einzelzeichen (2)<br />
• Bedeutung der Ersatzdarstellungen für Steuerzeichen:<br />
'\a' Alarm<br />
'\b' Rückschritt (Backspace)<br />
'\f' Seitenvorschub (Formfeed)<br />
'\n' Zeilenende (Newline)<br />
'\r' Wagenrücklauf (Carriage-Return)<br />
'\t' Horizontal-Tabulator<br />
'\v' Vertikal-Tabulator<br />
Nicht vergessen: der Compiler wandelt alle Schreibweisen<br />
in binäre Zeichencode-Nummern (je nach Plattform z.B. ASCII)<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-6<br />
Hochschule Konstanz<br />
ANSI-C Literale: Zeichenketten<br />
Schreibweise für Zeichenketten (Strings):<br />
• in Doppel-Hochkommas<br />
"Hallo"<br />
"" leerer String<br />
• nur durch Zwischenraum (Whitespace) getrennte Zeichenketten<br />
fasst der Compiler zu einer Zeichenkette zusammen:<br />
"Hal" "lo"<br />
das gleiche wie "Hallo"<br />
zwischen den Doppelhochkommas sind<br />
alle Schreibweisen für Einzelzeichen erlaubt,<br />
wobei die Einfach-Hochkommas entfallen,<br />
z.B. "Hallo\n"<br />
Typ des Literals abhängig vom Präfix:<br />
• ohne Präfix<br />
• mit Präfix L<br />
z.B. L"Hallo"<br />
char*<br />
wchar_t*<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-7<br />
Hochschule Konstanz
Beispiel-Programm Zeichen-Literale<br />
• Quellcode:<br />
#include <br />
int main()<br />
{<br />
printf("%s\n", "Hallo");<br />
}<br />
printf("%s\n", "Hal" "lo");<br />
printf("Hallo\n");<br />
printf("%c%c%c%c%c\n", 'H', 'a', 'l', 'l', 'o');<br />
return 0;<br />
%s ist Zeichenkettenausgabe<br />
%c ist Einzelzeichenausgabe<br />
Konsolenausgabe<br />
des Programms:<br />
Hallo<br />
Hallo<br />
Hallo<br />
Hallo<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-8<br />
Hochschule Konstanz<br />
ANSI-C Literale: Symbolische Konstanten<br />
Der ANSI-C-Präprozessor erlaubt es, symbolische Namen für Literale zu vergeben.<br />
• Definition einer symbolischen Konstanten:<br />
#define Name Literal<br />
Präprozessor-Anweisungen sind Zeilen, die mit # beginnen<br />
der Name sollte nur aus Großbuchstaben bestehen<br />
(und eventuell Ziffern und Unterstriche, allerdings nicht als erstes Zeichen)<br />
• Benutzung einer symbolischen Konstanten:<br />
nach der Definition kann der Name anstelle des Literals geschrieben werden<br />
der Name wird beim Übersetzen vom Präprozessor durch das Literal ersetzt<br />
• Beispiel:<br />
#define PI 3.14159265358979323846<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-9<br />
Hochschule Konstanz
ANSI-C Literale: Vergleich mit Java<br />
Schreibweise der Literale ist in ANSI-C und Java weitgehend gleich<br />
Wichtige Unterschiede bei ANSI-C:<br />
• es gibt ganze Zahlen ohne Vorzeichen<br />
• der Zeichensatz ist nicht Unicode<br />
• Verkettung von String-Literalen ohne +<br />
• keine Literale true und false<br />
• symbolische Namen für Literale<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-10<br />
Hochschule Konstanz<br />
ANSI-C Literale: Empfehlungen<br />
Zahlen-Literale:<br />
• echte Zahlen immer dezimal schreiben<br />
• Bitmuster immer oktal oder noch besser hexadezimal schreiben<br />
Zeichen-Literale:<br />
• die oktale und hexadezimale Angabe von Code-Nummern (ausser '\0') vermeiden<br />
Es drohen sonst Überraschungen auf Rechnern mit verschiedenen Zeichencodes.<br />
symbolische Konstanten:<br />
• Literale in der Regel nur zum Initialisieren von Variablen verwenden,<br />
ansonsten symbolische Konstanten bevorzugen<br />
Kommt ein bestimmtes Literal an mehreren Stellen vor, ist nicht erkennbar,<br />
ob zwischen diesen Stellen ein logischer Zusammenhang besteht<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-11<br />
Hochschule Konstanz
ANSI-C Variablen: Eigenschaften<br />
Variablen dienen dazu, Werte im Hauptspeicher abzulegen und anzusprechen.<br />
• eine Variable hat einen Namen:<br />
Besteht aus Buchstaben, Ziffern und Unterstrichen.<br />
Darf nicht mit einer Ziffer beginnen und darf kein ANSI-C Schlüsselwort sein.<br />
• eine Variable hat einen Typ:<br />
Legt fest, welche Art von Werten die Variable aufnehmen kann (z.B. nur ganze Zahlen).<br />
Legt fest, welche Operationen erlaubt sind (z.B. Addition usw.).<br />
• eine Variable hat einen Wert:<br />
Steht in binärer Zahlendarstellung im Hauptspeicher.<br />
• eine Variable hat eine Adresse:<br />
Die Anfangsadresse des Werts im Hauptspeicher.<br />
• eine Variable hat einen Platzbedarf:<br />
Anzahl Bytes, die der Wert im Hauptspeicher belegt. Hängt vom Typ ab.<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-12<br />
Hochschule Konstanz<br />
ANSI-C Variablen: Syntax<br />
• Variablen-Definition legt Typ und Name fest:<br />
Erst nach ihrer Definition ist eine Variable benutzbar Typ Name;<br />
Definition lokaler Variablen nur am Anfang eines {}-Blocks<br />
• Wert:<br />
definierter Anfangswert nur mit Initialisierung<br />
Typ Name = Wert;<br />
Wertänderung per Zuweisung<br />
Name = Wert;<br />
bei Konstanten Initialisierungspflicht und keine Zuweisung const Typ Name = Wert;<br />
• Adresse:<br />
der Adressoperator liefert die Adresse einer Variablen &Name<br />
i.d.R. müssen Variablen eine durch sizeof (Typ) teilbare Adresse haben ( Alignment )<br />
• Platzbedarf:<br />
der sizeof-Operator liefert den Platzbedarf einer Variablen sizeof Name<br />
bzw. den Platzbedarf eines Typs.<br />
sizeof (Typ)<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-13<br />
Hochschule Konstanz
ANSI-C Datentypen: Übersicht<br />
Grundtypen (elementare Datentypen)<br />
• Arithmetische Typen<br />
Ganzzahlige Typen: char, int, ...<br />
Gleitkommatypen: float, double, ...<br />
Einen logischen Typ<br />
(boolean) gibt es nicht!<br />
• Anonymer Typ:<br />
void<br />
Abgeleitete Typen<br />
• Zeiger: *<br />
• Felder: []<br />
Benutzerdefinierte Typen<br />
• Aufzählungen: enum<br />
• Strukturen: struct, union<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-14<br />
Hochschule Konstanz<br />
ANSI-C Grundtypen: int<br />
• Variablen-Definition: int zahl = 123;<br />
short int zahl = 123;<br />
long int zahl = 123L;<br />
unsigned int bytefolge = 0xffffffffU;<br />
unsigned short int bytefolge = 0xffffU;<br />
unsigned long int bytefolge = 0xffffffffUL;<br />
Kurzschreibweise: hinter short, long und unsigned kann int weggelassen werden<br />
• Wert:<br />
ganze Zahl mit Vorzeichen<br />
mit Zusatz unsigned Bitmuster (ganze Zahl ohne Vorzeichen).<br />
• Platzbedarf je nach Rechner bzw. Compiler:<br />
sizeof (short) ≤ sizeof (int) ≤ sizeof (long)<br />
typisch:<br />
2 Byte für short<br />
4 Byte für int und long (ILP32-Rechner)<br />
8 Byte für long (LP64- Rechner)<br />
Zusatz unsigned<br />
ist ohne Einfluss<br />
auf den Platzbedarf<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-15<br />
Hochschule Konstanz
Beispiel-Programm int-Variablen<br />
• Quellcode:<br />
#include <br />
int main()<br />
{<br />
int n = 0;<br />
int m = 1;<br />
}<br />
/* print variable values */<br />
printf("n = %d\n", n);<br />
printf("m = %d\n", m);<br />
/* print variable addresses */<br />
printf("&n = %p\n", (void*) &n);<br />
printf("&m = %p\n", (void*) &m);<br />
/* print type and variable sizes */<br />
printf("sizeof (int) = %lu\n", (unsigned long) sizeof (int));<br />
printf("sizeof n = %lu\n", (unsigned long) sizeof n);<br />
return 0;<br />
Konsolenausgabe<br />
des Programms:<br />
n = 0<br />
m = 1<br />
&n = 0x22efc4<br />
&m = 0x22efc0<br />
sizeof (int) = 4<br />
sizeof n = 4<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-16<br />
Hochschule Konstanz<br />
ANSI-C Grundtypen: float und double<br />
• Variablen-Definition:<br />
• Wert:<br />
bei float<br />
bei double<br />
float zahl = 3.14F;<br />
double zahl = 3.14;<br />
long double zahl = 3.14L;<br />
einfach genaue Gleitkommazahlen (single precision)<br />
doppelt genaue Gleitkommazahlen (double precision)<br />
bei long double erweitert genaue Gleitkommazahlen (extended precision)<br />
• Platzbedarf je nach Rechner bzw. Compiler:<br />
sizeof (float) ≤ sizeof (double) ≤ sizeof (long double)<br />
typisch:<br />
4 Byte für float<br />
8 Byte für double<br />
16 Byte für long double<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-17<br />
Hochschule Konstanz
ANSI-C Grundtypen: char<br />
• Variablen-Definition: char zeichen = 'a';<br />
wchar_t zeichen = L'a';<br />
signed char byte = –1;<br />
unsigned char byte = 0xff;<br />
• Wert:<br />
bei char<br />
Einzelzeichen im Standard-Zeichensatz (normalerweise ASCII)<br />
bei wchar_t<br />
Einzelzeichen in einem größeren Zeichensatz (z.B. Unicode)<br />
bei signed char ganze Zahl mit Vorzeichen<br />
bei unsigned char Bitmuster (ganze Zahlen ohne Vorzeichen)<br />
• Platzbedarf je nach Rechner bzw. Compiler:<br />
1 ≡ sizeof (char) ≤ sizeof (wchar_t) ≤ sizeof (long)<br />
1 ≡ sizeof (signed char) ≡ sizeof (unsigned char)<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-18<br />
Hochschule Konstanz<br />
ANSI-C Grundtypen: void<br />
• Variablen-Definition:<br />
• Wert:<br />
entfällt — es gibt keine Variablen vom Typ void<br />
entfällt<br />
• Platzbedarf:<br />
entfällt — sizeof-Operator auf void nicht anwendbar<br />
Verwendung des Typs void:<br />
• zur Definition abgeleiteter Typen<br />
void* Zeiger auf "irgendwas"<br />
• bei Funktions-Definitionen<br />
void f(void); Funktionen ohne Rückgabewert bzw. ohne Parameter<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-19<br />
Hochschule Konstanz
ANSI-C Grundtypen: Vergleich mit Java<br />
Grundtypen und Schreibweise der Variablen-Definition<br />
sind in ANSI-C und Java sehr ähnlich<br />
Wichtige Unterschiede bei ANSI-C:<br />
• kein Grundtyp boolean<br />
• es gibt ganze Zahlen ohne Vorzeichen<br />
• Platzbedarf und Speicheradresse von Variablen<br />
lassen sich mit Operatoren sizeof bzw. & ermitteln<br />
• Platzbedarf und damit Wertebereiche der Grundtypen sind plattformabhängig<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-20<br />
Hochschule Konstanz<br />
ANSI-C Grundtypen: Empfehlungen<br />
• vorzugsweise die Grundtypen char, int, double verwenden<br />
Die anderen Grundtypen nur verwenden, wenn es einen zwingenden Grund gibt.<br />
• Zusatz const verwenden, wenn eine Variable ihren Wert<br />
nach der Initialisierung nicht mehr ändern soll:<br />
const double pi = 3.14159265358979323846;<br />
• Achtung:<br />
Die Mischung unterschiedlich großer Zahltypen<br />
sowie von Zahltypen mit und ohne Vorzeichen<br />
kann zu überraschenden Ergebnissen führen.<br />
double x = 8.5 + 1 / 2; // setzt x auf 8.5 statt 9<br />
unsigned a = 1;<br />
int b = -2;<br />
if (a + b > 0) ... // Summe ist 4 294 967 295 statt -1<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-21<br />
Hochschule Konstanz
ANSI-C Abgeleitete Typen: Zeiger (1)<br />
Zu jedem Typ kann ein Zeigertyp (Pointertyp) abgeleitet werden, indem man<br />
in der Variablen-Definition einen Stern ∗ vor den Variablen-Namen schreibt.<br />
• Variablen-Definition:<br />
• Wert:<br />
Typ Name = Wert;<br />
Typ ∗Zeigername_1 = &Name;<br />
Typ ∗∗Zeigername_2 = &Zeigername_1;<br />
Die Adresse eines Speicherbereichs (Wert 0 bedeutet, der Zeiger zeigt nirgendwohin)<br />
• Platzbedarf je nach Rechner bzw. Compiler:<br />
sizeof (int) ≤ sizeof (Typ ∗)<br />
• Grafische Darstellung:<br />
typisch: 4 Byte<br />
Zeigername_2 Zeigername_1 Name<br />
& Zeigername_1 & Name Wert<br />
Kästchen stehen für<br />
Speicherbereiche<br />
Pfeile stehen für Adresswerte<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-22<br />
Hochschule Konstanz<br />
ANSI-C Abgeleitete Typen: Zeiger (2)<br />
• Zeiger auf konstanten Wert:<br />
const Typ Name = Wert;<br />
Typ ∗Zeigername = &Name; // Fehler<br />
const Typ ∗Zeigername = &Name;<br />
• konstanter Zeiger:<br />
Typ Name = Wert;<br />
Typ ∗ const Zeigername = &Name;<br />
• konstanter Zeiger auf konstanten Wert:<br />
const Typ ∗ const Zeigername = &Name;<br />
• Inhaltsoperator ∗ macht vom Zeiger adressierten Speicherbereich zugreifbar:<br />
∗Zeigername<br />
Achtung: Programm-Absturz, wenn der Zeiger den Wert 0 hat<br />
Inhaltsoperator ist Gegenstück zum Adressoperator:<br />
∗&Name ist das gleiche wie Name<br />
Der Wert einer Konstanten kann<br />
auch auf dem Umweg über Zeiger<br />
nicht geändert werden.<br />
Ein konstanter Zeiger zeigt<br />
während des ganzen Programmlaufs<br />
auf denselben Speicherbereich.<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-23<br />
Hochschule Konstanz
ANSI-C Abgeleitete Typen: Zeiger (3)<br />
void-Pointer<br />
• Variablen-Definition:<br />
Typ Name = Wert;<br />
void ∗void_pointer = &Name;<br />
• Wert:<br />
Adresse eines Speicherbereichs beliebigen Typs (aber Inhalt nicht zugreifbar)<br />
• Platzbedarf:<br />
wie andere Zeiger auch<br />
• Typecast-Operator (T ) wandelt einen void-Pointer in einen konkreten Pointer:<br />
Typ ∗typ_pointer = (Typ ∗) void_pointer;<br />
Achtung: chaotische Laufzeitfehler, wenn der void-Pointer<br />
nicht auf einen Speicherbereich des angegeben Typs zeigt<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-24<br />
Hochschule Konstanz<br />
ANSI-C Abgeleitete Typen: Zeiger (4)<br />
Verwendung von Zeigern z.B. bei dynamischer Spreicherverwaltung:<br />
• die Funktion malloc reserviert Speicher für Werte eines Typs<br />
und liefert die Adresse des Speicherbereichs:<br />
Typ ∗Zeigername = (Typ*) malloc(sizeof (Typ));<br />
if (Zeigername == NULL)<br />
{<br />
Anzahl benötigte Bytes<br />
... /* Fehlerbehandlung */ malloc hat Rückgabetyp void*<br />
}<br />
malloc liefert die ungültige Adresse 0 (wird oft NULL geschrieben),<br />
wenn die angeforderte Menge Speicher nicht verfügbar ist.<br />
Achtung: malloc reserviert nur Speicher, initialisiert ihn aber nicht<br />
• mit der Funktion free kann (und sollte!) per malloc reservierter Speicher<br />
irgendwann wieder freigegeben werden:<br />
#include erforderlich,<br />
free(Zeigername);<br />
damit malloc und free bekannt sind<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-25<br />
Hochschule Konstanz
Beispiel-Programm Zeiger-Variable<br />
• Quellcode:<br />
#include <br />
int main()<br />
{<br />
int n = 3082;<br />
int *p = &n;<br />
}<br />
/* print pointer value */<br />
printf("p = %p\n", (void*) p);<br />
/* print pointer address */<br />
printf("&p = %p\n", (void*) &p);<br />
/* print pointer size */<br />
printf("sizeof p = %lu\n", (unsigned long) sizeof p);<br />
/* print dereferenced pointer value */<br />
printf("*p = %d\n", *p);<br />
return 0;<br />
Konsolenausgabe<br />
des Programms:<br />
p = 0x22efc4<br />
&p = 0x22efc0<br />
sizeof p = 4<br />
*p = 3082<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-26<br />
Hochschule Konstanz<br />
ANSI-C Abgeleitete Typen: Felder (1)<br />
Zu jedem Typ kann ein Feldtyp (Arraytyp) abgeleitet werden, indem man<br />
in der Variablen-Definition eine Feldgröße in Klammern [] angibt.<br />
• Variablen-Definition: Typ Feldname[Feldgröße] = {Wert_1, Wert_2, ...};<br />
Kurzschreibweise: Die Feldgröße kann entfallen, wenn eine Initialisierung angegeben ist<br />
• Wert:<br />
• Platzbedarf:<br />
Folge von Werten gleichen Typs<br />
(Zugriff nur elementweise mit Indexoperator)<br />
sizeof Feldname ≡ Feldgröße ∗ sizeof (Typ)<br />
• Grafische Darstellung:<br />
Feldname[]<br />
[0] = Wert_1<br />
[1] = Wert_2<br />
:<br />
[Feldgröße - 1] = Wert_N<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-27<br />
Hochschule Konstanz
ANSI-C Abgeleitete Typen: Felder (2)<br />
• Indexoperator [] macht die Feld-Elemente zugreifbar:<br />
Feldname[Index]<br />
Der Index muss ganzzahlig sein und zwischen 0 und Feldgröße - 1 liegen.<br />
Indices außerhalb dieses Bereichs führen zu undefinierten Laufzeitfehlern!<br />
der Feldname ohne [] ist Kurzschreibweise für die Adresse des ersten Feldelements:<br />
Feldname ist das gleiche wie &Feldname[0]<br />
Der Feldname ist also keine Name für den Speicherbereich des Felds,<br />
sondern ein Name für die Anfangsadresse des Felds!<br />
• der Indexoperator ist Kurzschreibweise für Inhaltsoperator und Zeigerarithmetik:<br />
Zeigername[Index] ist das gleiche wie ∗(Zeigername + Index)<br />
Zeigerarithmetik arbeitet mit der Einheit sizeof (Typ):<br />
Zeigername + Index bedeutet Adresse + Index ∗ sizeof (Typ)<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-28<br />
Hochschule Konstanz<br />
ANSI-C Abgeleitete Typen: Felder (3)<br />
Felder und dynamischer Spreicherverwaltung:<br />
• die Funktion calloc reserviert Speicher für ein Feld von Werten eines Typs<br />
und liefert die Adresse des Speicherbereichs:<br />
Typ ∗Zeigername = (Typ*) calloc(Feldgröße, sizeof (Typ));<br />
if (Zeigername == NULL)<br />
{<br />
... /* Fehlerbehandlung */<br />
}<br />
• calloc initialisiert den reservierten Speicher mit 0<br />
wird die Initialisierung nicht gebraucht, kann malloc verwendet werden:<br />
Typ ∗Zeigername = (Typ*) malloc(Feldgröße * sizeof (Typ));<br />
• Speicher auch bei calloc mit free wieder freigegeben:<br />
free(Zeigername);<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-29<br />
Hochschule Konstanz
Beispiel-Programm Feld-Variable<br />
• Quellcode:<br />
#include <br />
int main()<br />
{<br />
int a[] = {3421, 3442, 3635, 3814};<br />
const int n = (int)(sizeof a / sizeof (int));<br />
int i;<br />
}<br />
/* print array values and addresses */<br />
printf("a = %p\n", (void*) a);<br />
for (i = 0; i < n; ++i)<br />
{<br />
printf("%d: %p %d\n", i, (void*) &a[i], a[i]);<br />
}<br />
/* print array size */<br />
printf("sizeof a = %lu\n", (unsigned long) sizeof a);<br />
return 0;<br />
Was gibt das Programm auf der Konsole aus?<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-30<br />
Hochschule Konstanz<br />
Beispiel-Programm Feld-Zeiger (1)<br />
• Quellcode:<br />
#include <br />
#include /* calloc, malloc, free, ... */<br />
#include /* NULL, size_t, ... */<br />
int main()<br />
{<br />
oder ohne Initialisierung mit 0:<br />
const int n = 4; int *a = (int*) malloc(n * sizeof (int));<br />
int i;<br />
int *a = (int*) calloc((size_t) n, sizeof (int));<br />
if (a == NULL)<br />
{<br />
printf("Speicherreservierung fehlgeschlagen!\n");<br />
return 1;<br />
}<br />
a[0] = 3421;<br />
a[1] = 3442;<br />
a[2] = 3635;<br />
a[3] = 3814;<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-31<br />
Hochschule Konstanz
Beispiel-Programm Feld-Zeiger (2)<br />
• Fortsetzung Quellcode:<br />
...<br />
}<br />
/* print array values and addresses */<br />
printf("&a = %p\n", (void*) &a);<br />
printf("a = %p\n", (void*) a);<br />
for (i = 0; i < n; ++i)<br />
{<br />
printf("%d: %p %d\n", i, (void*) &a[i], a[i]);<br />
}<br />
/* print array size */<br />
printf("sizeof a = %lu\n", (unsigned long) sizeof a); /* pointer size */<br />
printf("%d * sizeof *a = %lu\n", n, (unsigned long) n * sizeof *a);<br />
free(a);<br />
return 0;<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-32<br />
Hochschule Konstanz<br />
ANSI-C Abgeleitete Typen: String (1)<br />
Ein String ist ein Feld von Einzelzeichen mit '\0' als letztem Zeichen.<br />
Strings werden über Zeiger-Variablen benutzt.<br />
• Variablen-Definition:<br />
• Wert:<br />
const char ∗s = "Hallo";<br />
const, weil<br />
String-Literal<br />
nicht änderbar!<br />
Anfangsadresse eines Strings (d.h. die Adresse seines ersten Zeichens)<br />
• Platzbedarf:<br />
sizeof "Hallo" ≡ 6 (Anzahl Zeichen incl. '\0')<br />
sizeof s ≡ sizeof (char∗)<br />
• Grafische Darstellung: s<br />
oder einfacher:<br />
s<br />
[0] = 'H'<br />
[1] = 'a'<br />
[2] = 'l'<br />
[3] = 'l'<br />
[4] = 'o'<br />
[5] = '\0'<br />
"Hallo"<br />
Zeichen '\0'<br />
dient in C als<br />
Endemarkierung<br />
von Strings<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-33<br />
Hochschule Konstanz
ANSI-C Abgeleitete Typen: String (2)<br />
String-Literale sind als Feld-Initialisierer verwendbar<br />
• Variablen-Definition:<br />
char s[] = "Hallo";<br />
Kurzschreibweise für:<br />
char s[] = {'H', 'a', 'l', 'l', 'o', '\0'};<br />
• Wert:<br />
• Platzbedarf:<br />
• Grafische Darstellung:<br />
Folge der Zeichen (Kopie des String-Literals einschließlich '\0')<br />
sizeof s ≡ 6 (Anzahl Zeichen einschl. '\0')<br />
s[]<br />
[0] = 'H'<br />
[1] = 'a'<br />
[2] = 'l'<br />
[3] = 'l'<br />
[4] = 'o'<br />
[5] = '\0'<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-34<br />
Hochschule Konstanz<br />
ANSI-C Abgeleitete Typen: String (3)<br />
• Manipulation von C-Strings mit Bibliotheks-Funktionen:<br />
char ∗strcpy(char ∗s1, const char ∗s2);<br />
kopiert den String s2 in den Speicherbereich s1 und liefert s1 als Rückgabewert<br />
char ∗strcat(char ∗s1, const char ∗s2);<br />
hängt den String s2 an den String s1 an und liefert s1 als Rückgabewert<br />
int strcmp(const char ∗s1, const char ∗s2 );<br />
Vergleicht die Strings s1 und s2 und liefert 0, wenn die Strings gleich sind,<br />
eine Zahl größer 0 bei s1 > s2 bzw. eine Zahl kleiner 0 bei s1 < s2<br />
size_t strlen(const char ∗s);<br />
liefert die Länge des Strings s ohne '\0'<br />
... /* noch einige weitere str-Funktionen */<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-35<br />
Hochschule Konstanz
Beispiel-Programm String-Variablen (1)<br />
• Quellcode:<br />
#include <br />
#include <br />
#include <br />
#include <br />
Was gibt das Programm auf der Konsole aus?<br />
damit die strxxx-Funktionen bekannt sind<br />
int main()<br />
{<br />
char a[] = "halli";<br />
const char *s = "hallo";<br />
char *t = NULL;<br />
/* compare, copy and concatenate strings */<br />
if (strcmp(a, s) < 0)<br />
{<br />
t = (char*) malloc(sizeof a + strlen(s));<br />
if (t == NULL) ... /* error handling */<br />
}<br />
...<br />
strcat(strcpy(t, a), s); /* or: strcpy(t, a); strcat(t, s); */<br />
strcpy und strcat<br />
allokieren keinen Speicher<br />
deshalb zuerst mit malloc<br />
genug Speicher reservieren<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-36<br />
Hochschule Konstanz<br />
Beispiel-Programm String-Variablen (2)<br />
• Fortsetzung Quellcode:<br />
...<br />
/* print string values and addresses */<br />
printf("a = %p %s\ns = %p %s\nt = %p %s\n",<br />
(void*) a, a, (void*) s, s, (void*) t, t);<br />
printf("sizeof a = %lu\n", (unsigned long) sizeof a); /* 6 */<br />
printf("sizeof s = %lu\n", (unsigned long) sizeof s); /* 4 bzw. 8 */<br />
printf("sizeof t = %lu\n", (unsigned long) sizeof t ); /* 4 bzw. 8 */<br />
printf("strlen(a) = %lu\n", (unsigned long) strlen(a)); /* 5 */<br />
printf("strlen(s) = %lu\n", (unsigned long) strlen(s)); /* 5 */<br />
printf("strlen(t) = %lu\n", (unsigned long) strlen(t )); /* 10 */<br />
}<br />
s = a; /* copies the address, not the string */<br />
/* a = s; syntax error */<br />
free(t);<br />
return 0;<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-37<br />
Hochschule Konstanz
ANSI-C Abgeleitete Typen: Felder von Feldern<br />
Mehrdimensionale Felder am Beispiel einer 2x3-Matrix<br />
• Variablen-Definition: int matrix[2][3] = {{10, 11, 12}, {20, 21, 22}};<br />
• Wert:<br />
zeilenweise Folge der Matrix-Elemente<br />
(Zugriff nur elementweise mit Indizierungs-Operatoren)<br />
• Platzbedarf: sizeof matrix ≡ 2 * 3 * sizeof (int)<br />
• Indizierung: matrix[i][j] ≡ ∗(*(matrix + i ) + j)<br />
• Grafische Darstellung:<br />
matrix[][]<br />
[0][0] = 10<br />
[0][1] = 11<br />
[0][2] = 12<br />
1. Zeile<br />
Recheneinheit<br />
sizeof (int)<br />
Recheneinheit<br />
sizeof (int[3])<br />
[1][0] = 20<br />
[1][1] = 21<br />
2. Zeile<br />
[1][2] = 22<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-38<br />
Hochschule Konstanz<br />
Beispiel-Programm Matrix-Zeiger (1)<br />
• Quellcode:<br />
#include <br />
#include <br />
#define M 3 /* number of columns */<br />
int main()<br />
{<br />
/* allocate and initialize memory for 2x3 matrix */<br />
const int n = 2; /* number of lines */<br />
int i, j;<br />
int (*matrix)[M] = (int(*)[M]) malloc(n * M * sizeof (int));<br />
if (matrix == NULL) ... /* error handling */<br />
matrix[0][0] = 10;<br />
matrix[0][1] = 11;<br />
matrix[0][2] = 12;<br />
matrix[1][0] = 20;<br />
matrix[1][1] = 21;<br />
matrix[1][2] = 22;<br />
...<br />
Spaltenanzahl muss bereits zur<br />
Übersetzungszeit feststehen<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-39<br />
Hochschule Konstanz
Beispiel-Programm Matrix-Zeiger (2)<br />
• Fortsetzung Quellcode:<br />
...<br />
/* print matrix addresses and values */<br />
printf("&matrix = %p\n", (void*) &matrix);<br />
printf("matrix = %p\n", (void*) matrix);<br />
for (i = 0; i < n; ++i)<br />
{<br />
printf("[%d] %p: %p\n", i, (void*) &matrix[i], (void*) matrix[i]);<br />
for (j = 0; j < M; ++j)<br />
{<br />
printf(" [%d] %p: %d\n", j, (void*) &matrix[i][j], matrix[i][j]);<br />
}<br />
}<br />
}<br />
/* print matrix size */<br />
printf("sizeof matrix = %lu\n", (unsigned long) sizeof matrix);<br />
printf("%d * sizeof *matrix = %lu\n", n, (unsigned long) n * sizeof *matrix);<br />
free(matrix);<br />
return 0;<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-40<br />
Hochschule Konstanz<br />
ANSI-C Abgeleitete Typen: Felder von Zeigern<br />
Felder von Zeigern am Beispiel einer 2x3-Matrix<br />
• Variablen-Definition: int line_0[3] = {10, 11, 12};<br />
int line_1[3] = {20, 21, 22};<br />
int ∗matrix[2] = {line_0, line_1};<br />
• Wert:<br />
Folge von Zeilen-Adressen<br />
• Platzbedarf: sizeof matrix ≡ 2 ∗ sizeof (int∗)<br />
• Indizierung: matrix[i][j] ≡ ∗(∗(matrix + i) + j)<br />
Recheneinheit<br />
sizeof (int)<br />
• Grafische Darstellung:<br />
line_0[]<br />
matrix[] [0] = 10<br />
[0] = line_0 [1] = 11<br />
[1] = line_1 [2] = 12<br />
line_1[]<br />
[0] = 20<br />
[1] = 21<br />
[2] = 22<br />
Recheneinheit<br />
sizeof (int*)<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-41<br />
Hochschule Konstanz
Beispiel-Programm Matrix-Doppelzeiger (1)<br />
• Quellcode:<br />
#include <br />
#include <br />
int main()<br />
{<br />
/* allocate and initialize memory for 2x3-matrix */<br />
const int n = 2; /* number of lines */<br />
const int m = 3; /* number of columns */<br />
int i, j;<br />
int **matrix = (int**) malloc(n * sizeof (int*));<br />
if (matrix == NULL) ... /* error handling */<br />
for (i = 0; i < n; ++i)<br />
{<br />
matrix[i] = (int*) malloc(m * sizeof (int));<br />
if (matrix[i] == NULL) ... /* error handling */<br />
}<br />
...<br />
sowohl Zeilen- als auch Spaltenanzahl<br />
müssen erst zur Laufzeit feststehen<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-42<br />
Hochschule Konstanz<br />
Beispiel-Programm Matrix-Doppelzeiger (2)<br />
• Fortsetzung Quellcode:<br />
...<br />
matrix[0][0] = 10;<br />
...<br />
matrix[1][2] = 22;<br />
wie Feld von Feldern (Folie 2-39)<br />
/* print matrix addresses and values */<br />
...<br />
wie Feld von Feldern (Folie 2-40),<br />
aber m statt M<br />
}<br />
/* free matrix memory */<br />
for (i = 0; i < n; ++i)<br />
{<br />
free(matrix[i]);<br />
}<br />
free(matrix);<br />
return 0;<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-43<br />
Hochschule Konstanz
ANSI-C Abgeleitete Typen: Vergleich mit Java<br />
Bei abgeleiteten Typen kaum Gemeinsamkeiten zwischen ANSI-C und Java:<br />
• ANSI-C Zeiger bieten sehr viel mehr Möglichkeiten als Java Referenzen<br />
in Java nur Referenzen auf Objekte im Heap<br />
in ANSI-C Zeiger auf jeden beliebigen Speicherbereich, auch auf dem Stack<br />
• ANSI-C kennt keinen echten Feld-Typ<br />
der Indexoperator ist nur eine Kurzschreibweise für Adressarithmetik<br />
und kann auf jede beliebige Adresse angewendet werden<br />
die Feldlänge wird nicht im Feld hinterlegt,<br />
deshalb beim Feldzugriff keine automatische Überwachung der Indexgrenzen<br />
in Java Felder nur im Heap, in ANSI-C auch auf dem Stack<br />
Felder von Feldern gibt es in Java nicht<br />
• ANSI-C kennt keinen echten String-Typ<br />
nur Felder von Zeichen mit ungültigem Zeichen '\0' als Endemarkierung<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-44<br />
Hochschule Konstanz<br />
ANSI-C Abgeleitete Typen: Empfehlungen<br />
• Zeiger-Typen sind ein zentrales Konzept von ANSI-C<br />
• Feld-Typen sind verkappte Verwandte der Zeiger<br />
der Name einer Feld-Variablen ist kein Name für einen Speicherbereich,<br />
sondern ein Name für die Adresse eines Speicherbereichs<br />
an Stelle von Feld-Variablen besser Zeiger auf mit calloc bzw. malloc<br />
dynamisch reservierten Speicher verwenden (free nicht vergessen!)<br />
an Stelle der Felder von Feldern besser Felder von Zeigern verwenden<br />
• Strings sind Felder von Einzelzeichen<br />
Speicher per Feld-Variable (vermeiden) oder besser dynamisch per malloc reservieren<br />
beim Speicherplatzbedarf das abschließende '\0'-Zeichen nicht vergessen!<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-45<br />
Hochschule Konstanz
ANSI-C Benutzerdefinierte Typen: enum<br />
Eine Aufzählung (Enumeration) definiert Namen für int-Literale.<br />
• Typ-Deklaration:<br />
Vorsicht:<br />
die Namen der<br />
Enumeratoren<br />
sind nicht lokal<br />
zur Typdeklaration!<br />
enum Enumname<br />
{<br />
Enumerator_1 = Wert_1,<br />
Enumerator_2 = Wert_2,<br />
...<br />
Enumerator_N = Wert_N<br />
};<br />
Die Angabe der<br />
Enumerator-Werte<br />
ist optional.<br />
Default-Wert für den<br />
ersten Enumerator ist 0,<br />
für die anderen der<br />
Vorgängerwert plus 1.<br />
• Variablen-Definition:<br />
• Wert:<br />
• Platzbedarf:<br />
enum Enumname Name = Enumerator;<br />
einer der Enumerator-Werte<br />
Enumerator-Werte können überall verwendet werden,<br />
wo int-Werte verwendet werden können.<br />
sizeof (enum Enumname) ≡ sizeof (int)<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-46<br />
Hochschule Konstanz<br />
Beispiel-Programm enum-Variable<br />
• Quellcode:<br />
#include <br />
enum month {jan = 1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec};<br />
int main()<br />
{<br />
/* enum month aMonth = 3; funktioniert bei C, aber nicht bei C++ */<br />
enum month aMonth = mar;<br />
}<br />
/* print variable value */<br />
printf("aMonth = %d\n", aMonth);<br />
/* print variable address */<br />
printf("&aMonth = %p\n", (void*) &aMonth);<br />
/* print variable size */<br />
printf("sizeof aMonth = %lu\n", (unsigned long) sizeof aMonth);<br />
return 0;<br />
Konsolenausgabe<br />
des Programms:<br />
aMonth = 3<br />
&aMonth = 0x22efc4<br />
sizeof aMonth = 4<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-47<br />
Hochschule Konstanz
ANSI-C Benutzerdefinierte Typen: struct (1)<br />
Eine Struktur fasst Werte beliebiger Typen zusammen.<br />
• Typ-Deklaration:<br />
struct Strukturname<br />
{<br />
Typ_1 Komponente_1;<br />
...<br />
Typ_N Komponente_N;<br />
};<br />
• Variablen-Definition:<br />
struct Strukturname Name = {Wert_1, ..., Wert_N};<br />
• Wert:<br />
• Platzbedarf:<br />
Folge der Komponenten-Werte.<br />
N<br />
∑<br />
i = 1<br />
• Grafische Darstellung:<br />
sizeof (Typ_i) ≤ sizeof(struct Strukturname)<br />
wegen Alignment der Komponenten<br />
Name<br />
Komponente_1 = Wert_1<br />
:<br />
Komponente_N = Wert_N<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-48<br />
Hochschule Konstanz<br />
ANSI-C Benutzerdefinierte Typen: struct (2)<br />
• Komponentenauswahl-Operatoren (Punkt und Pfeil):<br />
Name.Komponente_1<br />
Zeigername–>Komponente_1<br />
• Adresse einer Komponente:<br />
& Name . Komponente_1<br />
& Zeigername –> Komponente_1<br />
• Verkettete Strukturen enthalten einen Zeiger auf den eigenen Strukturtyp:<br />
struct int_list<br />
{<br />
struct int_list ∗next; /* Verkettung */<br />
int n;<br />
};<br />
struct int_list last = {NULL, 10};<br />
struct int_list first = {&last, 20};<br />
Pfeil ist Kurzschreibweise für<br />
(∗Zeigername).Komponente_2<br />
Adresse der ersten Komponente<br />
ist Adresse der Struktur insgesamt<br />
first<br />
next =<br />
n = 20<br />
last<br />
next = NULL<br />
n = 10<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-49<br />
Hochschule Konstanz
Beispiel-Programm struct-Variable<br />
• Quellcode:<br />
#include <br />
struct date<br />
{<br />
int day;<br />
const char *month;<br />
int year;<br />
};<br />
...<br />
...<br />
int main()<br />
{<br />
struct date d = {1, "September", 2000};<br />
}<br />
/* print variable value */<br />
printf("%d. %s %d\n", d.day, d.month, d.year);<br />
/* print variable address */<br />
printf("&d = %p\n", (void*) &d);<br />
printf("&d.day = %p\n", (void*) &d.day);<br />
printf("&d.month = %p\n", (void*) &d.month);<br />
printf("&d.year = %p\n", (void*) &d.year);<br />
/* print variable size */<br />
printf("sizeof d = %lu\n",<br />
(unsigend long) sizeof d);<br />
return 0;<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-50<br />
Hochschule Konstanz<br />
ANSI-C Benutzerdefinierte Typen: union (1)<br />
Eine Variante ist eine Struktur, bei der alle Komponenten dieselbe Adresse haben.<br />
• Typ-Deklaration:<br />
union Unionname<br />
{<br />
Typ_1 Variante_1;<br />
...<br />
Typ_N Variante_N;<br />
};<br />
zu einer Zeit kann<br />
nur eine der Varianten<br />
gespeichert sein<br />
nur die erste Variante<br />
kann initialisiert werden<br />
• Variablen-Definition:<br />
union Unionname Name = {Wert_1};<br />
• Wert:<br />
der Wert einer der Varianten<br />
• Platzbedarf: sizeof (union Unionname) ≡ MAX sizeof (Type_i )<br />
N<br />
i = 1<br />
• Grafische Darstellung:<br />
Name<br />
Variante_1 = Wert_1<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-51<br />
Hochschule Konstanz
ANSI-C Benutzerdefinierte Typen: union (2)<br />
• Variantenauswahl-Operatoren (Punkt und Pfeil):<br />
Name.Variante_2<br />
Zeigername–>Variante_2<br />
• unbenannte Varianten:<br />
enum int_or_string {type_int, type_string};<br />
struct struct_with_union<br />
{<br />
enum int_or_string u_type;<br />
union<br />
{<br />
hier kein Unionname<br />
int i;<br />
char ∗s;<br />
} u;<br />
};<br />
struct struct_with_union x;<br />
x.u_type = type_int;<br />
x.u.i = 1;<br />
x.u_type = type_string;<br />
x.u.s = "Hallo";<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-52<br />
Hochschule Konstanz<br />
ANSI-C Benutzerdefinierte Typen: typedef<br />
Eine typedef-Deklaration definiert lediglich einen Aliasnamen für einen Typ.<br />
• Deklaration:<br />
typedef Typname Aliasname;<br />
• Variablen-Definition:<br />
Typname Name;<br />
Aliasname Name;<br />
beide Definitionen<br />
sind gleichwertig<br />
• besonders nützlich bei enum-, struct und union-Typen:<br />
struct date<br />
{<br />
int day;<br />
};<br />
const char *month;<br />
int year;<br />
typedef struct date date;<br />
date ist Aliasname für struct date<br />
(gleicher Bezeichner für struct und Alias<br />
ist erlaubt und übliche Konvention)<br />
date d = {1, "September", 2000}; /* statt struct date d ... */<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-53<br />
Hochschule Konstanz
ANSI-C Benutzerdefinierte Typen: Vergleich mit Java<br />
Bei den benutzerdefinierten Typen große Unterschiede zwischen ANSI-C und Java:<br />
• enum-Typen sind sehr viel primitiver realisiert als in Java<br />
in ANSI-C eigentlich nur eine nette Schreibweise für ganzzahlige Konstanten<br />
• struct-Typen sind eine primitive Vorstufe der Java-Klassen<br />
nur öffentliche Instanzvariablen<br />
keine Methoden und Konstruktoren<br />
keine Vererbung<br />
auch Wert-Variablen möglich (in Java nur Speicherreservierung mit new)<br />
• union-Typen gibt es in Java nicht<br />
in Java wegen Vererbung und Polymorphie überflüssig<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-54<br />
Hochschule Konstanz<br />
ANSI-C Benutzerdefinierte Typen: Empfehlungen<br />
• enum-Typen sind nützlich für die Codierung nicht-numerischer Information.<br />
Verarbeitung oft mit switch-Anweisungen<br />
• struct-Typen sind das zentrale Konzept für benutzerdefinierte Typen<br />
verkettete Strukturen sind oft ein guter Ersatz für Felder<br />
• union-Typen gefährden die Typsicherheit<br />
vorzugsweise innerhalb eines struct-Typs als unbenannte Variante<br />
zusammen mit einer Typ-Komponente verwenden<br />
• typedef-Aliasnamen sind eine nützliche Schreibvereinfachung<br />
können Programme änderungsfreundlicher machen<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-55<br />
Hochschule Konstanz
ANSI-C Daten: Lernzettel<br />
#define Adresse Adressoperator Alignment Array calloc char const double<br />
enum Feld Feld von Feldern Feld von Zeigern float free Indexoperator<br />
Inhaltsoperator int Literal long malloc Pointer short signed sizeof size_t<br />
strcat strcmp strcpy strlen struct symbolische Konstante typedef union<br />
unsigned Variable void wchar_t Zeiger Zeigerarithmetik<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 2-56<br />
Hochschule Konstanz
<strong>Systemprogrammierung</strong><br />
<strong>Teil</strong> 3: ANSI-C Anweisungen<br />
Ausdrücke / Operatoren / Ablaufsteuerung<br />
Prof. Dr. H. Drachenfels Version 4.0<br />
Hochschule Konstanz 22.2.2013<br />
ANSI-C Anweisungen: Übersicht<br />
Ein Programm besteht aus Anweisungen (Statements):<br />
• Variablen-Definitionen<br />
Typ Name = Ausdruck; /* nur global oder am Anfang eines Anweisungsblocks */<br />
• Ausdrücke mit darauf folgendem Semikolon<br />
Ausdruck; /* besteht aus Literalen, Namen und Operatoren */<br />
; /* Spezialfall leere Anweisung */<br />
• Anweisungsblöcke in geschweiften Klammern<br />
{Anweisung Anweisung ...}<br />
{ } /* Spezialfall leere Anweisung */<br />
• Anweisungen zur Ablaufsteuerung (Kontrollstrukturen)<br />
Verzweigungen: if-else switch-case-default<br />
Schleifen:<br />
Sprünge:<br />
while do-while for<br />
break continue return goto<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-1<br />
Hochschule Konstanz
ANSI-C Operatoren: Übersicht (1)<br />
• Operatoren mit einem Operanden:<br />
1<br />
2<br />
3<br />
Operator Name Stelligkeit Assoziativität Vorrang 1<br />
++, -- Postfix-Inkrement/Dekrement unär – 2 1<br />
. Komponente Auswahl unär – 2 1<br />
-> Komponente Inhalt mit Auswahl unär – 2 1<br />
[ Index ] Indizierung unär 3 – 2 1<br />
( Parameterliste ) Methodenaufruf unär 3 – 2 1<br />
++, -- Präfix-Inkrement/Dekrement unär – 2 2<br />
+, - Vorzeichen Plus/Minus unär – 2 2<br />
! Logische Negation unär – 2 2<br />
~ Bitweise Invertierung unär – 2 2<br />
* Inhalt unär – 2 2<br />
& Adresse unär – 2 2<br />
(Typ ) Typanpassung unär – 2 2<br />
sizeof Speicherplatzbedarf unär – 2 2<br />
Sortierung vom höchsten Vorrang 1 bis niedrigstem Vorrang 15.<br />
Einstellige Operatoren haben keine Assoziativität. Sie werden von innen nach außen berechnet.<br />
In ( ) oder, [ ] geklammerte Parameter der Operatoren bleiben bei der Stelligkeit unberücksichtigt.<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-2<br />
Hochschule Konstanz<br />
ANSI-C Operatoren: Übersicht (2)<br />
• Operatoren mit zwei Operanden:<br />
Operator Name Stelligkeit Assoziativität Vorrang<br />
∗ Multiplikation binär links 3<br />
/ Division binär links 3<br />
% Modulo binär links 3<br />
+ Addition / Konkatenation Binär links 4<br />
- Subtraktion binär links 4<br />
> Rechts-Shift binär links 5<br />
< kleiner binär links 6<br />
größer binär links 6<br />
>= größer-gleich binär links 6<br />
== Gleichheit binär links 7<br />
!= Ungleichheit Binär links 7<br />
& bitweises Und binär links 8<br />
^ bitweises XOR binär links 9<br />
| bitweises Oder binär links 10<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-3<br />
Hochschule Konstanz
ANSI-C Operatoren: Übersicht (3)<br />
• weitere Operatoren mit zwei Operanden, bzw. in einem Fall mit drei Operanden:<br />
Operator Name Stelligkeit Assoziativität Vorrang<br />
&& Logisches Und binär links 11<br />
|| Logisches Oder binär links 12<br />
? : Bedingung ternär rechts 13<br />
= Zuweisung binär rechts 14<br />
+= Additions-Zuweisung binär rechts 14<br />
-= Subtraktions-Zuweisung binär rechts 14<br />
∗= Multiplikations-Zuweisung binär rechts 14<br />
/= Divisions-Zuweisung binär rechts 14<br />
%= Modulo-Zuweisung binär rechts 14<br />
&= Bitweise-XOR-Zuweisung binär rechts 14<br />
^= Bitweise-Oder-Zuweisung binär rechts 14<br />
|= Bitweise-Und-Zuweisung binär rechts 14<br />
= Rechts-Shift-Zuweisung binär rechts 14<br />
, Sequenz binär links 15<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-4<br />
Hochschule Konstanz<br />
ANSI-C Operatoren: Komma<br />
Der Komma-Operator bildet eine Ausdrucks-Sequenz<br />
• Syntax:<br />
Ausdruck1, Ausdruck2<br />
• der Datentyp des Ausdrucks insgesamt<br />
ist der Datentyp des Ausdrucks hinter dem Komma<br />
• der Wert des Ausdrucks insgesamt<br />
ist der Wert des Ausdrucks hinter dem Komma<br />
• Achtung: nicht jedes Komma in einem C-Programm ist ein Komma-Operator !<br />
int i, tmp;<br />
kein Komma-Operator<br />
(tmp = i, ++i, tmp); /* entspricht i++; */<br />
Komma-Operator<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-5<br />
Hochschule Konstanz
ANSI-C Operatoren: Logik und Vergleiche<br />
Logische Operatoren ! && || verknüpfen Werte mit Zahl- und Zeigertypen.<br />
Ein Zahl- oder Zeigerwert 0 wird dabei als false interpretiert.<br />
Jeder Zahl- oder Zeigerwert ungleich 0 wird dabei als true interpretiert.<br />
• der Datentyp eines logischen Ausdrucks ist int<br />
• der Wert eines logischen Ausdrucks ist 0 oder 1<br />
Achtung: Durch den fehlenden Typ boolean können die logischen Operatoren && und ||<br />
leicht mit den arithmetischen Bitoperatoren & und | verwechselt werden!<br />
Die Vergleichs-Operatoren < >= == !=<br />
prüfen eine Relation zwischen zwei Werten.<br />
• der Datentyp eines Vergleichs-Ausdrucks ist int<br />
• der Wert eines Vergleichs-Ausdrucks ist 1, wenn die Relation zutrifft, sonst 0<br />
Achtung: Durch den fehlenden Typ boolean kann in Bedingungen<br />
leicht der Gleichheitstest == mit der Zuweisung = verwechselt werden!<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-6<br />
Hochschule Konstanz<br />
ANSI-C Operatoren: Vergleich mit Java<br />
Bei den Operatoren gibt es wenig Unterschiede zwischen ANSI-C und Java:<br />
• der Sequenz-Operator , fehlt in Java<br />
• der Rechts-Shift ohne Vorzeichen >>> fehlt in ANSI-C<br />
• die Operatoren für Zeiger und Zeigerarithmetik &, *, ->, sizeof<br />
sind in Java überflüssig<br />
• der Typabfrage instanceof ist in ANSI-C überflüssig<br />
• in ANSI-C gibt es logisches Und/Oder nur als && bzw. || mit fauler Auswertung<br />
In Java haben die Operatoren & und | je nach Operandentyp eine andere Bedeutung:<br />
im Fall von boolean sind es logische Operatoren mit voller Auswertung beider Operanden<br />
im Fall von ganzen Zahlen sind es wie bei ANSI-C arithmetische Bitoperatoren<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-7<br />
Hochschule Konstanz
ANSI-C Ausdrücke: Auswertungs-Reihenfolge (1)<br />
• Die Auswertungs-Reihenfolge der Operatoren eines Ausdrucks<br />
wird bestimmt von Vorrang, Assoziativität und Klammerung:<br />
a = ( b + ~ c ++ + d ) ∗ (double) e + f [i]<br />
14 ( ( 4 2 1 ) 4 ) 3 2 4 1<br />
• eindeutig darstellbar als Auswertungsbaum:<br />
a b c d e f i<br />
Reihenfolge<br />
++<br />
wegen Vorrang [i]<br />
~ (double)<br />
+<br />
+<br />
=<br />
Reihenfolge<br />
wegen Links-<br />
Assoziativität<br />
∗<br />
Reihenfolge<br />
wegen<br />
Klammerung<br />
+<br />
Reihenfolge<br />
in jedem Ast<br />
von oben nach unten<br />
Reihenfolge<br />
zwischen den Ästen<br />
in der Regel beliebig<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-8<br />
Hochschule Konstanz<br />
ANSI-C Ausdrücke: Auswertungs-Reihenfolge (2)<br />
• Die Auswertungs-Reihenfolge der Operanden eines Operators<br />
ist in der Regel Compiler-abhängig.<br />
Man kann insbesondere nicht erwarten, dass<br />
ein Ausdruck von links nach rechts abgearbeitet wird.<br />
Beispiel: i = 0, v[i] = ++i<br />
Der Compiler darf Code erzeugen,<br />
der v[i] vor oder nach ++i auswertet.<br />
Je nachdem wird v[0] oder v[1] auf 1 gesetzt!<br />
• nur bei den folgenden vier Operatoren ist verbindlich festgelegt,<br />
dass der linke Operand vor dem rechten ausgewertet wird:<br />
Komma ,<br />
Bedingung ?:<br />
Logisches Und &&<br />
Logisches Oder ||<br />
Bei den Operatoren && bzw. ||<br />
wird der rechte Operand gar nicht ausgewertet,<br />
wenn der linke 0 bzw. ungleich 0 ist<br />
(Lazy Evaluation)<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-9<br />
Hochschule Konstanz
ANSI-C Ausdrücke: Makros<br />
Der ANSI-C-Präprozessor erlaubt es,<br />
häufig benötigte Ausdrücke als Makro zu definieren.<br />
• Definition eines Makros:<br />
#define Name(Parameterliste) Ausdruck<br />
die Parameterliste besteht nur aus durch Komma getrennten Namen ohne Typen<br />
• Benutzung eines Makros:<br />
nach der Definition kann ein Makro wie eine Funktion "aufgerufen" werden<br />
das Makro wird beim Übersetzen vom Präprozessor expandiert,<br />
d.h. durch den Ausdruck mit eingesetzten Argumenten ersetzt<br />
• Beispiel:<br />
#define max(a, b) ((a) > (b) ? (a) : (b))<br />
m = 2 * max(x + y, z); /* Benutzung des Makros */<br />
Achtung: unbedingt<br />
den ganzen Ausdruck<br />
und jeden Parameter<br />
klammern!<br />
m = 2 * ((x + y) > (z) ? (x + y) : (z)); /* expandiertes Makro */<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-10<br />
Hochschule Konstanz<br />
ANSI-C Ausdrücke: Vergleich mit Java<br />
Bei den Ausdrücken gibt es wichtige Unterschiede zwischen ANSI-C und Java:<br />
• in ANSI-C gibt es im Gegensatz zu Java mehrdeutige Ausdrücke<br />
• in ANSI-C gibt es keine echten logischen Ausdrücke<br />
dadurch Verwechslungsgefahr zwischen Arithmetik und Logik,<br />
(speziell zwischen Zuweisung = und Test auf Gleichheit ==<br />
sowie zwischen logischem Und/Oder und bitweisem Und/Oder)<br />
• in Java gibt es keine Makros<br />
dafür expandiert die Java Virtuelle Maschine zur Laufzeit automatisch<br />
häufig durchlaufene Aufrufe einfacher Methoden wie ein Makro<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-11<br />
Hochschule Konstanz
ANSI-C Ablaufsteuerung: Verzweigung<br />
Eine Verzweigung ermöglicht optionale und alternative Anweisungen.<br />
• Syntax:<br />
if (Bedingung) Anweisung /* falls Bedingung erfüllt */<br />
if (Bedingung)<br />
Anweisung1 /* falls Bedingung erfüllt */<br />
else<br />
Anweisung2 /* falls Bedingung nicht erfüllt */<br />
if (Bedingung1)<br />
Anweisung1 /* falls Bedingung1 erfüllt */<br />
else if (Bedingung2)<br />
Anweisung2 /* falls nur Bedingung2 erfüllt */<br />
else<br />
Anweisung3 /* falls keine Bedingung erfüllt */<br />
Eine Bedingung ist ein Ausdruck<br />
mit arithmetischem Typ oder Zeigertyp.<br />
Vorsicht bei<br />
geschachtelten<br />
Verzweigungen:<br />
Ein else-<strong>Teil</strong> gehört<br />
immer zum letzten<br />
noch offenen if.<br />
Eine andere Zuordnung<br />
muss mit geschweiften<br />
Klammern erzwungen<br />
werden:<br />
if (Bedingung1) {<br />
if (Bedingung2) ...<br />
} else {<br />
...<br />
}<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-12<br />
Hochschule Konstanz<br />
Beispielprogramm Verzweigung<br />
#include <br />
int main()<br />
{<br />
int m, n;<br />
}<br />
printf("Zwei Zahlen eingeben: ");<br />
if (scanf("%d%d", &m, &n) < 2)<br />
{<br />
fprintf(stderr, "Eingabefehler !\n");<br />
}<br />
else if (m > n)<br />
{<br />
printf("Maximum: %d\n", m);<br />
}<br />
else<br />
{<br />
printf("Maximum: %d\n", n);<br />
}<br />
return 0;<br />
Liest zwei ganze Zahlen ein und<br />
gibt deren Maximum aus.<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-13<br />
Hochschule Konstanz
ANSI-C Ablaufsteuerung: Fallunterscheidung<br />
Die Fallunterscheidung ist eine spezielle Schreibweise für Mehrfachverzweigungen.<br />
• Syntax: switch (Ausdruck) {<br />
case Wert1:<br />
Anweisung1<br />
break;<br />
case Wert2:<br />
Anweisung2<br />
break;<br />
default:<br />
Anweisung3<br />
}<br />
Im Prinzip gleichbedeutend mit:<br />
if (Ausdruck == Wert1)<br />
Anweisung1<br />
else if (Ausdruck == Wert2)<br />
Anweisung2<br />
else<br />
Anweisung3<br />
Der Ausdruck muss einen ganzzahligen Typ haben.<br />
Die case-Werte müssen dazu passende ganzzahlige Konstanten (bzw. enum-Werte) sein.<br />
Der default-Fall wird ausgeführt, wenn der Ausdruck keinen der case-Werte hat.<br />
Mit break wird die Fallunterscheidung verlassen.<br />
Ohne break z.B. hinter Anweisung1 würde<br />
nach Anweisung1 die Anweisung2 ausgeführt<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-14<br />
Hochschule Konstanz<br />
Beispielprogramm Fallunterscheidung (1)<br />
#include <br />
int main()<br />
{<br />
int month;<br />
printf("Monat eingeben [1-12]: ");<br />
if (scanf("%d", &month) < 1)<br />
{<br />
month = 0;<br />
}<br />
switch (month)<br />
{<br />
case 2:<br />
printf("28 oder 29 Tage\n");<br />
break;<br />
case 4:<br />
case 6:<br />
case 9:<br />
case 11:<br />
printf("30 Tage\n");<br />
break;<br />
Gibt die Anzahl der Tage<br />
eines Monats aus.<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-15<br />
Hochschule Konstanz
Beispielprogramm Fallunterscheidung (2)<br />
/* Fortsetzung ... */<br />
}<br />
}<br />
case 1:<br />
case 3:<br />
case 5:<br />
case 7:<br />
case 8:<br />
case 10:<br />
case 12:<br />
printf("31 Tage\n");<br />
break;<br />
default:<br />
fprintf(stderr, "Eingabefehler!\n");<br />
return 0;<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-16<br />
Hochschule Konstanz<br />
ANSI-C Ablaufsteuerung: Schleifen (1)<br />
Eine Schleife ermöglicht die wiederholte Ausführung einer Anweisung.<br />
• Syntax der while-Schleife:<br />
while (Bedingung)<br />
Anweisung<br />
Wiederholt die Anweisung, solange die Bedingung gilt.<br />
• Syntax der do-Schleife:<br />
do<br />
Anweisung<br />
while (Bedingung);<br />
Führt die Anweisung aus und wiederholt sie dann,<br />
solange die Bedingung gilt.<br />
Gleichbedeutend mit:<br />
{<br />
Anweisung<br />
while (Bedingung)<br />
Anweisung<br />
}<br />
Eine Bedingung ist wie gehabt ein Ausdruck mit arithmetischem Typ oder Zeigertyp.<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-17<br />
Hochschule Konstanz
ANSI-C Ablaufsteuerung: Schleifen (2)<br />
Die for-Schleife ist eine spezielle Schreibweise für Schleifen mit Laufvariablen.<br />
Die Laufvariable muss vor der Schleife definiert werden.<br />
for-Schleifen werden häufig benutzt, um Felder oder Listen (allgemein: Aggregate) abzulaufen.<br />
Dabei werden die aggregierten Elemente über eine Laufvariable angesprochen.<br />
• Syntax der allgemeinen for-Schleife:<br />
for (Initialisierung; Bedingung; Fortschaltung)<br />
Anweisung<br />
Die Initialisierung ist ein Ausdruck, der die Laufvariable<br />
auf das erste Element des Aggregats setzt.<br />
Die Fortschaltung ist eine Ausdruck, der die Laufvariable<br />
auf das nächst folgende Element des Aggregats setzt.<br />
Gleichbedeutend mit:<br />
{<br />
Initialisierung;<br />
while (Bedingung)<br />
{<br />
Anweisung<br />
Fortschaltung;<br />
}<br />
}<br />
Die Bedingung prüft, ob alle Elemente besucht wurden.<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-18<br />
Hochschule Konstanz<br />
Beispielprogramm while-Schleife<br />
#include <br />
int main ()<br />
{<br />
int sum = 0;<br />
int n;<br />
Liest ganze Zahlen ein und<br />
gibt deren Summe aus.<br />
printf("Zahlen eingeben (Ende mit Strg-d): ");<br />
while (scanf("%d", &n) == 1)<br />
{<br />
sum += n;<br />
}<br />
printf("Summe: %d\n", sum);<br />
}<br />
return 0;<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-19<br />
Hochschule Konstanz
Beispielprogramm do-Schleife<br />
#include <br />
int main()<br />
{<br />
int n = 0;<br />
/* Dezimalzahl einlesen */<br />
do<br />
{<br />
printf("Zahl zwischen 0 und 255 eingeben: ");<br />
}<br />
while (scanf("%d", &n) == 1<br />
&& (n < 0 || n > 255));<br />
...<br />
}<br />
...<br />
Liest eine ganze Zahl ein und<br />
gibt sie in Binärdarstellung aus.<br />
/* Binaerzahl ausgeben */<br />
printf(" "); /* 7 Leerzeichen */<br />
do<br />
{<br />
printf("%d\b\b", n % 2);<br />
n /= 2;<br />
}<br />
while (n > 0);<br />
printf("\n");<br />
return 0;<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-20<br />
Hochschule Konstanz<br />
Beispielprogramm for-Schleife<br />
#include <br />
Gibt Feldelemente aus.<br />
int main()<br />
{<br />
int an_array[] = {3082, 3101, 3275, 3436};<br />
const int array_size = (int) (sizeof an_array / sizeof *an_array);<br />
int i; /* Laufvariable */<br />
for (i = 0; i < array_size; ++i)<br />
{<br />
printf("%d\n", an_array[i]);<br />
}<br />
}<br />
return 0;<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-21<br />
Hochschule Konstanz
ANSI-C Ablaufsteuerung: Sprünge (1)<br />
• Eine break-Anweisung springt hinter<br />
die umgebende Fallunterscheidung / Schleife:<br />
while (...)<br />
{<br />
...<br />
if (Bedingung) break;<br />
...<br />
}<br />
... /* break springt hier hin */<br />
Gleichbedeutend mit:<br />
int stop = 0;<br />
while (... && !stop)<br />
{<br />
...<br />
if (Bedingung)<br />
stop = 1;<br />
else<br />
...<br />
}<br />
• Eine continue-Anweisung springt zum<br />
nächsten Schleifen-Durchlauf:<br />
while (...)<br />
{<br />
if (Bedingung) continue;<br />
...<br />
}<br />
Gleichbedeutend mit:<br />
while (...)<br />
{<br />
if (!Bedingung)<br />
{<br />
...<br />
}<br />
}<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-22<br />
Hochschule Konstanz<br />
ANSI-C Ablaufsteuerung: Sprünge (2)<br />
• Eine goto-Anweisung springt zu einer markierten Anweisung:<br />
for (...) {<br />
for (...) {<br />
if (Bedingung) goto ende;<br />
...<br />
}<br />
}<br />
ende:<br />
...<br />
verlässt in einem Schritt die beiden<br />
ineinander geschachtelten Schleifen<br />
goto-Anweisung sollten vermieden werden<br />
den obigen Schleifenabbruch kann man ohne goto lösen, indem man<br />
die Schleifen in eine Funktion verlegt und diese per return verlässt<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-23<br />
Hochschule Konstanz
ANSI-C Ablaufsteuerung: Sprünge (3)<br />
Eine return-Anweisung springt an die Aufrufstelle einer Funktion zurück.<br />
Genaueres später bei den Funktionen.<br />
• Innerhalb von main beendet return das Programm:<br />
int main() {<br />
...<br />
if (Bedingung) return 1;<br />
...<br />
return 0;<br />
}<br />
Ein Rückgabewert 0 gilt als normales Programmende,<br />
ein Rückgabewert ungleich 0 gilt als Fehlerabbruch<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-24<br />
Hochschule Konstanz<br />
Beispielprogramm Sprünge (1)<br />
#include <br />
#include <br />
Liest ganze Zahlen ein und<br />
gibt deren Summe aus.<br />
int main()<br />
{<br />
int sum = 0;<br />
int n;<br />
printf("Zahlen eingeben (Ende mit Strg-D): ");<br />
while (1) /* Endlos-Schleife, alternativ auch for (;;) */<br />
{<br />
int i = scanf("%d", &n);<br />
if (i == EOF) /* Strg-D ? */<br />
{<br />
fprintf(stderr, "*** Eingabeende\n");<br />
break; /* hinter die Schleife springen */<br />
}<br />
...<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-25<br />
Hochschule Konstanz
Beispielprogramm Sprünge (2)<br />
}<br />
}<br />
...<br />
else if (i == 0)<br />
{<br />
char c;<br />
}<br />
fprintf(stderr, "*** Eingabe ist keine ganze Zahl: ");<br />
while ((c = getchar()) != EOF && !isspace(c))<br />
{<br />
putc(c, stderr);<br />
}<br />
putc('\n', stderr);<br />
continue; /* zum naechsten Schleifendurchlauf springen */<br />
sum += n;<br />
printf("Summe: %d\n", sum);<br />
return 0; /* normales Programmende */<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-26<br />
Hochschule Konstanz<br />
ANSI-C Anweisungen: Empfehlungen (1)<br />
• Leerzeichen machen Ausdrücke lesbarer, unnötige Klammern nicht unbedingt:<br />
a + b ∗ c a+(b*c) /* Klammern unnötig */<br />
(a + b) ∗ c (a+b)*c /* Klammern notwendig */<br />
• Ausdrücke mit Seiteneffekten vermeiden:<br />
a = b + c++; /* Seiteneffekt auf c */<br />
a = b + c; /* Aufteilung meistens besser */<br />
++c;<br />
• Nur eine Anweisung pro Zeile schreiben, Kontrollstrukturen mehrzeilig schreiben.<br />
if (Bedingung)<br />
{<br />
if (Bedingung) Anweisung;<br />
Anweisung;<br />
}<br />
Vereinfacht erheblich die Fehlersuche und Qualitätssicherung mit Werkzeugen<br />
wie Compiler, Debugger usw.<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-27<br />
Hochschule Konstanz
ANSI-C Anweisungen: Empfehlungen (2)<br />
• Durch Zwischenraum (Whitespace), Klammerung und Einrückung<br />
die Blockstruktur der Ablaufsteuerung verdeutlichen:<br />
if (Bedingung1)<br />
{<br />
Anweisung1<br />
Anweisung2<br />
}<br />
while (Bedingung2)<br />
{<br />
Anweisung3<br />
}<br />
Einrückung in geklammerten Blöcken<br />
(üblicherweise 4 Leerzeichen)<br />
Leerzeile zwischen geklammerten Blöcken<br />
öffnende und schließende Klammer linksbündig<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-28<br />
Hochschule Konstanz<br />
ANSI-C Anweisungen: Vergleich mit Java<br />
Bei den Anweisungen gibt es einige Unterschiede zwischen ANSI-C und Java:<br />
• in ANSI-C Variablen-Definitionen nur am Anfang eines Blocks<br />
• in ANSI-C keine Ausnahmebehandlung mit try/catch/throw<br />
• in ANSI-C keine vereinfachte for(T element : alleElemente)-Schleife<br />
außerdem kann die Laufvariable einer for-Schleife nicht erst im Schleifenkopf,<br />
sondern muss vor der Schleife definiert werden<br />
• in ANSI-C gibt es die Sprünge break und continue nur ohne Marke<br />
Marken können nur mit goto angesprungen werden, was aber vermieden werden sollte<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-29<br />
Hochschule Konstanz
ANSI-C Anweisungen: Lernzettel<br />
Anweisung Auswertungsreihenfolge break case default continue do else<br />
for goto if Kommaoperator Makro Statement switch while<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 3-30<br />
Hochschule Konstanz
<strong>Systemprogrammierung</strong><br />
<strong>Teil</strong> 4: ANSI-C Programme<br />
Funktionen / Übersetzungseinheiten / Bibliotheken<br />
Prof. Dr. H. Drachenfels Version 7.0<br />
Hochschule Konstanz 20.2.2014<br />
ANSI-C Programme: Aufbau<br />
Ein C-Programm ist technisch eine Sammlung von Funktionen und globalen Daten.<br />
• genau eine der Funktionen muss main heissen<br />
Bei main beginnt die Ausführung des Programms.<br />
Ein C-Programm ist organisatorisch eine Sammlung von Übersetzungseinheiten.<br />
• eine Übersetzungseinheit enthält Definitionen<br />
einiger logisch zusammengehöriger Funktionen und Daten<br />
• Übersetzungseinheiten dienen dazu, Programme überschaubar zu gliedern<br />
• logisch zusammengehörige Übersetzungseinheiten<br />
werden wiederum in Bibliotheken zusammengefasst.<br />
größere Programme enthalten leicht hunderte Übersetzungseinheiten<br />
und tausende Funktionen, von denen viele nicht selbst implementiert sind,<br />
sondern aus (gekauften) Bibliotheken stammen.<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-1<br />
Hochschule Konstanz
ANSI-C Funktionen: Eigenschaften<br />
Funktionen (Unterprogramme, Prozeduren)<br />
fassen Folgen von Anweisungen zusammen, die immer wieder gebraucht werden.<br />
• eine Funktion hat einen Namen:<br />
Namens-Konvention wie bei Variablen<br />
• eine Funktion kann Parameter und einen Rückgabewert haben:<br />
dienen der Übergabe von zu verarbeitenden Werten und zu liefernden Ergebnissen<br />
• eine Funktion hat einen Typ:<br />
legt Anzahl und Typen der Parameter sowie den Typ des Rückgabe-Werts fest<br />
• eine Funktion hat einen Rumpf:<br />
enthält Variablen-Definitionen und Anweisungen.<br />
• eine Funktion hat eine Adresse:<br />
die Anfangsadresse ihres ausführbaren Codes im Hauptspeicher<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-2<br />
Hochschule Konstanz<br />
ANSI-C Funktionen: Syntax (1)<br />
• Funktions-Prototyp (Funktions-Deklaration):<br />
Typ Name(Parameterliste);<br />
void Name(Parameterliste); /* ohne Rückgabewert */<br />
Typ Name(void); /* ohne Parameter */<br />
Erst nach ihrer Deklaration ist eine Funktion benutzbar.<br />
Die Parameterliste besteht aus durch Komma getrennten Typnamen.<br />
Zusätzlich können (und sollten) Parameternamen angegeben werden.<br />
• Beispiel:<br />
Parameter-Namen(dürfen auch fehlen)<br />
int max(int a, int b);<br />
Rückgabetyp<br />
Funktions-Name<br />
Parameter-Typen<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-3<br />
Hochschule Konstanz
ANSI-C Funktionen: Syntax (2)<br />
• Funktions-Definition:<br />
Rückgabetyp Name(Parameterliste)<br />
{<br />
Anweisungen<br />
}<br />
Der Kopf muss genau dem Prototyp entsprechen, aber Parameternamen sind Pflicht.<br />
Der Rumpf enthält mindestens eine return-Anweisung:<br />
return Rückgabewert; /* Typ des Werts muss zum Rückgabetyp passen */<br />
return; /* bei void-Funktionen (darf am Ende des Rumpfs auch fehlen) */<br />
• Beispiel: int max(int a, int b)<br />
{<br />
if (a > b) return a;<br />
return b;<br />
}<br />
Kopf<br />
Rumpf<br />
Funktion mit Wert a verlassen<br />
Funktion mit Wert b verlassen<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-4<br />
Hochschule Konstanz<br />
ANSI-C Funktionen: main<br />
Jedes Programm muss genau eine Funktion mit dem Namen main enthalten.<br />
• es gibt eine Variante mit und eine ohne Parameter:<br />
int main(int argc, char *argv[])<br />
{<br />
}<br />
...<br />
Erklärung der Parameter:<br />
argv<br />
argc + 1<br />
Feld von String-Pointern (argument vector).<br />
Feldgröße (argument count).<br />
int main()<br />
{<br />
...<br />
}<br />
argv[0]<br />
argv[1]<br />
argv[argc – 1]<br />
argv[argc]<br />
Programm-Name (Kommando)<br />
erstes Kommandozeilen-Argument<br />
letztes Kommandozeilen-Argument<br />
0 (NULL-Pointer)<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-5<br />
Hochschule Konstanz
Beispiel-Programm main<br />
• Quellcode:<br />
#include <br />
int main(int argc, char *argv[])<br />
{<br />
int i;<br />
for (i = 0; i
ANSI-C Funktionen: Aufruf<br />
• der Aufruf-Operator () veranlasst die Ausführung einer Funktion<br />
und liefert den Rückgabe-Wert der Funktion:<br />
Name(Argumentliste)<br />
Zeigernname(Argumentliste)<br />
Die Argumentliste besteht aus durch Komma getrennten Ausdrücken.<br />
Die Parameter der Funktion werden mit den Werten der Argumentliste initialisiert.<br />
• Beispiel:<br />
Aufruf über Funktions-Name<br />
int z = max(7, 8); /* setzt z auf 8 */<br />
Aufruf über Funktions-Zeiger<br />
int (∗maximum)(int , int ) = max;<br />
int z = maximum(7, 8); /* setzt z auf 8 */<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-8<br />
Hochschule Konstanz<br />
ANSI-C Funktionen: globale und lokale Variablen<br />
• Globale Variablen<br />
Definition außerhalb der Funktionsrümpfe (dann in vielen Funktionen benutzbar)<br />
oder static-Definition innerhalb eines Funktionsrumpfs (dann funktions-privat)<br />
Lebensdauer:<br />
Speicherort:<br />
Programmlauf<br />
Daten-Segment<br />
• Lokale Variablen (automatische Variablen)<br />
Definition innerhalb eines Funktionsrumpfs am Anfang eines Anweisungsblock {},<br />
dadurch nur innerhalb dieses Anweisungsblocks benutzbar<br />
Lebensdauer:<br />
Speicherort:<br />
• Parameter<br />
Ausführung des Anweisungsblocks<br />
Stack-Segment<br />
spezielle lokale Variablen, Definition im Funktionskopf,<br />
Initialisierung mit den Argumenten des Funktionsaufrufs<br />
Lebensdauer:<br />
Speicherort:<br />
Ausführung der Funktion<br />
Stack-Segment<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-9<br />
Hochschule Konstanz
Beispiel-Programm globale und lokale Variablen<br />
• Quellcode:<br />
int function(int param);<br />
int global = 1;<br />
int main()<br />
{<br />
int local = 1;<br />
local = function(local); /* local wird 4 */<br />
local = function(global); /* local wird 7 */<br />
return 0;<br />
}<br />
int function(int param)<br />
{<br />
static int private_global = 1;<br />
}<br />
int local = param + 1;<br />
private_global++;<br />
global = param + 2;<br />
return local + private_global;<br />
Funktions-Prototyp<br />
Funktions-Definition (Implementierung)<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-10<br />
Hochschule Konstanz<br />
ANSI-C Funktionen: Hauptspeicherbelegung (1)<br />
main:<br />
PC<br />
function:<br />
SB<br />
& global:<br />
& private_global:<br />
Code-Segment<br />
Daten-Segment<br />
Der Prozessor merkt sich<br />
• die Adresse des<br />
nächsten Befehls im<br />
Program Counter PC<br />
• das aktuelle Ende des<br />
Stack-Segments im<br />
Stack Pointer SP<br />
Heap-Segment<br />
der Übersichtlichkeit<br />
wegen weggelassen<br />
Der Prozessor adressiert<br />
• globale Daten relativ zur<br />
Static Base SB<br />
SP<br />
FP<br />
& local:<br />
Stack-Segment<br />
• lokale Daten relativ zum<br />
Frame Pointer FP<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-11<br />
Hochschule Konstanz
ANSI-C Funktionen: Hauptspeicherbelegung (2)<br />
main:<br />
function:<br />
PC<br />
SB<br />
& global:<br />
& private_global:<br />
Code-Segment<br />
Daten-Segment<br />
Ein Funktions-Aufruf<br />
benutzt den Stack:<br />
• zum Speichern von<br />
Argumenten<br />
(hier: param)<br />
• zum Speichern der<br />
Rückkehrinformation<br />
(alter PC und FP)<br />
SP<br />
FP<br />
& local:<br />
& param:<br />
& local:<br />
FP<br />
PC<br />
Stack-Segment<br />
der Rückgabewert wird<br />
in einem Prozessorregister<br />
oder auf dem Stack geliefert<br />
(hier nicht gezeigt)<br />
• zum Reservieren von Platz<br />
für die lokalen Variablen<br />
(hier: local)<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-12<br />
Hochschule Konstanz<br />
ANSI-C Funktionen: Eingabe-Parameter<br />
Mit Eingabe-Parametern übergibt ein Aufrufer Werte an eine Funktion:<br />
• bei Grundtyp-Parametern Übergabe per Wertkopie<br />
void funktion(int zahl);<br />
• bei Zeiger- und Feld-Parametern Übergabe per Adresskopie<br />
void funktion(const int ∗zeiger);<br />
void funktion(int feldlaenge, const int feld[]);<br />
• bei Strukturtyp-Parametern Übergabe per Adresskopie<br />
Wertkopie ist bei großen Strukturen zu ineffizient<br />
struct struktur { ... };<br />
void funktion(const struct struktur *s);<br />
const verwenden,<br />
damit die Funktion<br />
nur Lesezugriff<br />
auf den Speicher<br />
des Aufrufers hat<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-13<br />
Hochschule Konstanz
ANSI-C Funktionen: Ausgabe-Parameter<br />
Mit Ausgabe-Parametern übergibt eine Funktion Werte an ihren Aufrufer:<br />
• Adresskopie verwenden<br />
void funktion(int ∗zeiger);<br />
void funktion(int feldlaenge, int feld[]);<br />
void funktion(struct struktur *s);<br />
kein const,<br />
damit die Funktion<br />
Schreibzugriff<br />
auf den Speicher<br />
des Aufrufers hat<br />
• Funktionen mit Ausgabe-Parametern bezeichnet man<br />
auch als Funktionen mit Seiteneffekten.<br />
Nach der reinen Lehre sollten Funktionen als Ergebnis<br />
nur einen Rückgabe-Wert liefern (return-Anweisung).<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-14<br />
Hochschule Konstanz<br />
ANSI-C Funktionen: Vergleich mit Java<br />
Bei den Funktionen gibt es deutliche Unterschiede zwischen ANSI-C und Java:<br />
• die ANSI-C Funktionen entsprechen im Prinzip den Java Klassenmethoden<br />
und ANSI-C globale Variablen entsprechen den Java Klassenvariablen<br />
weil es keine umschließenden Klassen gibt, liegen anders als bei Java<br />
die Namen aller Funktionen und globalen Variablen in einem gemeinsamen Namensraum<br />
• ANSI-C Funktionen können nicht überladen werden<br />
der Funktionsname muss ohne Betrachtung der Parametertypen eindeutig sein<br />
• Ausgabeparameter gibt es in Java nicht<br />
es gibt lediglich Seiteneffekte auf per Eingabeparameter übergebene Objekte<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-15<br />
Hochschule Konstanz
ANSI-C Übersetzungseinheiten: Aufbau<br />
Ein C-Programm wird in Module gegliedert, die sich getrennt übersetzen lassen<br />
(Übersetzungseinheiten).<br />
• jede Übersetzungseinheit besteht aus einer .h-Datei und einer .c-Datei:<br />
Name.h<br />
Name.c<br />
Header-Datei<br />
Implementierungs-Datei<br />
beim Hauptprogramm (Übersetzungseinheit mit nur der Funktion main )<br />
entfällt die Header-Datei<br />
• die Header-Dateien werden mit Präprozessor-Anweisungen<br />
in Implementierungs-Dateien oder andere Header-Dateien hineinkopiert,<br />
immer wenn Deklarationen daraus verwendet werden:<br />
#include "Name.h"<br />
• nur die Implementierungs-Dateien werden mit dem Compiler übersetzt<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-16<br />
Hochschule Konstanz<br />
ANSI-C Übersetzungseinheiten: Header-Dateien<br />
Eine Header-Datei enthält die Schnittstelle der Übersetzungseinheit, d.h.<br />
Deklarationen von Namen, die in anderen Übersetzungseinheiten sichtbar sein sollen<br />
• benutzerdefinierte Typen:<br />
struct date<br />
{<br />
int day, month, year;<br />
};<br />
typedef struct date date;<br />
• symbolische Konstanten und Makros:<br />
#define MAXDAY 31<br />
• globale Variablen (extern-Deklaration ohne Initialisierung):<br />
extern date epoch; /* wegen extern keine Speicherreservierung */<br />
• Funktions-Prototypen:<br />
void print_date(const date *d);<br />
wenn die Strukturkomponenten<br />
privat sein sollen:<br />
typedef struct date date;<br />
dann in anderen Übersetzungseinheiten<br />
nur date-Zeiger möglich<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-17<br />
Hochschule Konstanz
ANSI-C Übersetzungseinheiten: Implementierungs-Dateien<br />
Eine Implementierungs-Datei enthält die Implementierung der Schnittstelle.<br />
• Kopie der eigenen Schnittstelle (und ggf. der verwendeten Schnittstellen):<br />
#include "date.h"<br />
• Definition der Schnittstellenvariablen (ohne extern und mit Initialisierung):<br />
date epoch = {1, 1, 1970}; /* Start der Unix Zeitrechnung */<br />
• Definition der Schnittstellen-Funktionen:<br />
void print_date(const date *d)<br />
{<br />
printf("%d.%d.%d\n", d->day, d->month, d->year);<br />
}<br />
• private Deklarationen und Definitionen<br />
Typen, symbolische Konstanten, Makros, globale Variablen, Funktionen,<br />
die nur innerhalb der Übersetzungseinheit verwendet werden<br />
private globale Variablen und Funktionen werden mit static markiert<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-18<br />
Hochschule Konstanz<br />
ANSI-C Übersetzungseinheiten: Präprozessor-Anweisungen<br />
Der Präprozessor des C-Compilers<br />
nimmt vor der eigentlichen Übersetzung Textersetzungen vor.<br />
• Präprozessor-Anweisungen stehen in Zeilen, die mit # beginnen<br />
• eine #include-Anweisung muss immer verwendet werden,<br />
wenn in einer Datei (egal, ob Header- oder Implementierungs-Datei)<br />
ein Name aus einer anderen Übersetzungseinheit verwendet wird<br />
#include "Name.h"<br />
ersetzt der Präprozessor rekursiv durch den Inhalt von Name.h<br />
• #ifndef/#define/#endif-Anweisungen in allen Header-Dateien sorgen dafür,<br />
dass deren Inhalte auch bei verschachtelten #include-Anweisungen<br />
höchstens einmal in jede Implementierungs-Datei kopiert werden<br />
#ifndef NAME_H<br />
#define NAME_H<br />
... /* Inhalt der Header-Datei Name.h */<br />
#endif<br />
if not defined :<br />
beim ersten #include ist<br />
NAME_H noch nicht definiert,<br />
ab dem zweiten #include wird<br />
der #ifndef-Block übersprungen<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-19<br />
Hochschule Konstanz
Beispiel-Programm Übersetzungseinheiten (1)<br />
Globale Variable als getrennte Übersetzungseinheit (siehe zum Vergleich Folie 4-10)<br />
• Header-Datei:<br />
/* global.h */<br />
#ifndef GLOBAL_H<br />
#define GLOBAL_H<br />
extern int global;<br />
• Implementierungs-Datei:<br />
/* global.c */<br />
#include "global.h"<br />
int global = 1;<br />
#endif<br />
#ifndef / #define / #endif<br />
verhindert mehrfaches Kopieren<br />
in dieselbe Implementierungs-Datei<br />
die Implementierungs-Datei<br />
einer Übersetzungseinheit enthält<br />
immer die eigene Header-Datei<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-20<br />
Hochschule Konstanz<br />
Beispiel-Programm Übersetzungseinheiten (2)<br />
Funktion als getrennte Übersetzungseinheit (siehe zum Vergleich Folie 4-10)<br />
• Header-Datei:<br />
/* function.h */<br />
#ifndef FUNCTION_H<br />
#define FUNCTION_H<br />
int function(int param);<br />
#endif<br />
die Funktion benutzt einen Namen<br />
aus der Übersetzungseinheit global<br />
(deshalb #include "global.h")<br />
• Implementierungs-Datei:<br />
/* function.c */<br />
#include "function.h"<br />
#include "global.h"<br />
int function(int param)<br />
{<br />
static int private_global = 1;<br />
int local = param + 1;<br />
private_global++;<br />
global = param + 2;<br />
return local + private_global;<br />
}<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-21<br />
Hochschule Konstanz
Beispiel-Programm Übersetzungseinheiten (3)<br />
Hauptprogramm als getrennte Übersetzungseinheit (siehe zum Vergleich Folie 4-10)<br />
• Header-Datei:<br />
entfällt<br />
das Hauptprogramm<br />
benutzt Namen aus den<br />
Übersetzungseinheiten<br />
function und global<br />
• Implementierungs-Datei:<br />
/* localglobalvar.c */<br />
#include "function.h"<br />
#include "global.h"<br />
int main()<br />
{<br />
int local = 1;<br />
}<br />
local = function(local); /* local wird 2 */<br />
global = function(local); /* global wird 3 */<br />
local = function(global); /* local wird 4 */<br />
return 0;<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-22<br />
Hochschule Konstanz<br />
ANSI-C Übersetzungseinheiten: Compiler und Linker-Aufrufe (1)<br />
Compiler/Linker-Aufrufe bei Programmen mit mehren Übersetzungseinheiten:<br />
• jede Übersetzungseinheit getrennt übersetzen:<br />
gcc -c –I. function.c<br />
gcc -c –I. global.c<br />
gcc -c –I. localglobalvar.c<br />
die Option -I. gibt an, dass die Header-Dateien<br />
im lokalen Verzeichnis zu suchen sind<br />
(mehrere Optionen -IVerzeichnisname möglich)<br />
• dann den Objektcode der Übersetzungseinheiten (Endung .o)<br />
zu einem ausführbaren Programm binden:<br />
gcc localglobalvar.o function.o global.o -o localglobalvar<br />
das ausführbare Programm nennt man üblicherweise so<br />
wie die Übersetzungseinheit mit dem Hauptprogramm main<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-23<br />
Hochschule Konstanz
ANSI-C Übersetzungseinheiten: Compiler und Linker-Aufrufe (2)<br />
Die Übersetzungsschritte im Einzelnen:<br />
Übersetzungssschritt Eingabe Ergebnis Aufruf<br />
Präprozessor<br />
function.c<br />
function.h<br />
global.h<br />
function.i gcc -E<br />
Compiler function.i function.s gcc -S<br />
Assembler function.s function.o gcc -c<br />
• üblicherweise alle Übersetzungsschritte mit einem Aufruf:<br />
gcc -c function.c<br />
die obigen Zwischenschritte sind aber manchmal bei der Fehlersuche hilfreich<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-24<br />
Hochschule Konstanz<br />
ANSI-C Übersetzungseinheiten: Bibliotheken<br />
Bibliotheken fassen mehrere Übersetzungseinheiten in einer Datei zusammen:<br />
• Statische Bibliothek (unter Linux Präfix lib und Endung .a für Archiv)<br />
ar rs libbeispiel.a function.o global.o<br />
Eine statische Bibliothek ist eine Sammlung von Objekt-Dateien.<br />
• Dynamische Bibliothek (unter Linux Präfix lib und Endung .so für Shared Object)<br />
gcc -shared function.o global.o -o libbeispiel.so<br />
Eine dynamische Bibliothek ist quasi ein gebundenes Programm ohne Hauptprogramm.<br />
Beim Binden ersparen Bibliotheken das Aufzählen aller Übersetzungseinheiten:<br />
gcc localglobalvar.o -L. –lbeispiel -o localglobalvar<br />
Mit -L wird das Verzeichnis und mit -l der Name ohne Präfix der Bibliothek angegeben.<br />
Eine statische Bibliothek wird nach dem Binden nicht mehr gebraucht.<br />
Eine dynamische Bibliothek muss zur Laufzeit des Programms zugreifbar sein<br />
(dazu Verzeichnis in der Umgebungsvariablen LD_LIBRARY_PATH angeben).<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-25<br />
Hochschule Konstanz
ANSI-C Programme: Deployment<br />
Archive fassen mehrere Dateien in einer Datei zusammen.<br />
Sie erleichtern das Deployment (Distribution von Programmen auf Zielrechner)<br />
• Linux-Beispiel: tar-Kommando<br />
tar czf beispiel.tar.gz localglobalvar libbeispiel.so<br />
erzeugt ein mit gzip komprimiertes Archiv beispiel.tar.gz,<br />
das die Dateien localglobalvar und libbeispiel.so enthält<br />
tar xzf beispiel.tar.gz<br />
extrahiert die im Archiv enthaltenen Dateien wieder<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-26<br />
Hochschule Konstanz<br />
ANSI-C Übersetzungseinheiten: Vergleich mit Java<br />
Bei den Übersetzungseinheiten unterscheiden sich ANSI-C und Java erheblich:<br />
• bei Java sind Klassen zugleich Übersetzungseinheiten<br />
da Methoden und Variablen immer in Klassen enthalten sind, kann der Compiler<br />
über den qualifizierten Klassennamen immer die richtige Übersetzungseinheit finden<br />
• bei ANSI-C ist der Name einer Übersetzungseinheit unabhängig vom Inhalt<br />
weil es weder Klassen noch Pakete gibt, kann der Compiler einem Namen nicht ansehen,<br />
in welcher Übersetzungseinheit er definiert ist<br />
deshalb ist eine Aufteilung in Header- und Implementierungs-Dateien notwendig,<br />
und die Header-Dateien müssen explizit in die Implementierungs-Dateien kopiert werden<br />
• static in ANSI-C entspricht eher private in Java<br />
static markierte Funktionen und globale Variablen<br />
sind nur innerhalb der Implementierungs-Datei zugreifbar, in der sie definiert sind<br />
static markierte Variablen innerhalb einer Funktionen (gibt es in Java nicht)<br />
haben die Lebensdauer einer globalen Variablen,<br />
sind aber nur innerhalb der Funktion zugreifbar<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-27<br />
Hochschule Konstanz
ANSI-C Standard-Bibliothek: Überblick<br />
Die Schnittstelle der Standard-Bibliothek ist (teilweise etwas beliebig)<br />
in diverse Header-Dateien aufgeteilt:<br />
• Grundlegendes zur Sprachunterstützung (NULL, size_t, malloc, calloc, free, ...)<br />
<br />
• Ein-/Ausgabe<br />
<br />
• Umgang mit Zeichen und Zeichenketten<br />
<br />
• Umgang mit Zahlen:<br />
<br />
• Umgang mit Datum und Zeit<br />
<br />
• Umgang mit Fehlern und Ausnahmesituationen<br />
<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-28<br />
Hochschule Konstanz<br />
ANSI-C Standard-Bibliothek: (1)<br />
• Speicherverwaltung:<br />
void* calloc(size_t n, size_t size);<br />
void* malloc(size_t size);<br />
Was tun die Funktionen?<br />
void free(void* p);<br />
void* realloc(void* p, size_t size);<br />
• Programmende und Interaktion mit der Ablaufumgebung:<br />
void abort(void);<br />
int atexit(void (*exit_handler)(void));<br />
void exit(int status); /* status: EXIT_FAILURE oder EXIT_SUCCESS */<br />
char* getenv(const char* name); /* Umgebungsvariable abfragen */<br />
int system(const char* s); /* Kommando ausführen */<br />
• Umwandlung von Zeichenketten in Zahlen, Zufallszahlen, etc.:<br />
double atof(const char* s);<br />
int atoi(const char* s);<br />
int rand(void); /* Zufallszahlengenerator */<br />
void srand(unsigned int seed); /* Anfangswert für Zufallszahlen */<br />
...<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-29<br />
Hochschule Konstanz
ANSI-C Standard-Bibliothek: (2)<br />
• Suchen und Sortieren:<br />
void *bsearch(const void* key, const void* p, size_t n, size_t size,<br />
int (*cmp)(const void*, const void*));<br />
void qsort(void* p, size_t n, size_t size,<br />
int (*cmp)(const void*, const void*));<br />
• Beispiel:<br />
#include /* damit bsearch und qsort bekannt sind */<br />
int intcmp(const void ∗, const void ∗); /* Vergleichsfunktion */<br />
...<br />
int a[4] = {40, 20, 10, 30};<br />
int n = 50, *p;<br />
qsort(a, 4, sizeof (int), intcmp); /* sortiert a aufsteigend */<br />
p = (int*) bsearch(&n, a, 4, sizeof (int), intcmp); /* sucht 50 in a */<br />
Wie sieht die Implementierung von intcmp aus?<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-30<br />
Hochschule Konstanz<br />
ANSI-C Standard-Bibliothek: <br />
• Prüfen der Zeichenart (Ziffer, Buchstabe, Zwischenraum usw.):<br />
int isalnum(int c);<br />
int isalpha(int c);<br />
int iscntrl(int c);<br />
int isdigit(int c);<br />
int isgraph(int c);<br />
int islower(int c);<br />
int isprint(int c);<br />
int ispunct(int c);<br />
int isspace(int c);<br />
int isupper(int c);<br />
int isxdigit(int c);<br />
• Wandeln in Klein- / Großbuchstaben:<br />
int tolower(int c);<br />
int toupper(int c);<br />
• Beispiel:<br />
#include /* damit isdigit bekannt ist */<br />
...<br />
if (isdigit('9')) printf("9 ist eine Ziffer\n");<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-31<br />
Hochschule Konstanz
ANSI-C Standard-Bibliothek: <br />
• Kopieren, Verarbeiten, Vergleichen von Zeichenketten:<br />
Beispiele siehe <strong>Teil</strong> 2<br />
• Vergleichen, Kopieren, Initialisieren usw. von Speicherbereichen:<br />
int memcmp(const void *cs, const void *ct, size_t n);<br />
void ∗memcpy(void ∗p1, const void ∗p2, size_t n);<br />
void ∗memset(void ∗p, int c, size_t n);<br />
...<br />
• Beispiel:<br />
#include /* damit memset und memcpy bekannt sind */<br />
...<br />
int a[4];<br />
int b[4];<br />
memset(a, 0, sizeof a); /* initialisiert Speicherbereich von a mit 0 */<br />
memcpy(b, a, sizeof a); /* kopiert Inhalt von von a nach b */<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-32<br />
Hochschule Konstanz<br />
ANSI-C Standard-Bibliothek: <br />
• Symbolische Konstanten für Zahlenbereich und Genauigkeit<br />
von Gleitkommazahlen () und ganzen Zahlen ():<br />
DBL_DIG<br />
Genauigkeit von double in Anzahl Dezimalstellen<br />
DBL_EPSILON kleinste double-Zahl x mit 1.0 + x != 1.0<br />
DBL_MAX<br />
größte double-Zahl<br />
DBL_MIN<br />
kleinste normalisierte double-Zahl<br />
...<br />
INT_MAX<br />
INT_MIN<br />
...<br />
größte int-Zahl<br />
kleineste int-Zahl<br />
• Mathematische Funktionen ():<br />
double sqrt(double x); Quadratwurzel<br />
double sin(double x); Sinus<br />
...<br />
• Beispiel: #include /* damit sqrt bekannt ist */<br />
double d = sqrt(9.0); /* setzt d auf 3.0 */<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-33<br />
Hochschule Konstanz
ANSI-C Programme: Empfehlungen<br />
• Getrennte Übersetzungseinheiten zur Modularisierung verwenden.<br />
Hauptprogramm als eigene Übersetzungseinheit ohne Header-Datei<br />
• Variablen-Definitionen immer so lokal wie möglich<br />
globale Variablen vermeiden<br />
• ANSI-C Standard-Bibliothek gegenüber Eigenimplementierungen bevorzugen:<br />
z.B. qsort verwenden, statt selbst ein Sortierverfahren zu programmieren<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-34<br />
Hochschule Konstanz<br />
ANSI-C Programme: Lernzettel<br />
#endif #ifndef #include Archiv argc argv Aufrufoperator binden Compiler<br />
dynamische Bibliothek extern frame pointer Funktion Funktionskopf<br />
Funktionsprototyp Funktionsrumpf Funktionszeiger gcc globale Variable<br />
Header-Datei Implementierungsdatei Linker lokale Variable main memcpy<br />
memset Parameter Präprozessor program counter qsort return Rückgabewert<br />
Seiteneffekt stack pointer Standardbibliothek static statische Bibliothek<br />
übersetzen Übersetzungseinheit<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 4-35<br />
Hochschule Konstanz
<strong>Systemprogrammierung</strong><br />
<strong>Teil</strong> 5: Werkzeuge<br />
Programmerstellung, Fehlersuche<br />
Prof. Dr. H. Drachenfels Version 7.0<br />
Hochschule Konstanz 20.2.2014<br />
Programmierwerkzeuge<br />
Einsatzgebiete:<br />
• Erstellen von Programmen<br />
• Verwalten von Programmen<br />
• Prüfen von Programmen<br />
Nutzen:<br />
• Ermöglichung<br />
• Automatisierung<br />
• Optimierung<br />
• Qualitätssicherung<br />
Wie komme ich zu einem lauffähigen Programm?<br />
Woraus besteht ein gegebenes Programm und<br />
wann wurde was hinzugefügt / entfernt / geändert?<br />
Hat ein gegebenes Programm alle<br />
funktionalen und nicht funktionalen Eigenschaften,<br />
die von ihm erwartet werden?<br />
ein Vorgehen überhaupt erst möglich machen<br />
weniger Handarbeit<br />
weniger Aufwand<br />
weniger Mängel<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-1<br />
Hochschule Konstanz
Werkzeuge: Erstellen von Programmen (1)<br />
Was ist zu tun?<br />
• Bearbeiten von Quellcode<br />
Schreiben von neuem Quellcode<br />
Ändern von vorhandenem Quellcode<br />
• Transformieren von Quellcode in ausführbaren Code<br />
je nach Programmiersprache mehrere Transformationsschritte erforderlich<br />
bei aus vielen <strong>Teil</strong>en bestehenden Programmen<br />
Transformationsschritte pro Programmteil wiederholen<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-2<br />
Hochschule Konstanz<br />
Werkzeuge: Erstellen von Programmen (2)<br />
Wozu Werkzeuge?<br />
• Ermöglichung<br />
Werkzeug Texteditor unverzichtbar zum Bearbeiten von Quellcode (z.B. kwrite)<br />
Werkzeug Compiler unverzichtbar zur Transformation von Quellcode (z.B. gcc)<br />
• Automatisierung<br />
bei aus vielen <strong>Teil</strong>en bestehenden Programmen sehr viele Arbeitsschritte,<br />
die Arbeitsschritte automatisch veranlassen (z.B. mit Werkzeug make).<br />
• Optimierung<br />
bei Programmänderungen nur die notwendigen Arbeitsschritte durchführen,<br />
unnötige Arbeitsschritte automatisch weglassen (z.B. mit Werkzeug make)<br />
• Qualitätssicherung<br />
Mängel im Quellcode und bei Transformationsschritten entdecken / vermeiden<br />
(z.B. Formatierungsmängel beseitigen mit Werkzeug astyle).<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-3<br />
Hochschule Konstanz
Bearbeiten von Quellcode: Formatierung<br />
astyle – ein "Beautifier" für C / C++ / C# / Java-Quellcode<br />
Aufruf-Möglichkeiten:<br />
astyle [Optionen] Quelldatei ...<br />
ursprünglicher Code wird nach<br />
Quelldatei.orig gerettet.<br />
astyle [Optionen] < hässliche_Datei > verschönerte_Datei<br />
• Optionen:<br />
Festlegung des Formatierungsstils<br />
(Einrückung und Klammerung von Blöcken, Platzierung von Zwischenraum, ...):<br />
z.B. --style=ansi Einrückungs- und Klammerungsstil nach ANSI<br />
z.B. -p<br />
Leerstellen um Operatoren herum<br />
Festlegungen der Quellsprache (bei Aufruf mit Dateiumlenkung):<br />
z.B. –-mode=c<br />
• Funktionsweise:<br />
korrigiert die Formatierung in den angegebenen Quelldateien<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-4<br />
Hochschule Konstanz<br />
Bearbeiten von Quellcode: Suchen und Vergleichen<br />
Bearbeiten von Quellcode bedeutet vor allem korrigieren, ändern und erweitern.<br />
Dazu müssen die relevanten Stellen im vorhandenen Code gefunden werden.<br />
• Dateien suchen mit den Unix-Kommandos find und grep:<br />
find original/ -mtime 0 -name *.c -print<br />
liefert die Namen aller .c-Quelldateien im Verzeichnisbaum unter original/,<br />
die innerhalb der letzten 24 Stunden geändert wurden<br />
grep -rl "gruessen()" original/<br />
liefert die Namen aller Dateien im Verzeichnisbaum unter original/,<br />
die die Zeichenkette gruessen() enthalten<br />
• Dateien und Dateibäume vergleichen mit dem Unix-Kommando diff:<br />
diff -rq original/ backup/<br />
liefert die Namen aller Dateien, die sich inhaltlich unterscheiden<br />
oder nur in einem der beiden Verzeichnisbäume vorhanden sind<br />
diff original/hallo.c backup/hallo.c<br />
liefert alle Zeilen aus den beiden Dateien, die sich unterscheiden<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-5<br />
Hochschule Konstanz
Transformation von C-Quellcode: gcc<br />
gcc – der GNU Präprozessor / Compiler / Assembler / Linker für C<br />
• Aufruf:<br />
gcc [Option ...] Eingabedatei ...<br />
• Optionen:<br />
[-E|-S|-c] Transformationsschritte einschränken<br />
[-Dmacro[=definition] ...][-Umacro ...][-Idir ...] Präprozessor steuern<br />
[-std=standard][-pedantic][-Wwarn ...] "Strenge" des Compilers steuern<br />
[-g][-pg] Debuggen und Vermessen vorbereiten<br />
[-Olevel] Code-Optimierung steuern<br />
[-Ldir ...] [-lname ...] Linker steuern<br />
[-o outfile] Name der Ergebnisdatei angeben<br />
... insgesamt über 1000 Optionen, ca. 650 Seiten Dokumentation<br />
• empfohlene Optionen zur Qualitätssicherung des Quellcodes:<br />
-W -Wall -ansi -pedantic<br />
vor potenziellen<br />
Fehlern warnen<br />
Einhaltung des ANSI-C-Sprachstandards überwachen<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-6<br />
Hochschule Konstanz<br />
Übersetzungseinheiten: Beispiel<br />
Einfaches C-Programm mit zwei Übersetzungseinheiten:<br />
/* hallo.c */<br />
#include "gruss.h"<br />
int main()<br />
{<br />
gruessen();<br />
return 0;<br />
}<br />
/* gruss.h */<br />
#ifndef GRUSS_H<br />
#define GRUSS_H<br />
void gruessen(void);<br />
#endif<br />
/* gruss.c */<br />
#include "gruss.h"<br />
#include <br />
void gruessen(void)<br />
{<br />
printf("Hallo\n");<br />
}<br />
• Global sichtbare Namen im Header-Datei (Endung .h) deklarieren.<br />
• Header-Datei per #include in die Implementierungs-Datei (Endung .c) kopieren.<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-7<br />
Hochschule Konstanz
Übersetzungseinheiten: Compiler und Linker-Aufrufe<br />
Compiler/Linker-Aufrufe bei Programmen mit mehren Übersetzungseinheiten:<br />
• jede Übersetzungseinheit getrennt übersetzen:<br />
gcc -c –I. hallo.c<br />
gcc -c –I. gruss.c<br />
Der Präprozessor kopiert gruss.h jeweils in hallo.c bzw. gruss.c hinein.<br />
Option(en) -I geben an, wo Header-Dateien anderer Übersetzungseinheiten liegen.<br />
• dann den Objektcode der Übersetzungseinheiten (Endung .o) binden:<br />
gcc hallo.o gruss.o -o hallo<br />
Das ausführbare Programm nennt man üblicherweise so wie die Übersetzungseinheit<br />
mit dem Hauptprogramm main.<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-8<br />
Hochschule Konstanz<br />
Transformation von Quellcode: Probleme<br />
Manueller Aufruf von Compiler und Linker zu aufwändig:<br />
• bei Programmen mit vielen Übersetzungseinheiten viele Aufrufe notwendig<br />
• eventuell viele Optionen pro Aufruf<br />
Manueller Aufruf von Compiler und Linker zu fehlerträchtig:<br />
• nach Programmänderungen Vergessen notwendiger Aufrufe<br />
• ungünstige oder falsche Optionen bei den Aufrufen<br />
Abhilfe durch<br />
Automatisierung<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-9<br />
Hochschule Konstanz
Automatisierung der Programmerstellung<br />
Kommando-Prozedur<br />
• zur Programmerstellung erforderliche Kommandofolge in eine Datei schreiben<br />
• die Datei ausführen, um die Kommandofolge zu wiederholen<br />
Auch nach kleinen Programmänderungen werden alle Kommandos ausgeführt.<br />
Das kann bei Programmen mit vielen Übersetzungseinheiten sehr lange dauern<br />
und im Fehlerfall unnötige Folgefehler produzieren.<br />
Build-Werkzeug<br />
• die Abhängigkeiten zwischen den zu erstellenden Endergebnissen,<br />
Zwischenergebnissen und Quellen sowie die erforderlichen Kommandos<br />
in einem Bauplan festhalten<br />
• für den Bauplan das Build-Werkzeug aufrufen, um Zwischen- und<br />
Endergebnisse inkrementell erstellen bzw. aktualisieren zu lassen<br />
Es werden immer nur die laut Bauplan erforderlichen Kommandos ausgeführt.<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-10<br />
Hochschule Konstanz<br />
Kommando-Prozedur: Linux Shell-Script (1)<br />
• eine Linux-Shell ist ein Programm, mit dem Benutzer Linux über Kommandos<br />
bedienen können (Kommandointerpretierer)<br />
Es gibt verschiedene Implementierungen, die wichtigsten unter Linux sind:<br />
sh<br />
bash<br />
Bourne Shell (für Kommando-Prozeduren üblich)<br />
Bourne Again Shell (Standard für die interaktive Benutzung)<br />
• ein Shell-Script ist eine Datei mit einer Folge von Linux-Kommandos:<br />
#!/bin/sh<br />
gcc -c hallo.c<br />
gcc -c gruss.c<br />
gcc hallo.o gruss.o -o hallo<br />
• Shell-Script ausführen:<br />
sh Dateiname<br />
./Dateiname<br />
bei der zweiten Variante muss bei der Datei<br />
das Ausführungsrecht gesetzt sein<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-11<br />
Hochschule Konstanz
Kommando-Prozedur: Linux Shell-Script (2)<br />
• die Bourne-Shell kennt auch Variablen, Verzweigungen und Schleifen:<br />
#!/bin/sh<br />
for s in hallo.c gruss.c ; do<br />
compile_command="gcc -c $s" # Variable mit Initialisierung<br />
echo $compile_command<br />
# Wert der Variablen ausgeben<br />
eval $compile_command<br />
if [ $? -ne 0 ] ; then<br />
echo build failed<br />
exit 1<br />
fi<br />
done<br />
link_command="gcc -o hallo hallo.o gruss.o"<br />
echo $link_command<br />
eval $link_command<br />
if [ $? -ne 0 ] ; then<br />
echo build failed<br />
exit 1<br />
fi<br />
echo build successful<br />
# Wert der Variablen als Kommando ausführen<br />
# Rückgabewert des Kommandos prüfen<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-12<br />
Hochschule Konstanz<br />
Build-Werkzeug: make<br />
make – das Build-Programm unter Linux (Unix, ...)<br />
Aufruf:<br />
make [-f Bauplan] [Ziel] ...<br />
• fehlt die Option –f Bauplan, wird makefile oder Makefile verwendet<br />
Üblicherweise wird der Bauplan Makefile genannt,<br />
in speziellen Fällen werden auch Dateinamen mit Endung .mak oder .mk verwendet<br />
• Ziel ist eine zu erstellende Datei oder der Name einer Regel<br />
fehlt das Ziel, wird das im Bauplan als erstes genannte Ziel bearbeitet,<br />
üblicherweise heißt das erste Ziel im Bauplan all<br />
• sind mehrere Ziele angegeben, werden diese nacheinander bearbeitet<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-13<br />
Hochschule Konstanz
make: Beispiel (1)<br />
• einfacher Bauplan für das Programm hallo:<br />
# Makefile<br />
hallo: hallo.o gruss.o<br />
gcc hallo.o gruss.o -o hallo<br />
hallo.o: hallo.c gruss.h<br />
gcc -c hallo.c<br />
gruss.o: gruss.c gruss.h<br />
gcc -c gruss.c<br />
Tabulator vor dem Kommando nicht vergessen<br />
• Aufruf zum Erstellen bzw. Aktualisieren des Programms:<br />
make -f Makefile hallo<br />
Abhängigkeit<br />
(hallo abhängig von zwei Objektdateien)<br />
Kommando<br />
(erzeugt hallo aus<br />
zwei Objektdateien)<br />
make # tut das gleiche, weil Makefile Standardname und hallo erstes Ziel ist<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-14<br />
Hochschule Konstanz<br />
make: Beispiel (2)<br />
Abhängigkeiten ( ) steuern das inkrementelle Erstellen ( ):<br />
• Aufruf nach Änderung von gruss.c<br />
hallo.o<br />
hallo<br />
gruss.o<br />
hallo.c<br />
gruss.h<br />
gruss.c<br />
hallo.o wird<br />
nicht neu erstellt,<br />
weil unabhängig<br />
von gruss.c<br />
• Aufruf nach Änderung von gruss.h<br />
hallo.o<br />
hallo<br />
gruss.o<br />
hallo.c<br />
gruss.h<br />
gruss.c<br />
alles wird<br />
neu erstellt,<br />
weil abhängig<br />
von gruss.h<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-15<br />
Hochschule Konstanz
Makefile: Regeln<br />
• explizite Regeln:<br />
Ziel<br />
abhängig von<br />
hallo.o: hallo.c<br />
gcc -c hallo.c<br />
Quelle<br />
• implizite Regeln: beziehen sich auf Dateiendungen<br />
Kommando (muss mit Tabulator eingerückt sein)<br />
Quelle<br />
etwas.c<br />
.c.o:<br />
gcc -c $<<br />
Ziel etwas.o<br />
$< ist die Quelle,<br />
auf die die Regel<br />
angewendet wird<br />
• Musterregeln: explizite Regeln mit Platzhalter % für beliebige Zeichenfolgen<br />
Ziel<br />
etwas.o<br />
%.o: %.c<br />
gcc -c $<<br />
Quelle etwas.c<br />
$< ist die Quelle,<br />
auf die die Regel<br />
angewendet wird<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-16<br />
Hochschule Konstanz<br />
Makefile: explizite Regeln (1)<br />
Ziel ...: Quelle ...<br />
Kommando<br />
...<br />
Anwendung auf Dateien:<br />
• Ziel ist eine Datei, die mit der Regel erzeugt wird.<br />
Meist ein Ziel pro Regel, es sind aber auch mehrere erlaubt.<br />
• Quelle ist eine Datei, die zum Erstellen des Ziels gebraucht wird.<br />
Keine, eine oder viele Quellen pro Regel.<br />
• Kommando ist ein Befehl, der Zieldatei(en) aus Quelldatei(en) erzeugt.<br />
Meist kein (→ Sonderformen) oder ein Kommando pro Regel,<br />
auch komplizierte Kommandos in Shell-Skript-Syntax möglich.<br />
Liefert ein Kommando einen Fehlerstatus, beendet sich make automatisch.<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-17<br />
Hochschule Konstanz
Makefile: explizite Regeln (2)<br />
Sonderform Abhängigkeitsregel:<br />
• Eine Abhängigkeitsregel ist eine explizite Regel ohne Kommando:<br />
hallo.o: hallo.c gruss.h<br />
gruss.o: gruss.c gruss.h<br />
Abhängigkeitsregeln sind die in der Praxis<br />
am häufigsten verwendete Form<br />
der expliziten Regel<br />
• Abhängigkeitsregeln brauchen zur Ergänzung implizite oder Musterregeln,<br />
die die Kommandos festlegen, z.B.:<br />
%.o: %.c<br />
gcc -c $<<br />
• Abhängigkeitsregeln kann gcc automatisch aus den C-Quellen erzeugen,<br />
indem er die #include-Anweisungen auswertet:<br />
gcc -MM hallo.c gruss.c > depend<br />
schreibt die Regeln mittels Umlenkung der Standardausgabe in die Datei depend,<br />
die Datei depend kann dann per include in das Makefile integriert werden<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-18<br />
Hochschule Konstanz<br />
Makefile: explizite Regeln (3)<br />
Sonderform mit Pseudoziel:<br />
• Ein Pseudoziel ist keine Datei, sondern ein beliebiger Name,<br />
der nur dazu dient, bestimmte Arbeitsschritte gezielt aufrufbar zu machen:<br />
make Pseudoziel<br />
• Aufzählung der Pseudoziele im Makefile mit einer .PHONY-Regel:<br />
.PHONY: all clean install uninstall<br />
Pseudoziele all, clean, install, uninstall<br />
haben sich als Quasistandard eingebürgert<br />
• Die all-Regel zählt alle Endergebnisse des Makefiles auf:<br />
all: hallo Die all-Regel sollte immer die erste Regel im Makefile sein!<br />
• Die clean-Regel löscht alle Zwischen- und Endergebnisse,<br />
die mit dem Pseudoziel all erzeugt werden:<br />
clean:<br />
rm -f hallo hallo.o gruss.o<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-19<br />
Hochschule Konstanz
Makefile: implizite Regeln (1)<br />
.Quellendung.Zielendung:<br />
Kommando<br />
Anwendung auf Dateitypen, wobei die Dateiendung den Typ bestimmt:<br />
• Alle Endungen müssen beim eingebauten Ziel .SUFFIXES genannt sein:<br />
.SUFFIXES: .c .o<br />
• .Quellendung legt den Dateityp der Quelle fest, den die Regel benötigt.<br />
Der Name der Quelle Name.Quellendung muss mit dem<br />
Namen der Zieldatei übereinstimmen, für die die Regel ausgeführt wird.<br />
• .Zielendung legt den Dateityp fest, auf den die Regel anwendbar ist.<br />
Die Regel wird für beliebige Zieldateien Name.Zielendung ausgeführt,<br />
falls es für die Datei keine explizite Regel mit Kommando gibt.<br />
• Kommando ist ein Befehl, der die Zieldatei aus der Quelldatei erzeugt.<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-20<br />
Hochschule Konstanz<br />
Makefile: implizite Regeln (2)<br />
make hat für die wichtigsten Dateitypen vordefinierte implizite Regeln, z.B.:<br />
• Übersetzen und binden eines C-Programms mit nur einer Quelldatei:<br />
Zielendung ist leer (ausführbare Programme unter Unix ohne Endung)<br />
.c:<br />
$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $<<br />
make hallo erzeugt aus hallo.c das ausführbare Programm hallo<br />
• Übersetzen einer C-Quelle:<br />
.c.o:<br />
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<<br />
make hallo.o erzeugt aus hallo.c die Objektdatei hallo.o<br />
• Kommandos in vordefinierten Regeln sind mit Variablen definiert,<br />
um sie leicht an unterschiedliche Plattformen anpassen zu können:<br />
CC=gcc<br />
Präprozessor-Optionen (hier leer)<br />
CPPFLAGS=<br />
CFLAGS=<br />
Compiler-Optionen (hier leer)<br />
LDFLAGS=<br />
Linker-Optionen (hier leer)<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-21<br />
Hochschule Konstanz
Makefile: Musterregeln<br />
Zielmuster: Quellmuster<br />
Kommando<br />
Ziel- und Quellmuster enthalten % als Platzhalter für beliebige Zeichenkette:<br />
• Übersetzen und binden eines C-Programms mit nur einer Quelldatei:<br />
%: %.c<br />
$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $<<br />
z.B. ausführbares Programm hallo aus der C-Quelle hallo.c erzeugen<br />
• Übersetzen einer C-Quelle:<br />
%.o: %.c<br />
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<<br />
z.B. Objektdatei hallo.o aus der C-Quelle hallo.c erzeugen<br />
• Musterregeln wurden als flexiblerer Ersatz für implizite Regeln eingeführt<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-22<br />
Hochschule Konstanz<br />
Makefile: Variablen (1)<br />
Mit Variablen können häufig wiederkehrende Bestandteile von Makefiles<br />
zusammengefasst und parametrisiert werden.<br />
• Variablendefinition:<br />
Variable = Wert<br />
oder mehrzeilig<br />
Variable üblicherweise in Großbuchstaben<br />
Wert ist eine beliebige Zeichenkette<br />
• Variablenbenutzung:<br />
$(Variable)<br />
$(Variable:suffix=ersetzung)<br />
Variable = \<br />
Wert \<br />
Fortsetzung<br />
Textersetzung von $(Variable) durch den Wert der Variablen<br />
bzw. durch den am Ende modifizierten Wert der Variablen.<br />
Wenn eine Variable nicht definiert ist, wird ihr Wert als leer angenommen.<br />
Rekursives Expandieren: enthält der Wert wiederum Variablenbenutzungen,<br />
wird darauf erneut die Textersetzung angewendet, usw.<br />
Zeilenwechsel<br />
müssen mit \<br />
maskiert werden<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-23<br />
Hochschule Konstanz
Makefile: Variablen (2)<br />
Sonderfall automatische Variablen:<br />
• vordefinierte Variablen, die bei jeder Regelanwendung<br />
einen neuen Wert erhalten, z.B.:<br />
$@ Das Ziel, auf das die Regel gerade angewendet wird<br />
$< Die erste Quelle zum aktuellen Ziel<br />
$^ Alle Quellen zum aktuellen Ziel<br />
• die automatischen Variablen sind in impliziten Regeln und Musterregeln<br />
unentbehrlich, z.B.:<br />
.c:<br />
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $<<br />
bzw.<br />
%: %.c<br />
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $<<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-24<br />
Hochschule Konstanz<br />
Makefile: Variablen (3)<br />
Vordefinierte Variablen als Parametrierung eingebauter impliziter Regeln:<br />
• Kommandos sind in impliziten Regeln indirekt mit Variablen formuliert:<br />
$(KOMMANDO) Wert ist der zu verwendende Befehl<br />
$(KOMMANDOFLAGS) Wert ist zunächst leer<br />
Beispiele:<br />
CC=gcc<br />
der C-Compiler (mit Optionen $(CFLAGS))<br />
RM=rm -f<br />
der Löschbefehl für Dateien<br />
• die Werte der Variablen können nach Bedarf überschrieben werden:<br />
in der Aufrufumgebung export CC="gcc -g"<br />
im Makefile CC = gcc -g<br />
beim Aufruf von make make "CC=gcc -g"<br />
Wert bei Aufruf überschreibt Wert in Makefile überschreibt Wert aus Aufrufumgebung<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-25<br />
Hochschule Konstanz
Makefile: Rekursion<br />
Große Softwaresysteme bestehen aus vielen Paketen,<br />
die Paket für Paket mit make erstellt werden müssen.<br />
• Pakethierarchie wird im Dateisystem als Verzeichnishierarchie abgebildet,<br />
z.B:<br />
Softwaresystem hallohallo<br />
hallohallo/ Makefile<br />
mit Paketen hallo und hallo2<br />
hallo/ Makefile hallo.c<br />
hallo2/ Makefile hallo.c gruss.h gruss.c<br />
• Makefile des Softwaresystems ruft make rekursiv für die Pakete auf:<br />
# Makefile fuer Softwaresystem hallohallo<br />
PACKAGES=hallo hallo2<br />
.PHONY: all clean<br />
all clean:<br />
for p in $(PACKAGES); do \<br />
(cd $$p && $(MAKE) $@); \<br />
done<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-26<br />
Hochschule Konstanz<br />
Makefile: C-Beispiel hallo (1)<br />
# Makefile<br />
# Kommando-Variablen<br />
CC = gcc<br />
CFLAGS = -W -Wall -ansi -pedantic<br />
CPPFLAGS = -I.<br />
RM = rm -f<br />
# Hilfsvariablen<br />
TARGET = hallo<br />
OBJECTS = gruss.o<br />
SOURCES = $(TARGET).c $(OBJECTS:.o=.c)<br />
HEADERS = $(OBJECTS:.o=.h)<br />
Variablen für die vordefinierten<br />
C-Übersetzungsregeln<br />
Include-Dateien im aktuellen Verzeichnis suchen<br />
die C-Übersetzungsregel ist vordefiniert und<br />
# Musterregeln<br />
braucht deshalb nicht angegeben zu werden<br />
%.o: %.c<br />
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@<br />
...<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-27<br />
Hochschule Konstanz
Makefile: C-Beispiel hallo (2)<br />
...<br />
# Standardziele<br />
Pseudoziele<br />
.PHONY: all clean<br />
all: $(TARGET)<br />
clean:<br />
$(RM) $(TARGET) $(TARGET).o $(OBJECTS) depend<br />
depend: $(SOURCES) $(HEADERS)<br />
$(CC) $(CPPFLAGS) -MM $(SOURCES) > $@<br />
# Ziele zur Programmerstellung<br />
$(TARGET): $(TARGET).o $(OBJECTS)<br />
$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@<br />
# Abhaengigkeiten<br />
include depend<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-28<br />
Hochschule Konstanz<br />
Makefile: Empfehlungen (1)<br />
Variablen:<br />
• für jedes in einer Regel verwendete Kommando eine Variable definieren<br />
bei komplexen Kommandos zusätzliche Variable für Optionen<br />
• für die Liste der Übersetzungseinheiten / Quelldateien Hilfsvariablen definieren<br />
• in Regeln, wo immer möglich, automatische Variablen verwenden<br />
Regeln:<br />
• wo immer möglich, Musterregeln statt expliziter Regeln verwenden<br />
• immer zumindest die Pseudoziele all und clean vorsehen<br />
all muss das erste Ziel im Makefile sein<br />
clean muss alles beseitigen, was all erzeugt<br />
• Abhängigkeitsregeln möglichst automatisch erzeugen<br />
mit einem Ziel depend eine gleichnamige Datei erzeugen und per include einbinden<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-29<br />
Hochschule Konstanz
Makefile: Empfehlungen (2)<br />
Vorgehen beim Erstellen:<br />
• mit der all-Regel beginnen<br />
all: Endergebnis<br />
Endergebnis ist die zu erstellende Datei<br />
(bei Bedarf auch mehrere Dateien)<br />
• für jede bei all als Endergebnis genannte Datei eine Regel erstellen,<br />
für jede darin als Zwischenergebnis genannte Datei wiederum eine Regel,<br />
usw. bis nur noch Abhängigkeiten von Quelldateien auftreten:<br />
Endergebnis: Zwischenergebnisse<br />
Kommando<br />
...<br />
Zwischenergebnis: Quelldateien<br />
Kommando<br />
• eine clean-Regel erstellen<br />
clean:<br />
$(RM) Endergebnis Zwischenergebnisse<br />
• mit Variablen und Musterregeln die mehrfache Wiederholung von Dateinamen<br />
und Kommandos verhindern<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-30<br />
Hochschule Konstanz<br />
Prüfen von Programmen: Fehlersuche (1)<br />
Einige wichtige Arten von Laufzeitfehlern:<br />
• Absturz<br />
unerwartetes Programmende, z.B. wegen Speicherzugriffsfehler<br />
• Endlosschleife<br />
das Programm scheint zu "hängen", aber es läuft und läuft und läuft ...<br />
• Speicherüberlauf<br />
der ganze Rechner wird langsam,<br />
weil das Programm sämtlichen Speicher belegt hat<br />
• Fehlverhalten<br />
das Programm tut nicht, was es tun soll, liefert z.B. falsche Ergebnisse<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-31<br />
Hochschule Konstanz
Prüfen von Programmen: Fehlersuche (2)<br />
Vorgehen bei der Suche von Laufzeitfehlern:<br />
• Fehler reproduzieren<br />
einen Testfall erstellen, bei dem der Fehler auftritt<br />
oft schwierig bei Programmen mit vielfachen Abhängigkeiten von der Umgebung<br />
(Benutzer, andere Programme, Zeit, Daten in Dateien oder Datenbanken, Netzwerk, ...)<br />
• Fehler isolieren<br />
mögliche Fehlerursachen schrittweise eingrenzen<br />
Hypothesen aufstellen und prüfen<br />
Programmteile gezielt weglassen oder abändern<br />
feststellen, ob ältere Programmversionen den Fehler auch zeigen<br />
schrittweises Ausführen im Debugger<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-32<br />
Hochschule Konstanz<br />
Fehlersuche: Debugger<br />
Debugger erlauben es, den Programmablauf zu beobachten und zu beeinflussen,<br />
ohne den Code dafür aufwändig und fehlerträchtig abzuändern.<br />
Funktionalitäten:<br />
• Programm kontrolliert ausführen<br />
Zeile für Zeile, bis Funktionsende, bis zum nächsten Haltepunkt, ...<br />
• Programm unter bestimmten Bedingungen anhalten lassen<br />
unbedingte und bedingte Haltepunkte ("Break-Points", "Watch-Points")<br />
• Zustand des angehaltenen Programms untersuchen<br />
Aufruf-Stack anzeigen, Speicherinhalte anzeigen, ...<br />
• Zustand des angehaltenen Programms verändern<br />
Speicherinhalte ändern, Anweisungen überspringen, ...<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-33<br />
Hochschule Konstanz
Debugger: Nutzen<br />
Mit einem Debugger lässt sich meist schnell klären:<br />
• wo ein Programm abstürzt<br />
Programm mit gleichen Eingaben im Debugger laufen lassen<br />
oder core-Datei untersuchen<br />
(Unix legt bei einem Programmabsturz den gesamten Programmzustand<br />
in einer Datei core ab, einzuschalten mit: ulimit -c unlimited ).<br />
• wo ein Programm eine Endlosschleife enthält<br />
Programm im Debugger unterbrechen<br />
oder Programm "abschießen" (kill -6 ...), um untersuchbare core-Datei zu erhalten<br />
• ob eine Hypothese zur Fehlerursache stimmt<br />
gezielt Haltepunkte setzen und Programmzustand analysieren<br />
die Hypothese selbst findet man nur durch Nachdenken!<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-34<br />
Hochschule Konstanz<br />
Debugger: ddd<br />
ddd – der GNU Data-Display-Debugger, eine graphische Benutzeroberfläche<br />
für den kommandozeilen-orientierten GNU-Debugger gdb.<br />
Aufruf:<br />
ddd Programm [Core-Datei | Prozessnummer]<br />
• Programm:<br />
Die volle Funktionalität des Debuggers steht nur zur Verfügung,<br />
wenn das Programm mit der gcc-Option -g übersetzt wurde.<br />
es wird dann Information in den Code eingebettet, die dem Debugger<br />
den Rückschluss von Adressen auf Variablen und Zeilen im Quellcode erlaubt.<br />
• Core-Datei:<br />
nur beim nachträglichen Untersuchen eines abgestürzten Programms<br />
("Post-Mortem-Debugging").<br />
• Prozessnummer:<br />
zum nachträglichen Ankoppeln des Debuggers an ein laufendes Programm<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-35<br />
Hochschule Konstanz
Speicherfehler suchen: valgrind<br />
valgrind – ein Heapdebugger für x86-Linux<br />
Aufruf:<br />
valgrind [Optionen] Programm [Argumente]<br />
• Funktionsweise:<br />
interpretiert x86-Maschinencode (virtueller Prozessor) und<br />
führt dabei Buch über die Heapnutzung des Programms.<br />
Das Programm läuft dadurch wesentlich langsamer und braucht mehr Speicher.<br />
• Fehlererkennung:<br />
Lesezugriff auf nicht initialisierten Heapspeicher<br />
Lese- oder Schreibzugriff auf nicht reservierten Heapspeicher<br />
Feldgrenzen-Überschreitung für separat auf dem Heap allokierte Felder<br />
Speicherlecks (malloc/calloc ohne zugehöriges free)<br />
doppeltes Freigeben von reserviertem Speicher (mehrfaches free)<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-36<br />
Hochschule Konstanz<br />
Werkzeuge: Lernzettel<br />
$< $@ $^ Abhängigkeitsregel Absturz all astyle automatische Variable<br />
Breakpoint clean ddd Debugger diff Endlosschleife explizite Regel<br />
Fehlverhalten find gcc grep Heap-Debugger implizite Regel<br />
Kommandoprozedur make Makefile Musterregel Programmierwerkzeuge<br />
Pseudoziel Post-Mortem-Debugging shell-Script Speicherüberlauf valgrind<br />
Variable Watchpoint<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 5-37<br />
Hochschule Konstanz
<strong>Systemprogrammierung</strong><br />
<strong>Teil</strong> 6: Ein-/Ausgabe<br />
Dateizugriff, Elementare Ein-/Ausgabe<br />
Prof. Dr. H. Drachenfels Version 6.0<br />
Hochschule Konstanz 25.2.2014<br />
ANSI-C Ein-/Ausgabe: (1)<br />
Bei den Ein-/Ausgabefunktionen der ANSI-C Standard-Bibliothek werden die<br />
Eingabe-Quellen und Ausgabe-Ziele mit einem FILE-Zeiger angegeben:<br />
• FILE ist ein (Alias-)Name für eine Struktur,<br />
die den Zustand einer Eingabe-Quelle bzw. eines Ausgabe-Ziels verwaltet<br />
zum Zustand gehören Puffer, Lese-/Schreibposition, aufgetretene Fehler, ...<br />
• vordefinierte globale Variablen für die Standard-Ein-/Ausgabe:<br />
extern FILE *stdin;<br />
Hinweis: stdin, stdout und stderr<br />
extern FILE *stdout;<br />
können auch Präprozessor -Makros sein<br />
extern FILE *stderr;<br />
• fopen liefert Zeiger auf weitere FILE-Objekte:<br />
FILE *fopen(const char *dateiname, char *mode);<br />
mode "r" für reinen Lesezugriff, "w" für reinen Schreibzugriff, ...<br />
• fclose schließt nicht mehr benötigte Eingabe-Quellen und Ausgabe-Ziele:<br />
int fclose(FILE *fp);<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 6-1<br />
Hochschule Konstanz
ANSI-C Ein-/Ausgabe: (2)<br />
• Ein-/Ausgabe von Einzelzeichen:<br />
int fgetc(FILE *fp);<br />
liefert das nächste Zeichen (umgewandelt in int) oder EOF bei Eingabeende / Fehler<br />
int fputc(int c, FILE *fp);<br />
schreibt das Zeichen c und liefert c oder bei Fehler EOF<br />
...<br />
• Ein-/Ausgabe von Zeichenketten:<br />
char *fgets(char *s, int n, FILE *fp);<br />
liefert in s die nächsten maximal n - 1 Zeichen einer Zeile<br />
und gibt s zurück, bzw. NULL bei Eingabeende / Fehler<br />
int fputs(const char *s, FILE *fp);<br />
schreibt die Zeichenkette s und liefert nicht-negativen Wert bzw. bei Fehler EOF<br />
...<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 6-2<br />
Hochschule Konstanz<br />
ANSI-C Ein-/Ausgabe: (3)<br />
• formatierte Ein-/Ausgabe:<br />
int fscanf(FILE *fp, const char *format, ...);<br />
versucht die in format genannten Lücken zu füllen<br />
und liefert die Anzahl der gefüllten Lücken oder EOF bei Eingabeende<br />
int fprintf(FILE *fp, const char *format, ...);<br />
schreibt die Zeichenkette format inklusive der mit Werten gefüllten Lücken<br />
und liefert die Anzahl der insgesamt geschriebenen Bytes oder bei Fehler EOF<br />
...<br />
• Ein-/Ausgabe von Binärdaten:<br />
size_t fread(void *p, size_t size, size_t n, FILE *fp);<br />
liefert in p maximal n Portionen von size Byte<br />
und gibt die Anzahl der tatsächliche gelesenen Portionen zurück<br />
size_t fwrite(const void *p, size_t size, size_t n, FILE *fp);<br />
schreibt maximal n Portionen von size Byte aus p<br />
und gibt die Anzahl der tatsächliche geschriebenen Portionen zurück<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 6-3<br />
Hochschule Konstanz
ANSI-C Ein-/Ausgabe: (4)<br />
• Fehlerbehandlung:<br />
int feof(FILE *fp);<br />
liefert einen von 0 verschiedenen Wert, wenn das Eingabeende erreicht wurde<br />
int ferror(FILE *fp);<br />
liefert einen von 0 verschiedenen Wert, wenn ein Fehler aufgetreten ist<br />
void perror(const char *prefix);<br />
gibt prefix gefolgt von der Fehlermeldung des aktuellen Fehlers auf stderr aus<br />
void clearerr(FILE *fp);<br />
Setzt den Eingabeende- und Fehlerzustand zurück<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 6-4<br />
Hochschule Konstanz<br />
Beispiel-Programm <br />
#include /* fopen, fgetc, fclose */<br />
int main(int argc, char *argv[])<br />
{<br />
FILE *fp;<br />
int i, n;<br />
Zählt die Zeichen in Dateien<br />
for (i = 1; i < argc; ++i)<br />
{<br />
fp = fopen(argv[i], "r");<br />
if (fp == NULL) ... /* Fehlerbehandlung */<br />
n = 0;<br />
while (fgetc(fp) != EOF) ++n;<br />
printf("%s: %d Zeichen\n", argv[i], n);<br />
}<br />
fclose(fp);<br />
}<br />
return 0;<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 6-5<br />
Hochschule Konstanz
POSIX Ein-/Ausgabe: Übersicht<br />
POSIX (Portable Operating System Interface)<br />
ist ein Standard für die Programmierschnittstelle von Betriebssystemen.<br />
• der Standard legt C-Systemaufrufe und die zugehörigen Header-Dateien fest:<br />
über 80 Header-Dateien mit über 1000 Funktionen und Makros<br />
(dabei teilweise Überlappungen mit dem ANSI-C-Standard)<br />
Die meisten UNIX-Varianten und viele weitere Betriebssysteme<br />
halten sich ganz oder zumindest weitgehend an diesen Standard.<br />
• wichtige Header-Dateien im Zusammenhang mit Ein-/Ausgabe:<br />
und <br />
kein Schreibfehler!<br />
Umgang mit Dateien und Datenströmen (creat, open, read, write, close)<br />
und <br />
Umgang mit Verzeichnissen (stat, mkdir, opendir, readdir, closedir)<br />
<br />
Fehlerzustand und symbolische Namen für Fehlernummern (errno)<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 6-6<br />
Hochschule Konstanz<br />
POSIX Ein-/Ausgabe: Elementare Ein-/Ausgabe (1)<br />
Bei den elementaren Ein-/Ausgabefunktionen nach POSIX-Standard werden<br />
Eingabe-Quellen und Ausgabe-Ziele über einen Dateideskriptor angesprochen:<br />
• ein Dateideskriptor ist eine nicht-negative ganze Zahl<br />
bei der ANSI-C Ein-/Ausgabe in der FILE-Struktur gespeichert<br />
• vordefinierte Dateideskriptoren für die Standard-Ein-/Ausgabe:<br />
0 Standardeingabe<br />
1 Standardausgabe<br />
2 Standardfehlerausgabe<br />
• open liefert einen Dateideskriptor für eine Datei:<br />
int open(const char *dateiname, int flags); /* */<br />
liefert den kleinsten nicht belegten Dateideskriptor oder bei Fehler -1<br />
• close schließt nicht mehr benötigte Eingabe-Quellen und Ausgabe-Ziele:<br />
int close(int fd); /* */<br />
liefert 0 oder bei Fehler -1<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 6-7<br />
Hochschule Konstanz
POSIX Ein-/Ausgabe: Elementare Ein-/Ausgabe (2)<br />
• Ein-/Ausgabe von Bytes:<br />
ssize_t read(int fd, void *p, size_t n); /* */<br />
liefert in p maximal n Byte und gibt die Anzahl der tatsächliche gelesenen Bytes zurück,<br />
0 bei Eingabeende, -1 bei Fehler<br />
ssize_t write(int fd, const void *p, size_t n); /* */<br />
schreibt maximal n Byte aus p und gibt die Anzahl der tatsächliche geschriebenen Bytes<br />
oder bei Fehler -1 zurück<br />
ssize_t /* */<br />
Aliasname für einen ganzzahligen Typ mit Vorzeichen (int oder long)<br />
• Fehlerbehandlung:<br />
extern int errno; /* , errno kann auch ein Makro sein */<br />
POSIX-Funktionen weisen errno im Fehlerfall eine Fehlernummer ungleich 0 zu<br />
für die Fehlernummern sind symbolische Konstanten definiert<br />
(z.B: EACCES für fehlendes Zugriffsrecht auf eine Datei)<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 6-8<br />
Hochschule Konstanz<br />
Beispiel-Programm Dateien (1)<br />
#include /* fprintf */<br />
#include /* strerror */<br />
#include /* open, O_RDONLY, O_WRONLY, O_CREAT, O_EXCL */<br />
#include /* mode_t, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH */<br />
#include /* read, write */<br />
#include /* errno */<br />
int main(int argc, char *argv[])<br />
{<br />
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; /* Zugriffsrechte */<br />
int in, out; /* Dateideskriptoren */<br />
int n;<br />
unsigned char byte;<br />
if (argc != 3)<br />
{<br />
fprintf(stderr, "Aufruf: %s Quelle Ziel\n", argv[0]);<br />
return 1;<br />
}<br />
...<br />
Kopiert eine Datei<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 6-9<br />
Hochschule Konstanz
Beispiel-Programm Dateien (2)<br />
...<br />
in = open(argv[1], O_RDONLY);<br />
if (in == -1) ... /* Fehlerbehandlung */<br />
out = open(argv[2], O_WRONLY | O_CREAT | O_EXCL, mode);<br />
if (out == -1) ... /* Fehlerbehandlung */<br />
while (read(in, &byte, 1) > 0)<br />
{<br />
n = write(out, &byte, 1);<br />
if (n != 1) ... /* Fehlerbehandlung */<br />
}<br />
close(out);<br />
close(in);<br />
}<br />
return 0;<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 6-10<br />
Hochschule Konstanz<br />
POSIX Ein-/Ausgabe: Verzeichnisse<br />
Nach POSIX-Standard werden Verzeichnisse über DIR-Zeiger angesprochen:<br />
• opendir liefert einen DIR-Zeiger für ein Verzeichnis:<br />
DIR *opendir(const char *verzeichnisname); /* */<br />
liefert NULL bei Fehler<br />
• closedir beendet den Verzeichniszugriff:<br />
int closedir(DIR *dirp); /* < dirent.h> */<br />
liefert 0 oder bei Fehler -1<br />
• readdir liefert einen Zeiger auf den nächsten ungelesenen Verzeichniseintrag:<br />
struct dirent *readdir(DIR *dirp); /* < dirent.h> */<br />
der Verzeichniseintrag enthält unter d_name einen Dateinamen<br />
liefert NULL bei Verzeichnisende oder Fehler<br />
• stat liefert Statusinformation zu einer Datei (Dateityp, Zugriffsrechte, ...):<br />
int stat(const char *dateiname, struct stat *buf); /* < sys/stat.h> */<br />
liefert 0 oder bei Fehler -1<br />
Ausgabeparameter<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 6-11<br />
Hochschule Konstanz
Beispiel-Programm Verzeichnisse (1)<br />
#include /* fprintf, printf */<br />
#include /* strerror */<br />
Listet Verzeichnisse auf<br />
#include /* struct stat, S_IFMT, S_IFDIR */<br />
#include /* DIR, struct dirent, opendir, readdir */<br />
#include /* errno */<br />
int main(int argc, char *argv[])<br />
{<br />
struct stat s; /* Dateistatus */<br />
DIR *d; /* geoeffnetes Verzeichnis */<br />
struct dirent *e; /* gelesener Verzeichniseintrag */<br />
int i;<br />
...<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 6-12<br />
Hochschule Konstanz<br />
Beispiel-Programm Verzeichnisse (2)<br />
}<br />
...<br />
for (i = 1; i < argc; ++i)<br />
{<br />
/* Datei vorhanden? */<br />
if (stat(argv[i], &s) == -1) ... /* Fehlerbehandlung */<br />
}<br />
/* Dateityp Verzeichnis? */<br />
if ((s.st_mode & S_IFMT) != S_IFDIR) ... /* Fehlerbehandlung */<br />
d = opendir(argv[i]);<br />
if (d == NULL) ... /* Fehlerbehandlung */<br />
while ((e = readdir(d)) != NULL)<br />
{<br />
printf("%s/%s\n", argv[i], e->d_name);<br />
}<br />
closedir(d);<br />
return 0;<br />
mit Präprozessor-Option<br />
-D_XOPEN_SOURCE<br />
übersetzen, damit<br />
S_IFMT und S_IFDIR<br />
definiert sind<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 6-13<br />
Hochschule Konstanz
ANSI-C Ein-/Ausgabe: Lernzettel<br />
<br />
clearerror close closedir Dateideskriptor DIR errno fclose feof ferror<br />
fgetc fgets FILE fopen fprintf fputs fread fscanf fwrite open opendir<br />
perror POSIX read readdir ssize_t stat stderr stdin stdout<br />
struct dirent struct stat write<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 6-14<br />
Hochschule Konstanz
<strong>Systemprogrammierung</strong><br />
<strong>Teil</strong> 7: <strong>Einführung</strong> in C++<br />
Referenzen, Operator-Overloading,<br />
Namensräume, Klassen<br />
Prof. Dr. H. Drachenfels Version 5.0<br />
Hochschule Konstanz 25.2.2014<br />
C++: Überblick<br />
C++ ist eine von Bjarne Stroustrup entwickelte Erweiterung von C:<br />
• Namensräume<br />
• Referenzen<br />
• Ausnahmebehandlung<br />
• Überladen von Funktionen und Operatoren<br />
• Klassen, Vererbung, Polymorphie, dynamische Bindung<br />
• Templates<br />
• objektorientierte und Template-basierte Erweiterungen der Standardbibliothek<br />
(u.a. Ein-/Ausgabe-Klassen, String-Klasse, Vector-Klasse)<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-1<br />
Hochschule Konstanz
C++ Ein-/Ausgabe: Streams und Operatoren<br />
In C++ dienen Stream-Objekte als Eingabe-Quellen und Ausgabe-Ziele.<br />
Ein-/Ausgabe-Anweisungen werden mit den Operatoren > formuliert:<br />
#include // std::cout, std::cin, std::hex, std::endl, operator<br />
int main()<br />
{<br />
std::cout > zahl;<br />
std::cout
C++ Referenzen: Definition und Verwendung<br />
Eine Referenz definiert einen Aliasnamen für einen Speicherbereich.<br />
• Variablen-Definition:<br />
Typ Name = Wert;<br />
Typ &Aliasname = Name; // & kennzeichnet eine Referenz-Variable<br />
• Verwendung:<br />
als Parameter- und Rückgabetyp von Funktionen (insbesondere überladene Operatoren)<br />
der Compiler realisiert Referenz-Parameter mit Zeigern:<br />
void function(const int &n)<br />
{<br />
int m = n;<br />
...<br />
}<br />
...<br />
int k = 1;<br />
function(k);<br />
void function(const int *n)<br />
{<br />
int m = *n;<br />
...<br />
}<br />
...<br />
int k = 1;<br />
function(&k);<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-4<br />
Hochschule Konstanz<br />
C++ Operator-Overloading: Beispiel<br />
C++ erlaubt das Überladen von Operatoren für benutzerdefinierte Typen<br />
(wird unter anderem in der Ein-/Ausgabebibliothek verwendet).<br />
Beispiel:<br />
#include <br />
enum jahreszeit {fruehling, sommer, herbst, winter};<br />
std::ostream& operator
C++ Namensräume: Syntax<br />
Namensräume (Namespaces) verringern das Risiko von Namenskonflikten:<br />
• Namensraum-Deklaration:<br />
namespace Namensraumname<br />
{<br />
Deklarationen ...<br />
}<br />
namespace<br />
{<br />
Deklarationen ...<br />
}<br />
• Qualifizierung von Namen mit Scope Resolution Operator:<br />
Namensraumname::EinName<br />
Java-Entsprechung:<br />
package Paketname;<br />
definiert neuen Namensraum oder<br />
erweitert bestehenden Namensraum<br />
um weitere Deklarationen<br />
unbenannter Namensraum macht Deklarationen<br />
für andere Übersetzungseinheit unsichtbar<br />
• mit Using-Direktive auch Kurzschreibweise ohne Namensraumname:<br />
using namespace Namensraumname;<br />
Java-Entsprechung:<br />
EinName<br />
import Paketname.*;<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-6<br />
Hochschule Konstanz<br />
Beispiel-Programm Namensraum<br />
• Übersetzungseinheit Month<br />
(besteht nur aus Header-Datei):<br />
// Month.h<br />
#ifndef MONTH_H<br />
#define MONTH_H<br />
namespace htwg<br />
{<br />
enum Month<br />
{<br />
jan = 1, feb, mar,<br />
apr, may, jun,<br />
jul, aug, sep,<br />
oct, nov, dec<br />
};<br />
}<br />
#endif<br />
• Hauptprogramm<br />
(besteht nur aus Implementierungs-Datei):<br />
// enumvar.cpp<br />
#include "Month.h"<br />
using namespace htwg;<br />
#include <br />
using namespace std;<br />
int main()<br />
{<br />
Month m = htwg::oct;<br />
}<br />
cout
C++ Klassen: Eigenschaften<br />
C++Klassen fassen die C-Konzepte Struktur (struct) und Funktion zusammen<br />
• eine Klasse ist ein Bauplan für Objekte:<br />
die Klasse legt fest, welche Daten ihre Objekte enthalten<br />
und welche Funktionen Zugriff auf diese Daten haben (Kapselung).<br />
Die Daten heißen auch Attribute, Member-Daten oder Instanzvariablen.<br />
Die Funktionen heißen auch Operationen, Methoden oder Member-Funktionen.<br />
Zu den Funktionen zählen auch die Konstruktoren und der Destruktor.<br />
• jede Klasse hat mindestens einen Konstruktor:<br />
jedes neue Objekt wird garantiert mit einem Konstruktor-Aufruf intialisiert<br />
• jede Klasse hat genau einen Destruktor:<br />
bei jedem Objekt wird vor seiner Zerstörung (= Freigabe des Speichers)<br />
garantiert als letztes der Destruktor aufgerufen.<br />
Der Destruktor muss allen Speicher frei geben, der innerhalb der Klasse zusätzlich für das<br />
betreffende Objekt allokiert worden ist.<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-8<br />
Hochschule Konstanz<br />
C++ Klassen: Syntax (1)<br />
• Klassen-Deklaration (meist in einer Header-Datei Klassenname.h ):<br />
class Klassenname<br />
{<br />
public:<br />
Klassenname();<br />
// Default-Konstruktor<br />
~Klassenname();<br />
// Destruktor<br />
Klassenname(const Klassenname&);<br />
// Copy-Konstruktor<br />
Klassenname& operator=(const Klassenname&); // Zuweisungsoperator<br />
Rückgabetyp_1 Methode_1(...);<br />
...<br />
Rückgabetyp_N Methode_N(...);<br />
private:<br />
Datentyp_1 Instanzvariable_1;<br />
...<br />
Datentyp_M Instanzvariable_M;<br />
};<br />
Copy-Konstruktor, Destruktor,<br />
Zuweisungsoperator und<br />
eventuell den Default-Konstruktor<br />
ergänzt automatisch der Compiler,<br />
wenn sie fehlen<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-9<br />
Hochschule Konstanz
C++ Klassen: Syntax (2)<br />
• Methoden-Definitionen (meist in Implementierungsdatei-Datei Klassenname.cpp ):<br />
vor den Methodennamen muss Klassenname:: stehen<br />
Rückgabetyp_1 Klassenname::Methode_1(...)<br />
{<br />
... // Rumpf<br />
}<br />
...<br />
• die Funktionen einer Klasse haben implizit einen zusätzlichen Parameter this:<br />
Klassenname ∗ const this // konstanter Zeiger auf das Objekt des Aufrufs<br />
• Zugriff auf die private Instanzvariablen über this:<br />
this->Instanzvariable_1 // Kurzschreibeweise ohne this-> möglich<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-10<br />
Hochschule Konstanz<br />
C++ Klassen: Syntax (3)<br />
• Objekt-Erzeugung<br />
durch Variablen-Definition mit Klasse als Typ:<br />
Klassenname Objektname;<br />
oder per Operator new auf dem Heap:<br />
Klassenname ∗Objektzeiger = new Klassenname;<br />
• Objekt-Benutzung:<br />
Aufruf der öffentlichen Funktionen der zugehörigen Klasse<br />
mit Komponentenauswahl- und Methodenaufruf-Operator<br />
Objektname.Methode_1(...)<br />
Objektzeiger–>Methode_1(...)<br />
der Compiler wandelt die obigen Schreibweisen in einfache Funktionsaufrufe<br />
mit erstem Argument zum Initialisieren von this:<br />
Klassenname::Methode_1(&Objektname, ...)<br />
Klassenname::Methode_1(Objektzeiger, ...)<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-11<br />
Hochschule Konstanz
C++ Klassen: Konstruktoren (1)<br />
Konstruktoren sind diejenigen Funktionen einer Klasse, die Objekte initialisieren.<br />
• ein Konstruktor hat als Name den Klassennamen und hat keinen Rückgabetyp<br />
eine Klasse darf mehrere Konstruktoren haben, wenn sie unterschiedliche Parameter haben<br />
• ein parameterloser Konstruktor wird als Default-Konstruktor bezeichnet:<br />
Klassenname()<br />
wird eine Klasse ganz ohne Konstruktoren deklariert,<br />
erzeugt der Compiler implizit einen Default-Konstruktor,<br />
der für alle Instanzvariablen mit Klassen-Typ deren Default-Konstruktor aufruft<br />
• ein Konstruktor mit genau einem Parameter<br />
vom Typ konstante Referenz der Klasse wird als Copy-Konstruktor bezeichnet:<br />
Klassenname(const Klassenname &)<br />
initialisiert neues Objekt als Kopie eines bestehenden Objekts<br />
wird eine Klasse ohne Copy-Konstruktor deklariert,<br />
erzeugt der Compiler implizit einen, der die Daten komponentenweise kopiert<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-12<br />
Hochschule Konstanz<br />
C++ Klassen: Konstruktoren (2)<br />
Für Konstruktor-Implementierungen gibt es zwei Stile:<br />
• Initialisierungsliste im Methodenkopf (bevorzugter Stil)<br />
Klassenname::Klassenname()<br />
: Instanzvariable_1(Wert_1), ..., Instanzvariable_M(Wert_M)<br />
{ ... }<br />
• Zuweisungen im Methodenrumpf (funktioniert nicht bei const-Variablen)<br />
Klassenname::Klassenname()<br />
{<br />
Instanzvariable_1 = Wert_1;<br />
...<br />
Instanzvariable_M = Wert_M;<br />
}<br />
• Konstruktoren sollten unbedingt eine Ausnahme werfen,<br />
wenn sie ein Objekt nicht konsistent initialisieren können<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-13<br />
Hochschule Konstanz
C++ Klassen: Konstruktoren (3)<br />
Ein Konstruktor-Aufruf findet automatisch statt<br />
• beim Gültigwerden einer Variablen mit Klassen-Typ:<br />
Klassenname objektname;<br />
// Default-Konstruktor<br />
Klassenname objektname(einArgument); // Konstruktor mit Parameter<br />
Klassenname objektname = anderesObjekt; // Copy-Konstruktor<br />
globale Variablen sind gültig von Programmstart bis -ende<br />
lokale Variablen sind gültig vom Durchlaufen ihrer Definition<br />
bis zum Verlassen des umschließenden Anweisungsblocks<br />
• bei new mit einem Klassen-Typ:<br />
Klassenname ∗objektzeiger = new Klassenname;<br />
Klassenname ∗objektzeiger = new Klassenname(einArgument);<br />
• bei Wertparameter-Übergabe und Wert-Rückgabe von Funktions-Aufrufen:<br />
aFunction(objektname);<br />
return objektname;<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-14<br />
Hochschule Konstanz<br />
C++ Klassen: Destruktoren<br />
Ein Destruktor ist diejenige Funktion einer Klasse,<br />
die Objekte vor ihrer Zerstörung (Speicherfreigabe) aufräumt.<br />
• ein Destruktor hat als Name den Klassen-Namen mit vorangestellter Tilde<br />
und hat weder Parameter noch einen Rückgabetyp:<br />
~Klassenname()<br />
jede Klasse hat genau einen Destruktor<br />
wird eine Klasse ohne Destruktor deklariert, erzeugt der Compiler implizit einen Destruktor,<br />
der für alle Instanzvariablen mit Klassen-Typ deren Destruktor aufruft<br />
Ein Destruktor-Aufruf findet automatisch statt<br />
• beim Ungültigwerden einer Variablen mit Klassen-Typ:<br />
{<br />
Klassenname objektname;<br />
...<br />
} // objektname wird ungültig<br />
• jedem delete für einen Zeiger mit Klassen-Typ:<br />
delete objektzeiger;<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-15<br />
Hochschule Konstanz
Beispiel-Programm Klasse (1)<br />
• Quellcode Klassendeklaration (Date.h):<br />
class Date<br />
{<br />
public:<br />
Date();<br />
// Default-Konstruktor<br />
Date(int d, int m, int y); // Konstruktor mit Parametern<br />
Date(const Date &d);<br />
// Copy-Konstruktor<br />
~Date();<br />
Date &operator=(const Date &d);<br />
void print() const;<br />
private:<br />
int day;<br />
int month;<br />
int year;<br />
};<br />
// Destruktor<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-16<br />
Hochschule Konstanz<br />
Beispiel-Programm Klasse (2)<br />
• Quellcode Objektbenutzung:<br />
#include "Date.h"<br />
#include // std::cerr ...<br />
#include // std::invalid_argument<br />
int main()<br />
{<br />
try {<br />
Date d1;<br />
// Aufruf Default-Konstruktor<br />
Date d2(1, 9, 2000); // Aufruf Konstruktor mit Parametern<br />
Date d3 = d1;<br />
// Aufruf Copy-Konstruktor: Date d3(d1);<br />
d3 = d2;<br />
// Aufruf Zuweisungsoperator: d3.operator=(d2);<br />
d3.print();<br />
// Aufruf Date::print<br />
} // Destruktor-Aufrufe: d3.~Date(); d2.~Date(); d1.~Date();<br />
catch (std::invalid_argument &) {<br />
std::cerr
Beispiel-Programm Klasse (3)<br />
• Quellcode Konstruktoren (Date.cpp):<br />
Date::Date() // heimlicher Parameter: Date * const this<br />
{<br />
std::time_t t = std::time(0);<br />
std::tm ∗p = std::localtime(&t);<br />
}<br />
this->day = p->tm_mday<br />
this->month = p->tm_mon + 1;<br />
this->year = p->tm_year + 1900;<br />
Date::Date(int d, int m, int y)<br />
: day(d), month(m), year(y)<br />
{<br />
if (d < 1 || d > 31 || m < 1 || m > 12) throw std::invalid_argument();<br />
}<br />
Date::Date(const Date &d)<br />
: day(d.day), month(d.month), year(d.year)<br />
{ }<br />
Objekt werfen,<br />
nicht Objektadresse,<br />
deshalb ohne new<br />
diese Implementierungen<br />
des Copy-Konstruktors<br />
würde der Compiler auch<br />
automatisch erzeugen<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-18<br />
Hochschule Konstanz<br />
Beispiel-Programm Klasse (4)<br />
• Quellcode Destruktor, Zuweisung und Zugriffsfunktion (Date.cpp):<br />
Date::~Date()<br />
{ /∗ nichts zu tun ∗/ }<br />
Date& Date::operator=(const Date &d)<br />
{<br />
if (this != &d) { // keine Selbstzuweisung?<br />
this->day = d.day;<br />
this->month = d.month;<br />
this->year = d.year;<br />
}<br />
return ∗this;<br />
}<br />
diese Implementierungen von<br />
Destruktor und Zuweisung<br />
würde der Compiler auch<br />
automatisch erzeugen<br />
void Date::print() const<br />
{<br />
std::cout year<br />
C++ Klassen: Standard-Bibliothek (1)<br />
Ausschnitt aus der Klasse string (nach ISO-Standard noch komplizierter):<br />
class string<br />
{<br />
public:<br />
string();<br />
// Konstruktoren<br />
string(const string& str) ;<br />
string(const char ∗s);<br />
~string();<br />
// Destruktor<br />
};<br />
string& operator=(const string& str); // Zuweisungen<br />
string& operator=(const char ∗s );<br />
string& operator+=(const string& str);<br />
string& operator+=(const char ∗s);<br />
const char ∗c_str() const;<br />
unsigned length() const;<br />
// Datenabfragen<br />
const char& operator[](unsigned pos) const ;<br />
char& operator[](unsigned pos);<br />
...<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-20<br />
Hochschule Konstanz<br />
C++ Klassen: Standard-Bibliothek (2)<br />
Operatoren außerhalb der Klasse string (nach ISO-Standard noch komplizierter):<br />
// Verknüpfungen<br />
string operator+(const string& s1, const string& s2);<br />
...<br />
// Vergleiche<br />
bool operator==( const string & s1 , const string& s2 );<br />
...<br />
// Ein-/Ausgabe<br />
istream& operator>>(istream& is, string& s);<br />
ostream& operator buffer; // Risiko eines Pufferüberlaufs<br />
std::string s;<br />
std::cin >> s;<br />
// string-Objekt und operator>> sorgen für genug Speicher<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-21<br />
Hochschule Konstanz
C++ Klassen: Standard-Bibliothek (3)<br />
Ausschnitt aus der Template-Klasse vector (nach ISO-Standard noch komplizierter):<br />
template class vector<br />
{<br />
public:<br />
Template-Parameter<br />
vector();<br />
vector(unsigned n, const T& value = T());<br />
vector(const vector& v);<br />
~vector();<br />
vector& operator=(const vector& v);<br />
unsigned size() const;<br />
void resize(unsigned n, T c = T());<br />
T& operator[](unsigned i);<br />
T& at(unsigned i);<br />
...<br />
};<br />
template <br />
bool operator==(const vector& v, const vector& w);<br />
...<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-22<br />
Hochschule Konstanz<br />
C++ Klassen: Standard-Bibliothek (4)<br />
• zu fast jedem Typ kann ein Vektortyp abgeleitet werden:<br />
#include // damit std::vector bekannt ist<br />
// Vektor von vier ganzen Zahlen, alle mit 0 initialisiert:<br />
std::vector iv(4);<br />
// Vektor von zwei Strings, mit Leerstrings initialisiert:<br />
std::vector sv(2);<br />
• ein Vektor kennt im Gegensatz zum Feld seine Länge:<br />
for (int i = 0; i < iv.size(); i++) ...<br />
• Vektorzugriff per [] ohne oder per .at() mit Indexprüfung:<br />
iv[2] = 1; // std::vector::operator[](&iv, 2) = 1;<br />
iv.at(2) = 1; // std::vector::at(&iv, 2) = 1;<br />
• ein Vektor kann im Gegensatz zum Feld<br />
per Zuweisungs-Operator kopiert und per Vergleichsoperatoren verglichen werden<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-23<br />
Hochschule Konstanz
Beispiel-Programm: std::string<br />
#include <br />
#include <br />
int main()<br />
{<br />
std::string a = "halli"; // a("halli")<br />
std::string s = "hallo"; // s("hallo")<br />
std::string t; // leerer String<br />
// compare, copy and concatenate strings<br />
if (a < s) // operator
C++ Vererbung: Syntax<br />
• Unterklassen-Deklaration:<br />
class Unterklassenname : public Oberklassenname<br />
{<br />
public:<br />
// zusätzliche und überschriebene Methoden ...<br />
private:<br />
// zusätzliche Daten ...<br />
};<br />
• Definition von Unterklassen-Konstruktoren:<br />
bei einer public-Ableitung<br />
sind alle öffentlichen Methoden<br />
der Oberklasse auch in der<br />
Unterklasse öffentlich<br />
(entspricht Java extends)<br />
Unterklassenname::Unterklassenname()<br />
: Oberklassenname()<br />
{<br />
in der Initialisierungsliste muss ein<br />
...<br />
}<br />
Oberklassen-Konstruktor aufgerufen werden<br />
(fehlt der Aufruf, ergänzt der Compiler<br />
einen Aufruf des Oberklassen-Defaultkonstruktors)<br />
(entspricht Java super() )<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-26<br />
Hochschule Konstanz<br />
C++ Vererbung: Polymorphie und dynamische Bindung<br />
• nur Variablen vom Typ Zeiger auf Klasse oder Klassenreferenz<br />
können in C++ polymorph sein:<br />
Klassenname *Objektzeiger;<br />
Klassenname &Objektreferenz;<br />
• nur Methoden, die virtual markiert sind,<br />
können mit dynamischer Bindung aufgerufen werden:<br />
class Klassenname<br />
{<br />
...<br />
};<br />
virtual Rückgabetyp Methode(...);<br />
...<br />
erlauben auch Umgang mit<br />
Objekten einer Unterklasse<br />
zu Instanzmethoden ohne virtual gibt es in Java keine Entsprechung<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-27<br />
Hochschule Konstanz
C++ Vererbung: Schnittstellen (1)<br />
C++ macht leider keinen prinzipiellen Unterschied<br />
zwischen Klassen und Schnittstellen (beides class).<br />
• Schnittstellen-Deklaration:<br />
class Schnittstellenname<br />
{<br />
public:<br />
virtual ~Schnittstellenname() { }<br />
};<br />
virtual Rückgabetyp1 Methode1(...) = 0;<br />
...<br />
virtual RückgabetypN MethodeN(...) = 0;<br />
entspricht Java<br />
interface<br />
der Destruktor und die Methoden müssen public und virtual deklariert sein<br />
(nur virtual-Methoden werden mit dynamischer Bindung aufgerufen)<br />
entspricht Java<br />
der Destruktor muss eine leere Implementierung haben: { }<br />
abstract<br />
die Methoden haben keine Implementierung (pure virtual function): = 0<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-28<br />
Hochschule Konstanz<br />
C++ Vererbung: Schnittstellen (2)<br />
• Schnittstellen implementiert man per Vererbung mit abgeleiteten Klassen:<br />
class Klassenname : public virtual Schnittstellenname<br />
{<br />
public:<br />
// Konstruktoren, Destruktor usw. nach Bedarf<br />
Rückgabetyp1 Methode1( ... );<br />
...<br />
RückgabetypN MethodeN( ... );<br />
private:<br />
...<br />
};<br />
entspricht Java<br />
implements<br />
die Klassen-Deklaration wiederholt alle Methodensignaturen der Schnittstelle ohne = 0,<br />
wobei der Zusatz virtual fehlen darf<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-29<br />
Hochschule Konstanz
Beispiel-Programm Schnittstelle (1)<br />
• Quellcode Schnittstellendeklaration (Date.h):<br />
class Date<br />
{<br />
public:<br />
virtual ~Date() { }<br />
virtual void get(int *d, int *m, int *y) const = 0;<br />
};<br />
• Implementierungsdatei (Date.cpp) entfällt<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-30<br />
Hochschule Konstanz<br />
Beispiel-Programm Schnittstelle (2)<br />
• Quellcode Implementierungsklasse (CurrentDate.h):<br />
class CurrentDate : public virtual Date<br />
{<br />
public:<br />
void get(int *d, int *m, int *y) const;<br />
};<br />
• Quellcode weitere Implementierungsklasse (FixedDate.h):<br />
class FixedDate : public virtual Date<br />
{<br />
public:<br />
FixedDate(int d, int m, int y);<br />
void get(int *d, int *m, int *y) const;<br />
private:<br />
const int day;<br />
const int month;<br />
const int year;<br />
};<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-31<br />
Hochschule Konstanz
Beispiel-Programm Schnittstelle (3)<br />
• Quellcode Implementierungsklasse (CurrentDate.cpp):<br />
#include "CurrentDate.h"<br />
#include <br />
void CurrentDate::get(int *d, int *m, int *y) const<br />
{<br />
std::time_t t = std::time(0);<br />
std::tm *p = std::localtime(&t);<br />
}<br />
*d = p->tm_mday;<br />
*m = p->tm_mon + 1;<br />
*y = p->tm_year + 1900;<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-32<br />
Hochschule Konstanz<br />
Beispiel-Programm Schnittstelle (4)<br />
• Quellcode Implementierungsklasse (FixedDate.cpp):<br />
#include "FixedDate.h"<br />
#include <br />
FixedDate::FixedDate(int d, int m, int y)<br />
: day(d), month(m), year(y)<br />
{<br />
if (d < 1 || d > 31 || m < 1 || m > 12)<br />
{<br />
throw std::invalid_argument("Falsches Datum");<br />
}<br />
}<br />
void FixedDate::get(int *d, int *m, int *y) const<br />
{<br />
*d = day;<br />
*m = month;<br />
*y = year;<br />
}<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-33<br />
Hochschule Konstanz
Beispiel-Programm Schnittstelle (5)<br />
• Quellcode Objektbenutzung:<br />
#include "CurrentDate.h"<br />
#include "FixedDate.h"<br />
#include <br />
#include <br />
void printDate(Date *d)<br />
{<br />
int day;<br />
int month;<br />
int year;<br />
}<br />
...<br />
Polymorphie<br />
dynamische<br />
Bindung<br />
d->get(&day, &month, &year);<br />
std::cout
C++: Lernzettel<br />
C++ class Copy-Konstruktor Default-Konstruktor delete delete[]<br />
Destruktor Initialisierungsliste Klassendeklaration Methodendefinition<br />
Namensraum namespace new Operator-Overloading operator> private: public: pure virtual function Referenz<br />
Schnittstellendeklaration std::cin std::cout std::string std::vector Stream<br />
Unterklassendeklaration using virtual<br />
Prof. Dr. H. Drachenfels <strong>Systemprogrammierung</strong> 7-36<br />
Hochschule Konstanz