05.11.2014 Aufrufe

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

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!