Text
                    PC professionell
Arbeitsplatzrechner in
Ausbildung und Praxis
1
Kernighan/Ritchie
"
r
'I'
f
i
!
!',
1:.
ti
J.

,..
ti
r'
-
, 
.i:'i(
,
JII
.&
 -
 -=
F_


PC professionell Arbeitsplatzrechner in Ausbildung und Praxis Herausgeber: Prof. Dr.-Ing. Werner Heinzel, Fulda Brian W. Kernighan Dennis tVrRitchie PC professionell zeigt dem Anwender von Personal Computern den Aufbau, die Program- mierung und die verschiedenen Anwendungsmöglichkeiten seines "persönlichen Rechners am Arbeitsplatz" und führt ihn am Beispiel aktueller PC-Systeme oder mit der Darstellung und Diskussion von Fallbeispielen in die betriebliche Praxis derartiger Geräte ein. Personalcomputer werden in den verschiedensten Anwendungsbereichen eingesetzt: - in Klein- und Mittelbetrieben - als persönliches Arbeitsmittel in Fachabteilungen von Großbetrieben - als Ausbildungsmittel in Schulen, Hochschulen und Fortbildungsstätten - im privaten Bereich Programmieren in C Mit dem C-Reference Manual in deutscher Sprache PC professionell wendet sich an diese verschiedenen Benutzer des Personal Computers. Aufgrund des didaktischen Aufbaus und der eingearbeiteten Praxisbeispiele eignen sich die Bände zum Selbststudium. Zweite Ausgabe ANSIC Die Fachbuchreihe weist die folgenden thematischen Schwerpunkte auf: Grundlagen der Herd- und Softweretechnologle von Personel Computern, zum Beispiel - Systemstrukturen - Betriebssysteme - Programmiersprachen - Softwarewerkzeuge Die deutsche Ausgabe besorgten Prof. Dr. AT. Schreiner und Dr. Ernst Janich Reellsierte Systeme und deren Anwendungen, zum Beispiel - Benutzeranleitung für bestimmte PC-Systeme - Programmierung typischer Systeme - Anwendungen, Fallbeispiele, Softwaresammlungen - Softwarelösungen für bestimmte Branchen bzw. Anwendungsgebiete A B Gart Hanser Verlag München Wien Eine Coedition der Verlage Carl Hanser und Prentice-Hall International 
v Titel der amerikanischen Originalausgabe: "The C Programming Language, Second Edition, ANSI C" by Brian W. Kernighan and Dennis M. Ritchie @ Copyright 1988, 1978 by Bell Telephone Laboratories, Incorporated. Original English language edition published by Prentice-Hall International, a Division of Si mon & Schuster Englewood ClifTs, New Jersey 07632 All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form or by any means, electronic, mechanical, photocopying, recording, or otherwise, without the prior written permission of the publisher. UNIX™ ist ein eingetragenes Warenzeichen der AT&T Bell Laboratories. Vorwort Alle in diesem Buch enthaltenen Programme und Verfahren wurden nach bestem Wissen erstellt und mit Sorgfalt getestet. Dennoch sind Fehler nicht ganz auszuschließen. Aus diesem Grund ist das im vorliegenden Buch enthaltene Programm-Material mit keiner Verpflichtung oder Garantie irgend- einer Art verbunden. Autor und Verlag übernehmen infolgedessen keine Verantwortung und werden keine daraus folgende oder sonstige Haftung übernehmen, die auf irgendeine Art aus der Benutzung dieses Programm-Materials oder Teilen davon entsteht. Seit der Veröffentlichung von Programmieren in C im Jahr 1978 hat in der Welt der Computer eine Revolution stattgefunden. Große Computer sind viel größer und Perso- nal-Computer leisten ähnlich viel wie die Zentralrechner vor zehn Jahren. Auch C hat sich in der Zwischenzeit geändert, allerdings nur in bescheidenem Maße, und es hat sich weit über seine Anfänge als Sprache des UNIX-Betriebssystems hinaus verbreitet. Die zunehmende Popularität von C, die Änderungen an der Sprache im Lauf der Jahre und die Entwicklung von Übersetzern durch Gruppen, die nicht am Sprachentwurf beteiligt waren, haben gemeinsam die Notwendigkeit einer präziseren und zeitgemäßeren Sprachdefmition ergeben, als sie die Erste Ausgabe dieses Buches enthalten hat. 1983 richtete das American National Standards Institute (ANSI) eine Kommission mit dem Ziel ein, "eine eindeutige und maschinenunabhängige Defmition der Sprache C" zu erarbei- ten, die aber den Geist der Sprache erhalten sollte. Das Resultat ist der ANSI-Standard fürC. Umschlagkonzeption: Hans Peter Will berg Druck: Druckerei Appl, Wemding Buchbinderische Verarbeitung: Ludwig Auer, Donauwörth @ am Layout: Carl Hanser Verlag München Wien Printed in Germany Der Standard formalisiert Konstruktionen, auf die zwar in der (englischen) Ersten Ausgabe hingedeutet wurde, die aber dort nicht beschrieben wurden, insbesondere Strukturzuweisungen und Aufzählungstypen. Der Standard enthält eine neue Art von Funktionsdeklaration, die die Überprüfung von Defmition und Benutzung ermöglicht. Er legt eine Standard-Bibliothek fest, mit einem ausführlichen Satz von Funktionen für Eingabe und Ausgabe, Speicherverwaltung, Operationen mit Zeichenketten und für ähn- liche Aufgaben. Der Standard präzisiert das Verhalten von Konzepten, die in der ur- sprünglichen Defmition nicht detailliert wurden, und stellt gleichzeitig klar heraus, wel- che Aspekte der Sprache maschinenabhängig bleiben. Die vorliegende zweite Ausgabe von Programmieren in C beschreibt C nach der Defmition im ANSI-Standard. Wir haben zwar die Stellen gekennzeichnet, wo sich die Sprache weiterentwickelt hat, aber wir haben uns auch entschieden, ausschließlich im neuen Stil zu schreiben. Im großen und ganzen sollte das keinen wesentlichen Unter- schied machen; die sichtbarste Änderung ist die neue Form der Deklaration und Definiti- on von Funktionen. Moderne Übersetzer unterstützen schon die meisten Konzepte des Standards. Wir haben versucht, die Kürze der Ersten Ausgabe beizubehalten. C ist keine um- fangreiche Sprache, und ein dickes Buch wird ihr nicht sehr gerecht. Wir haben die Prä- sentation besonders wichtiger Konzepte verbessert, wie zum Beispiel von Zeigern, denn sie bilden den Kern der C-Programmierung. Wir haben die ursprünglichen Beispiele verfeinert und haben in mehreren Kapiteln neue hinzugefügt. Zum Beispiel ist jetzt die Abhandlung über komplizierte Vereinbarungen durch Programme erweitert, die Verein- barungen in lesbaren (deutschen) Text verwandeln und umgekehrt. Der Text dieses Bu- ches liegt maschinenlesbar vor; wie früher wurden alle Beispiele direkt aus dem Text ge- testet. Anhang A, die C-Sprachbeschreibung, ist nicht der Standard, sondern unser Ver- such, die wesentlichen Punkte des Standards auf kleinerem Raum zu vermitteln. Er soll für Programmierer leichtverständlich sein, aber nicht als Definition für die Konstrukteure von Übersetzern dienen - diese Rolle gebührt dem Standard selbst. Anhang B ist eine CIP-Titelaufnahme der Deutschen Bibliothek Kemighan, Brian W.: Programmieren in C : mit dem C-reference-Manual in deutscher Sprache I Brian W Kernighan ; Dennis M. Ritchie. Die dt. Ausg. besorgten A. T. Schreiner u. Ernst Janich. - 2. Ausg., ANS I C - München; Wien: Hanser; London : Prentice-Hall Internat., 1990 (PC professionell) Einheitssacht. : The C-programming-Ianguage (dt.) Erg. bildet: Tondo, Clovis L.: Das 'C-Lösungsbuch ISBN 3-446-15497-3 (Hanser) ISBN 0-H-110330-X (Prentice-Hall Internat.) NE: Ritchie, Dennis M.: Dieses Werk ist urheberrechtlieh geschützt. Alle Rechte, auch die der Übersetzung, des Nachdrucks und der Vervielfältigung des Buches, oder Teilen daraus, vorbehalten. Kein Teil des Werkes darf ohne schriftliche Genehmigung des Verlages in irgendeiner Form (Fotokopie, Mikrofilm oder ein anderes Verfahren), auch nicht für Zwecke der Unterrichtsgestaltung, reproduziert oder unter Verwendung elektronischer Systeme verarbeitet, vervielfältigt oder verbreitet werden. Eine Coedition der Verlage: Carl Hanser Verlag München Wien Prentice-Hall International Inc., London @ 1990 Prentice-Hall International Inc., London 
vi Vorwort VII Zusammenfassung der Standard-Bibliothek. Auch dieser Anhang soll zum Nachschlagen für Programmier sein, nicht für Implementierer. Anhang C ist eine knappe Zusam- menfassung der Anderungen des Standards gegenüber der ursprünglichen Definition von C. Brian W. Kernighan Dennis M. Ritchie Vorwort zur Ersten Ausgabe C ist eine Programmiersprache für allgemeine Anwendungen mit einfacher Aus- drucksweise, modemen Kontroll- und Datenstrukturen und einer reichen Auswahl an Operatoren. C ist weder hochabstrahierend, noch umfangreich und wurde auch nicht für ein spezielles Anwendungsgebiet entworfen. Aber das Fehlen von Einschränkungen und die allgemeine Verwendbarkeit machen C bequemer und effektiver für viele Aufgaben als angeblich mächtigere Programmiersprachen. C wurde ursprünglich entworfen und implementiert für das UNIX-Betriebssystem auf der DEC PDP-ll von Dennis Ritchie. Das Betriebssystem, der C- Übersetzer und praktisch alle UNIX-Anwendungsprogramme (insbesondere auch alle Programme, die zur Drucklegung dieses Buches verwendet wurden) sind in C geschrieben. Übersetzer für Produktionszwecke existieren für verschiedene andere Maschinen, darunter das IBM Sy- stem/370, das HoneyweIl 6000 System und die Interdata 8/32. C ist jedoch nicht an eine spezielle Hardware- oder Systemkonfiguration gebunden, und es ist leicht, Programme zu schreiben, die unverändert auf jeder Maschine ablaufen können, die C unterstützt. Das vorliegende Buch möchte dem Leser helfen, in C programmieren zu lernen. Es enthält zunächst einen informellen Überblick, damit neue Benutzer so schnell als möglich anfangen können. Danach folgen einzelne Kapitel über die hauptsächlichen Sprachkonzepte und schließlich eine Sprachdefinition. Wir stellen C vor allem durch Le- sen, Schreiben und Überarbeiten von Beispielen vor und nicht nur durch die Aufzählung von Regeln. Die Beispiele sind überwiegend komplette, funktionierende Programme und nicht nur isolierte Programmfragmente. Der Text dieses Buches existiert in maschinen- lesbarer Form; alle Beispiele wurden direkt aus diesem Text getestet. Wir haben nicht nur gezeigt, wie man die Sprache effektiv benutzt; wir haben auch versucht, soweit dies möglich ist, nützliche Algorithmen zu illustrieren sowie die Prinzipien guten Program- mierstils und soliden Programmentwurfs. Das Buch ist keine Einführung in das Programmieren; wir gehen davon aus, daß dem Leser einfache Programmierkonzepte - wie Variablen, Zuweisungen, Schleifen und Funktionen - geläufig sind. Ein unerfahrener Programmierer sollte trotzdem aus die- sem Text C erlernen können; allerdings wäre die Hilfe eines erfahrenen Kollegen nütz- lich. Wie wir im Vorwort zur Ersten Ausgabe schrieben, "bewährt sich [C) mit steigen- der Erfahrung". Mit einem Jahrzehnt mehr an Erfahrung denken wir immer noch so. Wir hoffen, daß Ihnen dieses Buch hilft, C zu lernen und gut zu benutzen. Wir schulden großen Dank den Freunden, die uns halfen, diese zweite Ausgabe zu produzieren. Jon Bentley, Doug Gwyn, Doug Mdlroy, Peter Nelson und Rob Pike gaben uns durchdachte Kommentare zu nahezu jeder Seite von Entwürfen. Für sorgfältiges Le- sen danken wir Al Abo, Dennis Allison, Joo Campbell, G. R. Emlin, Karen Fortgang, Al- len Holub, Andrew Hume, Dave Kristol, John Linderman, Dave Prosser, Gene Spafford und Chris Van Wyk. Hilfreiche Vorschläge machten Bill Cheswick, Mark Kernighan, An- dy Koonig, Robin Lake, Tom London, Jim Reeds, Clovis Tondo und Peter Weinberger. Dave Prosser beantwortete viele detaillierte Fragen zum ANSI-Standard. Wir benutzten Bjarne Stroustrup's C++ Übersetzer ausgiebig für lokale Tests unserer Programme und Dave Kristol stellte uns einen ANSI-C Übersetzer zum abschließenden Test zur Verfü- gung. Rich Drechsler half sehr bei der Drucklegung. Wir danken allen sehr. Nach unserer Erfahrung hat sich C als freundliche, ausdrucksstarke und flexible Sprache für einen breiten Anwendungsbereich erwiesen. C ist leicht zu erlernen und be- währt sich mit steigender Erfahrung. Wir hoffen, daß Ihnen dieses Buch hilft, C gut zu benutzen. Wohlüberlegte Kritik und Vorschläge von vielen Freunden und Kollegen haben zu diesem Buch und unserem eigenen Vergnügen dar an beigetragen. Insbesondere Mike Bianchi, Jim Blue, Stu Feldman, Doug McIlroy, Bill Roome, Bob Rosin und Larry Rosler haben alle mehrere Versionen sorgfältig gelesen. Wir sind auch Al Aho, Steve Bourne, Dan Dvorak, Chuck Haley, Debbie Haley, Marion Harris, Rick Holt, Steve Johnson, 
Vor : zur Ersten Ausgabe ix viü John Mashey, Bob Mitze, Ralph Muha, Peter Nelson, Elliot Pinson, Bill Plauger, Jerry Spivack, Ken Thompson und Peter Weinberger dankbar für hiHreiche Kommentare an verschiedenen Stellen und Mike Lcsk und Joe Ossanna für unschätzbare Hilfe bei der Drucklegung. Brian W. Kemighan Dennis M. Ritchie Vorwort zur deutschen Ausgabe Dieses Buch erschien erstmals 1978 in den USA und 1983 in Deutschland. Die Er- ste Ausgabe galt über zehn Jahre in den USA und über fünf Jahre im deutschen Sprach- raum als Standard und Definition für C, eine Programmiersprache, die sich keineswegs nur in UNIX-Kreisen sehr großer Beliebtheit erfreut. C wird gerne verwendet, und in C wird gute Software entwickelt, denn für C gibt es von den ursprünglichen Entwicklern diese ausgezeichnete Beschreibung, die nicht nur Vokabeln erklärt, sondern die anhand einer Vielzahl realistischer Beispielprogramme die Prinzipien zweckmäßiger Software- Entwicklung mit C vermittelt. Das American National Standards Institute (ANSI) verabschiedet jetzt einen Stan- dard für C. Man darf annehmen, daß sich ISO und DIN anschließen werden. Kernighan und Ritchie halten sich in der vorliegenden Zweiten Ausgabe an den neuen ANSI- Standard. Damit gibt es nach wie vor ein Buch über C, in dem vor allem steht, wie sich die Erfmder den Umgang mit ihrem Werkzeug vorstellen. Das Buch eignet sich aber auch für Umsteiger von "K&R-C' auf ANSI-C: im Anhang C sind die wichtigsten Ände- rungen auf knapp vier Seiten zusammengefaßt, und im Anhang A in der C-Sprachbe- schreibung kommentieren Kernighan und Ritchie leger-bissig die Entwicklungen der letzten zehn Jahre. Wir konnten im allgemeinen sehr wortgetreu übersetzen, mit einer wesentlichen Ausnahme: im Deutschen kann man Deklarationen, die Eigenschaften von Namen ver- einbaren, völlig von Definitionen unterscheiden, die dazu auch noch Speicherplatz bereit- stellen, denn für beide Vorgänge zusammen gibt es im Deutschen noch den Oberbegriff der Vereinbanmg. Diese Unterscheidung sollte vor allem die C-Sprachbeschreibung im Anhang A leichter verständlich machen. Schon in der Ersten Ausgabe haben wir uns sehr sorgfältig bemüht, in der Überset- zung einige Begriffe zu prägen: Wir sprechen von Vektoren, damit diese nicht im Kon- flikt ZU Bit-Feldern stehen, von denen es ja gerade keine Vektoren gibt. Vektoren haben Elemente, die Komponenten überlassen wir den Strukturen. Wir unterscheiden auch sehr sorgfältig die aktuellen Argumente, deren Werte in C an die formalen Parameter von Funktionen übergeben werden. In dieser Zweiten Ausgabe kommen einige neue Begriffe hinzu, und manche Be- griffe wurden geändert. Bei der Wahl der Fachausdrücke ließen wir uns von gewissen Kriterien leiten: Wir folgen dem Sprachgebrauch und sprechen jetzt doch von einer Bibliothek. Wir möchten "selbst-verständliche" Worte verwenden und sagen deshalb zum Beispiel statt Literallieber Konstante. Wir möchten nicht unnötig "neu-deutsch" formu- lieren und bevorzugen deshalb statt Compiler lieber Obersetzer, statt Header-Datei lieber nach wie vor Dejinitionsdatei und statt String lieber Zeichenkette . Aus dem Verweisopcrator * wurde der Inhaltsoperator , denn Verweis kann man zu leicht mit reference und damit vielleicht mit dem Adreßoperator & verwechseln. Die Ver- einigung verschiedener Typen zu verschiedener Zeit in einem Objekt, kurz union, haben wir von Variante in der Ersten Ausgabe jetzt in Union umgetauft, und ihre Komponenten nennen wir Alternativen. Den Namen einer Struktur bezeichnen wir als Etikett, denn auch eine Union und eine Aufzählung können ein Etikett haben. Die neuen Eigenschaften CODst und volatile bezeichnen wir alsAttribute eines Typs. 
x Vorwort z' ieutschen Ausgabe xi Insgesamt hat uns bei der Wortwahl sehr stark Siegfried Mahkorn's DlN-Wortliste* beeinflußt. Das Sachverzeichnis am Schluß des vorliegenden Buchs enthält eine Reihe von Querverweisen von englischen Begriffen zu unseren deutschen Übersetzungen. Der Satz dieses Buchs erfolgte natürlich mit einem UNIX-System: wir verwendeten Liangs Trennalgorithmus aus Knuths TEX mit deutschen Trennmustern von Norbert Schwarz, ein Programm zur Aufbereitung von C-Programmen, Kernighans Zeichenspra- che pie, Lesks Tabellensetzer tbl, Kernighan und Cherrys mathematischen Prozessor eqn, den für XENIX angepaßten device-independent troff der ELAN Computer Group, ein neues Dialog-Layout-Programm sowie ELANs Postprozessor für SoftPonts auf einem LaserJet+ von Hewlett-Packard. Brian Kernighan hat uns ein Magnetband mit den Originalquellen zur Verfügung gestellt, und er hat uns eine Reihe von Korrekturen über UseNet geschickt. Ulrich Breckel, OUmar Schuhbauer und Silke Seehusen lasen die C-Sprachbeschreibung, Elke und Peter Albrecht sahen den ganzen Text durch und Mitarbeiter des Carl Hanser Ver- lags plagten sich mit den Buchstabier- und Trennungslisten. Wir danken all diesen Hel- fern - hierbei auch einem namenlosen, engagierten Lektor - sehr herzlich. Ulm und Hollage, im November 1989 Ernst Janich Axel T. Schreiner Inhaltsverzeichnis . ..Zweisprachiges Register YOII Benennungen aus Nonnen und Nonn-Entwürfen des DlN" Arbeilspapier 395 der Gesellschaft für Mathematik und Datenverarbeitung mbH, Sankt Augustin, Juni 1989. Vorwort . . . . . . . . Vorwort zur Ersten Ausgabe Vorwort zur deutscben Ausgabe Elnmbrong....... . 1 Eine Oberslcbt In Beispielen 1.1 Erste Schritte 1.2 Variablen und Arithmetik 1.3 Die for-Anweisung 1.4 Symbolische Konstanten 1.5 Zeicheneingabe und Ausgabe 1.5.1 Dateien kopieren 1.5.2 Zeichen zählen 1.5.3 Zeilen zählen 1.5.4 Wörter zählen 1.6 Vektoren . . . . . 1.7 Funktionen . . . . 1.8 Argumente - Wertübergabe 1.9 Zeichcnvektoren . . . . 1.10 Externe Variablen und Gültigkeitsbereich Z Datentypen, Operatoren und Ausdrücke 2.1 Variablennamen . . . . . . 2.2 Datentypen und Speicherbedarf 2.3 Konstanten . . . . . . 2.4 Vereinbarungen ...... 2.5 Arithmetische Operatoren 2.6 Vergleiche und logische Verknüpfungen 2.7 Typumwandlungen ........ 2.8 Inkrement- und Dekrement-Operatoren 2.9 Bit-Manipulationen . . . . 2.10 Zuweisungen und Ausdrücke .... 2.11 Bedingter Ausdruck . . . . . . . . 2.12 Vorrang und Reihenfolge bei Bewertungen 3 Kontrollstrokturen .... 3.1 Anweisungen und Blöcke 3.2 if-else 3.3 else-if . . . . . . . 3.4 switch . . . . . . . 3.5 Schleifen - while und for 3.6 Schleifen - do-while 3.7 break und continue 3.8 goto und Marken . v vii ix 1 5 5 8 13 14 15 15 17 19 20 21 23 26 27 30 35 35 36 37 39 40 41 42 46 47 49 50 51 55 55 55 56 58 59 62 63 64 
"1 xii Inhaltsverzeichnis Inhaltsverzeichn' xiii 4 Funktionen und Programmstruktur 67 7.6 Fehlerbehandlung - stde" und exit 156 4.1 Grundbegriffe 67 7.7 Zeilen-Eingabe und -Ausgabe 158 4.2 Funktionen ohne gan77.ali ges Resultat 70 7.8 Weitere Funktionen 159 4.3 Externe Variablen . 72 7.8.1 Operationen mit Zeichenketten 159 4.4 Regeln zum Gültigkeitsbereich 78 7.8.2 Tests für Zeichenklassen und Umwandlung 160 4.5 Detinitionsdateien . 80 7.8.3 ungetc 160 4.6 static . 81 7.8.4 Kommandoausführung 160 4.7 register 81 7.8.5 Speicherverwaltung 160 4.8 Blockstruktur 82 7.8.6 Mathematische Funktionen 161 4.9 Initialisierung 83 7.8.7 ZufallszahJengenerator 161 4.10 Rekursion . 84 8 Die Scbnittstelle zum UNIX-Betriebssystem 163 4.11 Der C-Preprozessor 86 8.1 FHe-Deskriptoren 163 4.11.1 Defmitionsdateien einfügen 86 8.2 Elementare Ein- und Ausgabe - read und write 164 4.11.2 Textersatz . 87 8.3 open, creat, close, unlink 165 4.11.3 Bedingte Übersetzung 88 8.4 Random-Zugriff - /seek 168 5 Zeiger und Vektoren 91 8.5 Beispiel: Eine Implementierung vonfopen undgetc 168 5.1 Zeiger und Adresser. . 91 8.6 Beispiel: Kataloge ausgeben 172 5.2 Zeiger und Funktionsargumente 93 8.7 Beispiel: Funktionen zur Speicherverwaltung 177 5.3 Zeiger und Vektoren . 95 A C-Spracbbescbrelbung 183 5.4 Adreß-Arithmetik . 97 A,1 Einführung 183 5.5 chor-Zeiger und Funktionen 101 A,2 Lexikalische Konventionen 183 5.6 Vektoren von Zeigern; Zeiger auf Zeiger 104 A,2.1 Eingabesymbole 183 5.7 Mehrdimensionale Vektoren . 107 A,2.2 Kommentare 183 5.8 Initialisierung von Zeigervektoren . 109 A,2.3 Namen 184 5.9 Zeiger kontra mehrdimensionale Vektoren 109 A,2.4 Reservierte Worte . 184 5.10 Argumente aus der KommandozeHe 110 A,2.5 Konstanten 184 5.11 Zeiger auf Funktionen 114 A,2.6 Konstante Zeichenketten 186 5.12 Komplizierte Vereinbarungen 117 A,3 Syntax-Schreibweise 187 6 Strukturen . 123 A,4 Die Bedeutung von Namen 187 6.1 Die Grundbegriffe 123 A.4.1 Speicherklasse 187 6.2 Strukturen und Funktionen 125 A,4.2 Elementare Datentypen 188 6.3 Vektoren von Strukturen 127 A,4.3 Abgeleitete Typen . 189 6.4 Zeiger auf Strukturen 131 A,4.4 Attribute für Typen 189 6.5 Rekursive Strukturen 133 A,5 Objekte und L-Werte 189 6.6 Suchen in Tabellen 138 A,6 Typumwandlungen 189 6.7 typedef 140 A,6.1 Integer-Erweiterung 189 6.8 Unionen 141 A,6.2 Integer-Umwandlung 190 6.9 Bit-Felder 143 A,6.3 Integer- und Gleitpunktwerte 190 7 Eingabe und Ausgabe 145 A,6.4 Gleitpunkttypen 190 A,6.5 Arithmetische Umwandlungen 190 7.1 Standard-Eingabe und Standard-Ausgabe 145 A,6.6 Zeiger und Integer-Werte 191 7.2 Formatierte Ausgabe - printf 147 A,6.7 void 192 7.3 Variable Argumentlisten 149 A,6.8 Zeiger auf void . 192 7.4 Formatierte Eingabe - scanf 150 7.5 Dateizugriff 153 
xiv A,7 Ausdrücke .......... A,7.1 Erzeugung von Zeigerwerten A,7.2 Primärausdrücke . A,7.3 PostflX-Ausdrücke. A,7.4 Unäre Operatoren A,7.5 Typumwandlungen A,7.6 Multiplikative Operatoren. A,7.7 Additive Operatoren A,7.8 Shift-Operatoren . . A,7.9 Vergleiche .... A,7.10 Äquivalenzvergleiche A,7.11 UND-Verknüpfung von Bits A,7.12 Exklusive ODER-Verknüpfung von Bits A,7.13 ODER-Verknüpfung von Bits A.7.14 Logische UND-Verknüpfung . A,7.15 Logische ODER-Verknüpfung A,7.16 Bedingter Ausdruck . A,7.17 Zuweisungen A,7.18 Komma als Operator A,7.19 Konstante Ausdrücke A,8 Vereinbarungen A,8.1 Speicherklassen A.8.2 Typangaben . . A,8.3 Strukturen und Unionen A,8.4 Aufzählungen . . . . A,8.5 Deklaratoren .... A,8.6 Die Bedeutung von Deklaratoren A,8.7 lnitialisierung A,8.8 Typnamen . . . . . A,8.9 typedef . . . . . . A,8.10 Äquivalenz von Typen A,9 Anweisungen ...... A,9.1 Marken an Anweisungen A,9.2 Ausdruck als Anweisung A,9.3 Block . . . . . . . A,9.4 Auswahlanweisungen A,9.5 Wiederholungsanweisungen A,9.6 Sprunganweisungen . A,10 Externe Vereinbarungen A,10.1 FunktionsdefInitionen A,10.2 Externe Vereinbarungen A,11 Gültigkeitsbereich und Bindung A,lU Gültigkeitsbereich im Text A,11.2 Bindung . . . . . . . Inhaltsverzeichnis Inhaltsverzeichn xv 193 193 194 194 197 198 199 199 200 200 201 201 201 201 202 202 202 203 203 204 204 205 206 207 210 211 211 215 217 217 218 218 219 219 219 220 220 221 222 222 224 225 225 226 A,12 Der Preprozessor . . . . . A,12.1 Drei-Zeichen-Folgen A,U.2 Verbinden von Zeilen A,12.3 MakrodefInition und Expansion A,12.4 Einfügen von Dateien A,12.5 Bedingte Übersetzung A,12.6 Zeilenkontrolle . A,12.7 Fehlermeldungen A,U.8 pragma . . . . A,U,9 Leere Anweisung A,12.10 VordefInierte Namen A,13 Grammatik . . . . . . . B Die Standard-Bibliothek .... B.1 Ein- und Ausgabe: <stdio.h> B.1.1 Dateioperationen . . B.1.2 Formatierte Ausgabe B.1.3 Formatierte Eingabe . B,1.4 Ein- und Ausgabe von Zeichen B.1.5 Direkte Ein- und Ausgabe B.1.6 Positionieren in Dateien B.1.7 Fehlerbehandlung . . . . . B.2 Tests für Zeichenklassen: < ctype.h > B.3 Funktionen für Zeichenketten: < string.h > B.4 Mathematische Funktionen: < math.h > B.5 Hilfsfunktionen: < stdlib.h > . . . . B.6 Fehlersuche: < assert.h > . . . . . B. 7 Variable Argumentlisten: < stdarg.h > B.8 Globale Sprünge: <setjmp.h> . . B.9 Signale: < signal.h > . . . . . . B.10 Funktionen für Datum und Uhrzeit: <time.h> B.11 Grenzwerte einer Implementierung: <limits.h> und <float.h> C Änderungen in Kürze Sachverzeichnis . . . . . . . . . . . 226 227 227 227 229 230 231 231 231 231 232 232 239 239 240 241 243 244 246 246 247 247 248 249 251 253 253 254 254 255 257 259 263 
1 Einführung C ist eine Programmiersprache für allgemeine Anwendungen. C ist eng mit dem uNIX-Betriebssystem verbunden, wo die Sprache entwickelt wurde, denn sowohl das Sy- stem selbst, als auch die meisten Programme, die damit eingesetzt werden, sind in C ge- schrieben. Die Sprache selbst ist jedoch nicht von einem bestimmten Betriebssystem oder einer bestimmten Maschine abhängig. C wurde zwar als "Systemprogrammierspra- che" bezeichnet, da die Sprache sich sehr gut Zum Schreiben von Übersetzern und Be- triebssystemen eignet; wesentliche Programme in vielen anderen Bereichen wurden je- doch ebenfalls in C realisiert. Viele der wichtigen Ideen in C stammen von der Sprache BCPL, die Martin Ri- chards entwickelt hat. BCPL beeinflußte C indirekt durch die Sprache B, die Ken Thomp- son 1970 für das erste UNIX -System auf der DEC PDP-7 implementiert hat. BCPL und B sind "typenlose" Sprachen. Im Gegensatz dazu hat C eine Reihe von Datentypen. Die elementaren Typen sind Zeichen sowie ganze Zahlen und Gleitpunkt- zahlen verschiedener Größe. Darüber hinaus gibt es eine Hierarchie von abgeleiteten Datentypen, die mit Hilfe von Zeigern, Vektoren, Strukturen und Unionen erzeugt wer- den. Ausdrücke werden aus Operatoren und Operanden gebildet; jeder Ausdruck, insbe- sondere eine Zuweisung und ein Funktionsaufruf, kann eine Anweisung sein. Zeiger er- lauben maschinenunabhängige Adreß-Arithmetik. C besitzt die fundamentalen Kontrollstrukturen, die für wohlstrukturierte Pro- gramme notwendig sind: Zusammenfassung von Anweisungen, Entscheidungen (if-else), Auswahl von einem aus einer Menge von möglichen Fällen (switch), Schleifen mit Test des Abbruchkriteriums am Anfang (while, for) oder am Ende (do) sowie vorzeitiges Ver- lassen einer Schleife (break). Funktionen können Werte der elementaren Typen, aber auch von Strukturen, Unionen oder Zeigern als Resultat liefern. Jede Funktion darf rekursiv aufgerufen wer- den. Die lokalen Variablen einer Funktion sind typischerweise "automatisch", das heißt, sie werden bei jedem Aufruf der Funktion neu erzeugt. FunktionsdefInitionen können nicht verschachtelt werden, jedoch können Variablen im Stil der Algol-Blockstruktur ver- einbart werden. Die Funktionen eines C-Programms können sich in verschiedenen Ouelldateien befmden, die getrennt voneinander übersetzt werden. Variablen können in- tern zu einer Funktion oder extern und trotzdem nur innerhalb einer Ouelldatei bekannt oder auch im ganzen Programm erreichbar defIniert werden. Ein Preprozcssor ersetzt Makros im Programmtext, fügt andere Ouelldateien ein und ermöglicht bedingte Übersetzung. C ist eine relativ "maschinennahe" Sprache. Diese Charakterisierung ist nicht ab- wertend zu sehen; sie drückt nur aus, daß C mit den gleichen Objekten umgeht wie die meisten Computer selbst, nämlich mit Zeichen, Zahlen und Adressen. Diese können verknüpft und verschoben werden mit den arithmetischen und logischen Operatoren, die üblicherweise in echten Maschinen vorhanden sind. C verfügt über keine Operationen, mit denen man zusammengesetzte Objekte wie Zeichenketten, Mengen, Listen oder Vektoren direkt bearbeiten kann. Es gibt keine Operationen, die einen ganzen Vektor oder eine Zeichenkette manipulieren; man kann 
2 Einführung Einführung 3 jedoch eine Struktur als Einheit kopieren. Die Sprache selbst defIniert keine Speicher- verwaltung außer statischer Defmition und der Stack-Verwaltung, die für lokale Varia- blen in Funktionen besteht; es gibt keine Halde (heap) oder Wiedergewinnung von Spei- cher (garbage collection). Schließlich hat C selbst keine Einrichtungen für Ein- und Aus- gabe; es gibt keine READ- oder WRlTE-Anweisungen und keine eingebauten Techniken für Dateizugriff. Alle diese abstrakteren Mechanismen müssen als explizit aufgerufene Funktionen zur Verfügung gestellt werden. Die meisten C-Implementierungen enthalten eine relativ standardisierte Sammlung solcher Funktionen. Ähnlich gibt es in C nur die einfachen Kontrollstrukturen mit einem Eingang und einem Ausgang: Tests. Schleifen. Zusammenfassungen und Unterprogramme, nicht aber spezielle Einrichtungen fUr Multiprogrammierung, parallele Operationen, Prozeß-Syn- chronisation oder Coroutinen. Manche mögen das Fehlen derartiger Sprachelemente als beachtlichen Nachteil ansehen (..ich muß wirklich eine Funktion aufrufen, um zwei Zeichenketten zu verglei- chen?"), aber die Beschränkungen im Sprachumfang bieten außerordentliche Vorteile. Da C eine ziemlich kleine Sprache ist, kann sie auf kleinem Raum beschrieben und schnell erlernt werden. Ein Programmierer darf durchaus erwarten, daß er die ganze Sprache kennt, versteht und wirklich regelmäßig benutzt. Viele Jahre lang war die Definition von C die ..C-Sprachbeschreibung" im Anhang zur Ersten Ausgabe von Programmieren in C. 1983 richtete das American National Stan- dards Institute (ANSI) eine Kommission ein, die eine moderne, umfassende Defmition von C vorlegen sollte. Die resultierende Defmition. der ANSJ-Standard oder ,.ANSJ-C', wurde Ende 1988 abgeschlossen. Die meisten Sprachkonzepte des Standards werden von modemen Übersetzern bereits unterstützt. Der Standard beruht auf der ursprünglichen ..Sprachbeschreibung". Die Sprache hat sich ziemlich wenig geändert; ein Ziel des Standards war, daß die meisten existenten Programme gültig bleiben oder daß wenigstens die Übersetzer entsprechende Warnun- gen erzeugen können. Für die meisten Programmierer ist die wichtigste Änderung eine neue Syntax zur Deklaration und Defmition von Funktionen. Eine Funktionsdeklaration kann jetzt die Parameter der Funktion beschreiben; die Syntax der Defmition wurde analog geändert. Diese zusätzliche Information macht es Übersetzern viel leichter, Fehler aufgrund von Widersprüchen zwischen Argumenten und Parametern zu entdecken; nach unserer Er- fahrung ist das eine sehr nützliche zusätzliche Eigenschaft der Sprache. Es gibt andere kleine Sprachänderungen. Strukturzuweisungen und Aufzähltypen (enum) waren schon weit verbreitet und sind jetzt offiziell Teil der Sprache. Gleitpunkt- rechnung kann nun mit einfacher Genauigkeit erfolgen. Die arithmetischen Eigenschaf- ten, insbesondere für vorzeichenlose Typen, wurden klargestellt. Der Preprozessor ist aufwendiger. Die meisten dieser Änderungen werden die meisten Programmierer kaum betreffen. Ein zweiter wesentlicher Beitrag des Standards ist die Defmition einer Bibliothek, die zu C gehört. Sie legt Funktionen zum Zugriff auf das Betriebssystem fest (zum Bei- spiel, um Dateien zu lesen und zu schreiben), für formatierte Ein- und Ausgabe, Spei- cherverwaltung, Operationen mit Zeichenketten etc. Eine Sammlung von Standard-Defi- nitionsdateien erlaubt einheitlichen Zugriff zu Vereinbarungen von Funktionen und Da- tentypen. Programme, die mit Hilfe dieser Bibliothek auf das umgebende System zugrei- fen. können sicher sein, daß der Zugriff überall kompatibel ist. Der größte Teil der Bi- bliothek ist aufgebaut wie die stdio-Bibliothek im UNIX-System. Diese Standard-Biblio- thek für Ein- und Ausgabe wurde in der Ersten Ausgabe beschrieben, und sie war auch auf anderen Systemen weit verbreitet. Auch hier werden die meisten Programmierer we- nig Änderungen feststellen. Die Datentypen und Kontrollstrukturen in C werden direkt von den meisten Com- putern unterstützt. C-Programme benötigen daher nur ein kleines Laufzeitsystem. Die Funktionen der Standard-Bibliothek werden nur explizit aufgerufen und können deshalb vermieden werden. wenn man sie nicht braucht. Die meisten können in C geschrieben werden. und abgesehen von den Betriebssystem-Details, die sie verbergen, sind sie selbst portabel. Obgleich C den Fähigkeiten vieler Rechner angepaßt ist, ist die Sprache doch Un- abhängig von einer bestimmten Maschinenarchitektur. Mit einer gewissen S?rgfalt ist es leicht, portable Programme zu schreiben, das heißt, Programme, die ohne Anderungen auf einer Reihe von Computern ausgeführt werden können. Der Standard stellt Port abi- litätsprobleme klar heraus und schreibt einen Satz Konstanten vor, die die Maschine cha- rakterisieren, auf der das Programm ausgeführt wird. C ist nicht streng typgebunden, aber während seiner Evolution wurde die Typprü- fung verschärft. Die ursprüngliche DefInition von C verpönte zwar, aber erlaubte doch den Austausch von Zeigern und Integer-Werten; das wurde schon lange verboten, und der Standard verlangt jetzt die korrekten Vereinbarungen und expliziten Umwandlungs- operationen. die schon bisher von guten Übersetzern durchgesetzt wurden. Die neuen Funktionsdeklarationen sind ein weiterer Schritt in diese Richtung. Übersetzer warnen vor den meisten Typfehlern und es gibt keine automatische Umwandlung von unverträgli- chen Datentypen. Trotzdem bleibt C bei der grundsätzlichen Auffassung, daß Program- mierer wissen, was sie tun; sie müssen nur ihre Absichten explizit kundtun. Schließlich hat C, wie jede andere Sprache, ihre Mängel. Einige der Operatoren haben den falschen Vorrang; Teile der Syntax könnten besser sein. Nichtsdestotrotz hat sich C als eine außerordentlich effektive und ausdrucksstarke Sprache für einen breiten Bereich von Programmieranwendungen erwiesen. Der Rest des Buches ist folgendermaßen gegliedert: Kapitel 1 ist eine Übersicht der zentralen Teile von C in Beispielen. Der Leser soll möglichst schnell anfangen kön- nen, da wir fest überzeugt sind, daß man eine neue Programmiersprache nur erlernt, wenn man gleich darin Programme schreibt. Wir setzen grundsätzliche Programmier- kenntnisse voraus; es gibt hier keine Erklärung von Computern, von Übersetzungsvor- gängen oder für die Bedeutung von Ausdrücken wie n=n+l. Wo immer dies möglich war, haben wir versucht, nützliche Programmiertechniken aufzuzeigen; dieses Buch möchte jedoch kein Nachschlagewerk für Datenstrukturen und Algorithmen sein. Wenn wir wählen mußten, haben wir uns auf die Programmiersprache konzentriert. Kapitel 2 bis 6 diskutieren verschiedene Aspekte von C ausführlicher und dabei mehr formell als dies in Kapitel 1 geschieht. Dabei liegt die Betonung noch immer auf Beispielen von abgeschlossenen, nützlichen Programmen und nicht auf isolierten Frag- 
4 Einführung 5 menten. Kapitel 2 befaßt sich mit den elementaren Datentypen, Operatoren und Aus- drücken. Kapitel 3 behandelt Kontrollstrukturen: if-else, switch, while, for etc. Kapitel 4 behandelt Funktionen und Programm struktur - externe Variablen, Gültigkeitsberei- che, mehrere Quelldateien usw. Kapitel 5 beschreibt Zeiger- und Adreß-Arithmetik. Kapitel 6 behandelt Strukturen und Unionen (struct und union). Kapitel 7 beschreibt die Standard-Bibliothek, die eine allgemeine Schnittstelle zum Betriebssystem darstellt. Diese Bibliothek wird im ANSI-Standard definiert, und sie soll auf allen Maschinen zur Verfügung stehen, wo es C gibt, damit Programme, die diese Bi- bliothek für Eingabe, Ausgabe und andere Systemfunktionen benutzen, unverändert von einem System auf ein anderes transferiert werden können. Kapitel 8 beschreibt eine Schnittstelle zwischen C-Programmen und dem UNIX- Betriebssystem und konzentriert sich dabei auf Eingabe, Ausgabe, das Dateisystem und Speicherverwaltung. Obgleich Teile dieses Kapitels auf UNIX-Systeme zugeschnitten sind, sollten selbst Programmierer, die andere Systeme benutzen, in diesem Kapitel noch nütz- liche Information fmden. Insbesondere liefert dieses Kapitel einen Einblick, wie eine Version der Standard-Bibliothek implementiert ist sowie Vorschläge, um übertragbare Programme zu realisieren. Anhang A enthält eine Sprachbeschreibung. Die offtzielle Beschreibung der Syn- tax und Semantik von C ist der ANSI-Standard selbst. Dieses Dokument wendet sich je- doch vor allem an die Autoren von Übersetzern. Die Sprachbeschreibung im vorliegen- den Buch beschreibt die Sprache knapper und ohne den juristischen Stil. Anhang Bist eine Zusammenfassung der Standard-Bibliothek, wiederum eher für Benutzer als für Im- plementierer. Anhang C ist eine kurze Zusammenfassung der Änderungen gegenüber der ursprünglichen Sprache. Im Zweifelsfall bleiben jedoch der Standard und der eigene Übersetzer die endgültigen Autoritäten für die Sprache. 1 Eine Übersicht in Beispielen Beginnen wir mit einer schnellen Einführung in C. Wir möchten die wichtigsten Sprachelemente anband von realistischen Programmen zeigen, ohne uns dabei jedoch im Detail. in Regeln oder Ausnahmen zu verlieren. Im Augenblick versuchen wir nicht, vollständig oder auch nur präzise zu sein (abgesehen davon, daß die Beispiele natürlich korrekt sein sollen). Wir möchten, daß Sie so schnell wie möglich den Punkt erreichen, an dem Sie nützliche Programme schreiben können. Dazu müssen wir uns auf die ele- mentaren Dinge konzentrieren: Variablen und Konstanten, Arithmetik, Kontrollstruktu- ren, Funktionen und rudimentäre Möglichkeiten zur Eingabe und Ausgabe. Wir lassen absichtlich in diesem Kapitel Sprachelemente von C aus, die wichtig sind, um größere Programme zu schreiben. Dazu gehören Zeiger, Strukturen, eine große Zahl von Opera- toren, mit denen C so reich ausgestattet ist, verschiedene Kontrollstruktur-Anweisungen und die Standard-Bibliothek. Unser Vorgehen hat Nachteile. Am leichtesten sieht man das daran, daß alle De- tails eines bestimmten Sprachelements nicht hier aufgefunden werden können. Durch ih- re Kürze kann diese Übersicht auch in die Irre führen. Da die Beispiele nicht die vollen Möglichkeiten von C ausnützen, sind sie nicht so prägnant und elegant wie sie sein könn- ten. Wir haben versucht, diese Effekte zu minimieren, aber seien Sie gewarnt. Ein wei- terer Nachteil ist, daß spätere Kapitel notwendigerweise Teile des vorliegenden Kapitels wiederholen. Wir hoffen, daß die Wiederholung eher hilft als stört. Erfahrene Programmierer sollten in jedem Fall in der Lage sein, aus diesem Kapi- tel für ihre eigenen Programmieraufgaben zu extrapolieren. Anfänger sollten zusätzlich eigene kleine, ähnliche Programme schreiben. Beide Gruppen können dieses Kapitel als Rahmen benutzen für die späteren, mehr detaillierten Beschreibungen in den nachfol- genden Kapiteln. 1.1 Erste Schritte Eine neue Programmiersprache lernt man nur, wenn man in ihr Programme schreibt. Die erste Programmieraufgabe ist für alle Sprachen dieselbe: Ein Programm soll folgende Wörter ausgeben: hello, world Hier haben wir die große Hürde; um sie zu überwinden, müssen Sie in der Lage sein, Ih- ren Programmtext irgendwo zu erzeugen, ihn erfolgreich zu übersetzen, zu montieren, auszuführen, und Sie müssen herausfmden, wohin Ihre Ausgabe ging. Sind diese mecha- nischen Details gemeistert, dann ist alles andere vergleichsweise leicht. In C sieht ein Programm, das hello, world druckt, so aus: #include <stdio.h> meinO { printf(lIhello, world\n")i > 
6 1 Ein bersicht in Beispielen 1.1 Erste Schrit 7 Wie Sie dieses Programm genau ausführen, hängt davon ab, welches System Sie benutzen. Beim UNIX Betriebssystem, zum Beispiel, müssen Sie den Programmtext in ei- ner Datei erzeugen, deren Name mit .c endet, etwa heUo.c. Dann übersetzen Sie diese Datei mit dem Kommando ce hello.c Falls Sie nichts falsch gemacht haben, also etwa ein Zeichen ausgelassen oder etwas falsch buchstabiert haben, wird die Übersetzung ohne weitere Meldungen vor sich gehen, und es entsteht eine ausführbare Datei mit dem Namen a.out. Wenn man diese Datei mit dem Kommando #include <stdio.h> Infofmation zur Standard-Ein fAusgabe-Bibliothek einfügen. Funktion namens main definieren, die keine Argumentwerte empfängt. Anweisungen von main stehen in geschweiften Klammem. Eine Methode, um Daten zwischen Funktionen auszutauschen, besteht für die auf- rufende Funktion darin, eine Werteliste, die Argumente, der aufgerufenen Funktion als Parameter zur Verfügung zu stellen.. Die Klammern, die dem Funktionsnamen in dcr Definition folgen, umgeben die Parameterliste; in unserem Beispiel ist main eine Funkti- on ohne Parameter, dies wird durch leere Klammern () ausgedrückt. Die geschweiften Klammern { } umgeben die Anweisungen, aus denen die Funkti on besteht. Die Funktion main enthält nur die eine Anweisung printf(IIhello, world\n"); Eine Funktion wird aufgerufen, indem man ihren Namen angibt, gefolgt von einer Liste von Argumentwerten in Klammern. Somit wird die Funktion printf mit dem Argument "heUo, world\n" aufgerufen. printf ist eine Bibliotheksfunktion, die Ausgabe erzeugt; in diesem Fall die Zeichenkette zwischen den Doppelanführungszeichen. Eine Zeichenfolge in Doppelanführungszeichen, wie "heUo, world\n", wird konstante Zeichenkette (string constant) genannt. Vorläufig werden wir Zeichenketten nur als Argumente für printf und andere Funktionen verwenden. Die Zeichenfolge \n in dieser Zeichenkette ist die C-Schreibweise für den Zeilentrenner, das Zeichen, das dafür sorgt, daß die Ausgabe am linken Rand und auf ei- ner neuen Zeile fortgesetzt wird. Wenn Sie die Folge \n auslassen (ein lohnendes Expe- riment), sehen Sie, daß in Ihrer Ausgabe kein Zeilenvorschub stattfindet. Sie müssen \n benutzen, um einen Zeilentrenner in das Argument von printf einzubringen; falls Sie et- wa 8.out ausführt, dann resultiert die Ausgabe hello, world Bei anderen Betriebssystemen sind die Regeln möglicherweise anders; Sie sollten da einen lokalen Experten konsultieren. mainO ( printf(IIhello, world\n"); ma i n IUft die Bibliotheksfunktion pr i nt f cmf, um diese Zeichenfolge zu dlUcken; \n stellt den Zeilentrenner dar. printf(IIhello, world ") ; } Das erste C- Programm angeben, wird der C-Übersetzer eine Fehlermeldung erzeugen. priDtf trennt Zeilen niemals implizit, folglich können mehrere Aufrufe dazu die- nen, eine Ausgabezeile stückweise zusammenzufügen. Unser erstes Programm hätte auch wie folgt formuliert werden können: #include <stdio.h> mai nO ( Einige Erklärungen zum Programm selbst: Unabhängig von seiner Größe besteht ein C-Programm aus Funktionen und Variablen. Eine Funktion besteht aus Anweisungen, die definieren, welche Aktionen ausgeführt werden sollen, und die Variablen enthalten die Werte, die während der Ausführung benutzt werden. C-Funktionen sind vergleichbar den Funktionen und Unterprogrammen von Fortran oder den Prozeduren und Funktio- nen von Pascal. In unserem Beispiel ist main so eine Funktion. Normalerweise können Sie Funktionen mit beliebigen Namen bezeichnen, aber main ist ein besonderer Name _ Ihr Programm beginnt seine Ausführung am Anfang von maiD. Dies bedeutet, daß jedes Programm eine Funktion namens maln irgendwo haben muß. Die Funktion maln wird normalerweise andere Funktionen aufrufen, um ihre Auf- gaben zu erledigen; solche Funktionen können von Ihnen geschrieben worden sein oder aus Bibliotheken stammen, die zu Ihrer Verfügung stehen. Die erste Zeile des Pro- gramms #inctude <stdio.h> bewirkt, daß der Übersetzer Information über die Standard-EinfAusgabe-Bibliothek ein- fügt. Diese Zeile erscheint am Anfang vieler C-Programme. Die Standard-Ein/Ausga- be-Bibliothek wird im Kapitel 7 und im Anhang B beschrieben. printf(IIhello, 11); printf("world") ; printf("\n"); } Dieses Programm produziert dieselbe Ausgabe wie das vorherige. Beachten Sie, daß \n nur ein einzelnes Zeichen repräsentiert. Eine Darstellung mit Gegenschrägstrich wie \n ist ein allgemeiner und erweiterbarer Mechanismus zur Re- präsentierung von Steuerzeichen oder Zeichen, die auf dem Eingabegerät nicht vorhan- den oder nur schwer zu erhalten sind. C erlaubt beispielsweise auch die Darstellungen \t für ein Tabulatorzeichen, \b für einen backs pace , also zum Überdrucken des vorherge- henden Zeichens (solange das Ausgabegerät dies erlaubt), \" zur Darstellung des Dop- . Wir bezeichnen als Argument den Wert, der bei einem Funktionsaufruf übergeben wird, und als Parameter die Variable, die dicscn Wert in der aufgerufenen Funktion repräsentiert. A.d.Ü. 
8 1 Eine ::rsicht in Beispielen pelanführungszeichens, und \ \ für den Gegenschrägstrich selbst. Abschnitt 2.3 enthält eine vollständige Aufzählung. Aufgabe 1-1. Führen Sie das hello, world Programm auf Ihrem System aus. Untersu- chen Sie, was passiert, wenn Sie Teile des Programms auslassen; versuchen Sie, einige Fehlermeldungen hervorzurufen. 0 Aufgabe .1- Z. Experimentiere Sie mit anderen Darstellungen mit Gegenschrägstrich, stellen Sie also fest, was passiert, wenn ein Argument für prlntf die Zeichenfolge \c enthält, wobei c ein Zeichen ist, das hier noch nicht erwähnt wurde. 0 1.2 Variablen und Arithmetik Unser nächstes Programm benutzt die Regel oe = (5/9)(OF -32) und gibt folgende Temperaturtabelle in Fahrenheit und Celsius aus: o -17 20 -6 40 4 60 15 80 26 100 37 120 48 140 60 160 71 180 82 200 93 220 104 240 115 260 126 280 137 300 148 Das Programm besteht wiederum aus der DefInition einer einzigen Funktion mit dem Namen maln. Es ist länger als das, das hello, world ausgibt, aber es ist nicht komplizier- tr. Es führt ein paar neue Dinge ein, unter anderem Kommentare, Vereinbarungen, Va- riablen, arithmetische Ausdrücke, Schleifen und formatierte Ausgabe. #Include <stdlo.h> /* Umwandlung von Fahrenheit In Celsius fuer fahr" 0, 20, ..., 300 */ mal nO { int fahr, celsius: int lower, upper, step: lower ,,0: /* untere Grenze der Temperaturtabelle */ upper = 300: /* obere Grenze */ step "20; /* Schrittbreite */ fahr" lower: while (fahr <= upper> { celsius" 5 * (fahr-32> / 9; pri, ntf("Xd\tXd\n" fahr, celsius>: fahr " fahr + step: > > , 1.2 Variablen un( ithmetik 9 Die zwei Zeilen 1* Umwandlung von Fahrenheit in Celsius fuer fahr" 0, 20, ..., 300 */ sind ein Kommentar, der in diesem Fall kurz erklärt, was das Programm tut. Der C- Übersetzer ignoriert alle Zeichen zwischen /* und * /. Kommentare können überall dort stehen, wo auch ein Leerzeichen oder ein Tabulatorzeichen oder ein Zeilentrenner ste- hen kann; sie können freizügig benutzt werden, um ein Programm leichter verständlich zu machen. In C müssen alle Variablen vereinbart werden, bevor sie benutzt werden. Norma- lerweise geschieht dies am Anfang einer Funktion vor allen ausführbaren Anweisungen. Eine Vereinbarung beschreibt die Eigenschaften von Variablen,. sie besteht aus einem Typ und einer Liste von Variablen, die dann diesen Typ besitzen, wie zum Beispiel Int fahr, celsius: Int lower, upper, step: Der Typ lot legt fest, daß die angegebenen Variablen ganzzahlige Werte annehmen. Im Gegensatz dazu führt noat Gleitpunktvariablen ein, deren Werte mit Mantisse und Expo- nent repräsentiert werden. Signiftkanz und Wertebereich hängen von der jeweiligen Im- plementierung ab. 16- Bit Int- Werte zwischen - 32768 und + 32767 sind genauso üblich wie int mit 32 Bits. Eine noat-Zahl benötigt typischerweise 32 Bits, das ergibt minde- stens sechs signifikante Dezimalziffern mit einem Wertebereich zwischen etwa 10- 38 und 10+ 38 . Neben Int und noat hat C verschiedene andere elementare Datentypen: char ein einzelnes Zeichen short ein kleiner, ganzzahliger Wert lang ein großer, ganzzahliger Wert double ein Gleitpunktwert mit doppelter Genauigkeit Die Größen dieser Objekte sind ebenfalls maschinenabhängig. Darüber hinaus gibt es Vektoren, Strukturen und unions aus diesen elementaren Typen, Zeiger auf solche Objekte und Funktionen, die derartige Werte als Resultat liefern. Wir werden dies alles zu seiner Zeit kennenlernen. Die Berechnungen in unserem Temperaturumwandlungs-Programm beginnen mit den Zuweisuncranweisungen lower " 0: upper " 300: step " 20: fahr" lower; die den Variablen ihre Anfangswerte zuweisen. In C wird jede Anweisung mit einem Se- mikolon abgeschlossen. Jede Zeile in der Temperaturtabelle wird in gleicher Weise berechnet, folglich be- nutzen wir eine Schleife, die einmal pro Ausgabezeile wiederholt wird; dies ist die Aufga- be der whlle-Schleife . Wir unterscheiden zwei Arten von VereinbOlUngen: Definitionen, die Objekte erzeugen, und Deklarationen, die nur die Eigenschaften von Objekten festlegen. A.d.Ü. 
10 1 Eim )ersicht in Beispielen 1.2 Variablen u \rithmetik 11 while (fahr <= upper) ( werden sollen. Zum Beispiel benennt %cl ein ganzzahliges Argument, so daß mit der An- weisung pr i ntf (IXd\tXd\n", fahr, celsius); zwei g ltn77lthIig e Werte fahr und celsius ausgegeben werden, mit einem Tabulatorzei- chen (\t) dazwischen. Für jedes Formatelement im ersten Argument von printf muß nachfolgend ein wei- teres Argument angegeben werden: Argumente und Formatelemente müssen in Anzahl und Datentyp zueinander passen, andernfalls enthält die Ausgabe falsche Ergebnisse. Übrigens ist printf nicht Teil der Programmiersprache C; in C selbst ist Eingabe oder Ausgabe nicht defIniert. Bei printe handelt es sich nur um eine nützliche Funktion aus der Bibliothek, die normalerweise für C-Programme zur Verfügung steht. Die Ei- genschaften von printf sind jedoch im ANSI-Standard defIniert, somit sollten die Eigen- schaften bei jedem Übersetzer oder jeder Bibliothek, die den ANSI-Standard implemen- tieren, immer gleich sein. Damit wir uns auf C selbst konzentrieren können, werden wir Eingabe und Ausga- be bis zum Kapitel 7 kaum behandeln. Insbesondere werden wir formatierte Eingabe bis dahin zurückstellen. Falls Sie Zahlen eingeben wollen, sollten Sie die Beschreibung der Funktion scanf im Abschnitt 7.4 lesen. scanf verhält sich ähnlich wie printf, allerdings wird eine Eingabe verarbeitet und nicht eine Ausgabe erzeugt. Das Programm zur Umrechnung der Temperaturen enthält noch einige Probleme. Eines der einfachen betrifft die Ausgabe, die nicht sehr schön aussieht, weil die ZaWen nicht rechtsbündig ausgegeben werden. Das läßt sich einfach beheben; wenn wir zu je- dem %cl von printf noch eine Breitenangabe liefern, werden die Zahlen rechtsbündig in diesem Bereich ausgegeben. Zum Beispiel können wir mit printf(lIX3d X6d\n", fahr, celsius); die erste Zahl jeder Zeile auf einer drei Stellen breiten Fläche ausgeben, und die zweite Zahl auf sechs Stellen Breite. o -17 20 -6 40 4 60 15 80 26 100 37 } Die while-ScWeife funktioniert folgendermaßen: Nach while steht eine Bedingung in Klammern. Trifft sie zu (ist also der Wert von fahr kleiner oder gleich dem Wert von upper), wird der Rumpf der Schleife ausgeführt (also die drei Anweisungen, die von den geschweiften Klammern { und } eingescWossen sind). Dann wird die Bedingung wieder- um geprüft, und falls sie zutrifft, werden die abhängigen Anweisungen wieder ausgeführt. Trifft die Bedingung nicht zu (fahr ist größer als upper), so wird die Schleife beendet und die Ausführung des Programms fortgesetzt mit der Anweisung, die der Schleife folgt. In diesem Programm gibt es keine weiteren Anweisungen, und folglich wird die Ausführung beendet. Von while können mehrere Anweisungen abhängen, wenn sie in geschweifte Klam- mern eingescWossen sind wie im Temperaturumwandlungs-Programm. Alternativ kann auch eine einzelne Anweisung ohne geschweifte Klammern von while abhängen, wie das folgende Beispiel zeigt: while (i < j) i = 2 * i; Auf alle Fälle rücken wir im Quellprogramm die von while abhängigen Anweisungen um eine Tabulatorposition ein (was wir durch vier Leerzeichen angedeutet haben), damit auf Anhieb ersichtlich ist, welche Anweisungen innerhalb der Schleife sind. Einrücken be- tont die logische Struktur des Programms. Obgleich in C Anweisungen beliebig auf den Quellzeilen angeordnet werden können, so sind doch sauberes Einrücken und die Ver- wendung von Zwischenraum unerläßlich, damit Programme für Menschen leichter lesbar sind. Wir empfehlen, nur eine Anweisung pro Zeile zu schreiben und Operatoren mit Leerzeichen zu umgeben. Die Position von geschweiften Klammern ist weniger wichtig, obwohl manche Leute da ganz fanatisch werden. Wir haben in diesem Buch eine von mehreren populären Stilrichtungen benutzt. Gewöhnen Sie sich einen Stil an, den Sie für zweckmäßig halten, und benutzen Sie ihn dann grundsätzlich. Die wesentliche Arbeit bei der Temperaturumwandlung wird innerhalb der while- Schleife erledigt. Die Temperatur in Celsius wird berechnet und an die Variable celsius zugewiesen durch die Anweisung celsius = 5 * (fahr-32) I 9; Wir multiplizieren mit 5 und dividieren dann durch 9, und wir vermeiden den Ausdruck 5/9; in C wird, wie in vielen anderen Sprachen, bei ganzzahliger Division abgeschnitten, Bruchteile werden also ignoriert. 5 und 9 sind int-Werte und 5/9 ist deshalb null, und dadurch würden dann auch alle Temperaturen zu null. Dieses Beispiel zeigt auch ein bißchen mehr, wie printf arbeitet. printf ist eine all- gemeine Funktion zur Umwandlung von Darstellungsformaten; wir werden diese Funkti- on ausführlich in Kapitel 7 beschreiben. Das erste Argument für printf ist eine Zeichen- kette, die gedruckt werden soll. Fomlatelemente, die jeweils mit % beginnen, geben an, wo die nachfolgenden Argumente einzufügen sind und in welcher Form sie dargestellt Ein schwierigeres Problem haben wir uns durch die Verwendung ganzzaWiger Arithmetik eingehandelt: die Celsius-Temperaturwerte sind nicht sehr genau. Zum Bei- spiel ist O"F in Wirklichkeit etwa -17.8°C, und nicht -17. Um genauere Resultate zu er- halten, sollten wir Gleitpunktarithmetik statt ganzzahliger Arithmetik verwenden. Dies erfordert einige Änderungen in unserem Programm. Eine zweite Version sieht folgen- dermaßen aus: 
12 1 Eine rsicht in Beispielen 1.3 Die for-Anw ng 13 #include <stdio.h> 1* Umwandloog von Fahrenheit in Celsius fuer fahr = 0, 20, .... 300; Version mit Gleitpunkt *1 mainO { float fahr, celsius; int lower, upper, step; lower . 0; 1* ootere Grenze der Temperaturtabelle *1 upper = 300; 1* obere Grenze *1 step . 20; 1* Schrittbreite *1 fahr = lower; while (fahr <= upper) { celsius. (5.0/9.0) * (fahr-32.0); printf(IIX3.0f X6.1f\n", fahr, celsius); fahr = fahr + step; Breite und Genauigkeit brauchen nicht angegeben zu werden: o/t>6f bedeutet, daß der Zahlenwert wenigstens sechs Zeichen breit sein soll; %.2f verlangt, daß zwei Stellen nach dem Dezimalpunkt ausgegeben werden sollen, aber eine minimale Breite der Ausgabe ist nicht vorgegeben; und %e schließlich vereinbart nur, daß die Zahl als Gleitpunktwert dar- gestellt wird. Xd X6d :l:f X6f :I:.2f als dezimale ganze Zahl ausgeben als dezimale ganze Zahl ausgeben, mindestens 6 Zeichen breit als GleitpunktzaW ausgeben als Gleitpunktzahl ausgeben, mindestens 6 Zeichen breit als GleitpunktzaW ausgeben, 2 Zeichen hinter dem Dezimalpunkt als GleitpunktzaW ausgeben, X6.2f mindestens 6 Zeichen breit und 2 hinter dem Dezimalpunkt printe erkennt unter anderem auch Formatelemente wie %0 für oktale Darstellung, %x für hexadezimale Darstellung, %c für ein einzelnes Zeichen und %s für eine Zeichenket- te. %% schließlich stellt % selbst dar. Aufgabe 1-3. Ändern Sie das Programm zur Temperaturumwandlung so ab, daß über der Tabelle eine TiteIzeile gedruckt wird. 0 Auegabe 1-4. Schreiben Sie ein Programm, das eine Tabelle von Fahrenheit-Werten in Abhängigkeit von Celsius-Werten erzeugt. 0 } } Dies ist ziemlich so wie vorher, nur sind jetzt fahr und celsius als Doat definiert, und die Regel für die Umwandlung ist natürlicher formuliert. Wir konnten in der letzten Version 5/9 nicht benutzen, weil die ganzzahlige Division diesen Ausdruck zu Null abge- schnitten hätte. Ein Dezimalpunkt in einer Konstanten gibt an, daß es sich um eine Gleitpunktkonstante handelt, somit wird 5.0/9.0 nicht abgebrochen, weil es sich als Bruch zweier Gleitpunktwerte ergibt. Hat ein arithmetischer Operator ganzzahlige Operanden, wird eine ganzzahlige Operation durchgeführt. Hat ein arithmetischer Operator jedoch einen ganzzahligen und einen Gleitpunktoperanden, wird der ganzzahlige Wert in einen Gleitpunktwert umge- wandelt, bevor die Gleitpunktoperation durchgeführt wird. Wenn wir fahr-32 geschrie- ben hätten, wäre 32 automatisch auf Gleitpunkt gewandelt worden. Nichtsdestotrotz - schreibt man Gleitpunktkonstanten mit explizitem Dezimalpunkt, auch wenn sie einen ganzzahligen Wert haben, betont das für den Leser den Gleitpunktaspekt. Im nächsten Kapitel wird im Detail erklärt, wann ganzzahlige Werte in Gleitpunkt- werte umgewandelt werden. Im Moment halten wir fest, daß in der Zuweisung fahr = lower; 1.3 Die for-Anweisung Es gibt viele verschiedene Möglichkeiten, ein Programm für eine bestimmte Aufga- be zu formulieren. Betrachten wir eine Variante zur Temperaturumwandlung: #include <stdio.h> 1* Umwandlung von Fahrenheit in Celsius *1 mainO { int fahr; for (fahr = 0; fahr <= 300; fahr = fahr + 20) printf(lIX3d X6.1f\n". fahr, (5.0/9.0)*(fahr-32»; und bei der Bedingung while (fahr <= upper) so verfahren wird, wie wir erwarten - der int-Wert wird in Doat umgewandelt, bevor die Rechenoperation stattfmdet. Das Formatelement %3.0e in printe verlangt, daß eine GleitpunktzaW (im vorlie- genden Fall eahr) in einer Fläche dargestellt wird, die mindestens drei Zeichen breit ist und bei der kein Dezimalpunkt und keine Ziffern nach dem Dezimalpunkt erscheinen. o/t>6,1f beschreibt eine weitere Zahl (celsius), die wenigstens sechs Stellen einnehmen soll mit einer Ziffer nach dem Dezimalpunkt. Die Ausgabe sieht folgendermaßen aus: o -17.8 20 -6.7 40 4.4 } Dieses Programm liefert das gleiche Resultat, aber es sieht doch ganz anders aus. Vor allem haben wir auf alle Variablen verzichtet; es verbleibt nur noch fahr. Wir haben es als int vereinbart. Anfangs- und Endwert der Tabelle sowie die Schrittbreite erscheinen nur noch als Konstanten in der for-Anweisung, selbst auch eine neue Konstruktion, und der Ausdruck zur Berechnung der Temperatur in Celsius ist jetzt das dritte Argument zu printe, und nicht mehr eine separate Zuweisung. Diese letztere Modifikation ist ein Beispiel einer allgemeinen Regel in C - in je- dem Zusammenhang, in dem der Wert einer Variablen von bestimmtem Typ benutzt werden darf, kann man auch einen komplizierteren Ausdruck mit diesem Typ verwenden. Da das dritte Argument von printe ein Gleitpunktwert sein muß, der zum Formatelement o/t>6.1C paßt, kann als drittes Argument ein beliebiger Gleitpunktausdruck verwendet wer- den. 
14 1 Ein, '>ersicht in Beispielen 1.5 Zeichenein£ und Ausgabe 15 Die for-Anweisung ist eine Schleife, und zwar eine Verallgemeinerung der wblle- Schleife. Wenn man die for-Schleife mit der früheren wbUe-Schleife vergleicht, sollte die Funktionsweise klar werden. Innerhalb der Klammem sind drei Teile, die durch Semiko- lon getrennt werden. Der erste Teil, die lnitialisierung fahr = 0 wird einmal ausgeführt, unmittelbar bevor die Schleife begonnen wird. Der zweite Teil ist die Bedingung, die die Ausführung der Schleife kontrolliert: fahr <= 300 Diese Bedingung wird ausgewertet; trifft sie zu, dann werden die abhängigen Anweisun- gen der Schleife (hier ein einzelner Aufruf von printf) ausgeführt. Schließlich wird die Inkrementierung fahr = fahr + 20 ausgeführt, und die Bedingung der Schleife wird erneut ausgewertet. Die Ausführung der Schleife ist zu Ende, falls die Bedingung nicht mehr zutrifft. Analog wie von whlle kann von for eine einzelne Anweisung abhängen oder eine Reihe von Anweisungen, die in geschweifte Klammern eingeschlossen sind. lnitialisierung, Bedingung und Inkremen- tierung können beliebige einzelne Ausdrücke sein. Ob man whlle oder for verwendet, hängt davon ab, was klarer erscheint. for ist normalerweise sinnvoll für Schleifen, bei denen lnitialisierung und Inkrementierung ein- fache Anweisungen sind, die logisch zusammenhängen, denn dann ist for kompakter als wbiIe, und die Anweisungen zur Kontrolle der Schleife werden an einer Stelle zusam- mengefaßt. Aufgabe 1-5. Ändern Sie das TempcraturumwandIungs-Programm so ab, daß die Ta- belle in umgekehrter Reihenfolge gedruckt wird, das heißt, von 300 Grad bis 0 Grad. 0 1.4 Symbolische Konstanten Eine letzte Überlegung, bevor wir die Tempcraturumwandlung endgültig verlassen. Es ist ein schlechter Stil, wenn man "magische Zahlen" wie 300 und 20 tief in einem Pro- gramm versteckt; wenn jemand das Programm später lesen muß, enthalten solche Zahlen wenig Information, und es ist schwierig, sie systematisch zu ändern. Eine Möglichkeit magische Zahlen zu handhaben, besteht darin, ihnen sinnvolle Namen zu geben. Mit ei- ner #define-Zeile wird ein symbolischer Name oder eine symbolische Konstante als spezi- eller Text vereinbart: #def i ne Name Ersatzlext Daran anschließend wird an allen Stellen, wo ein solcher Name nicht innerhalb von An- führungszeichen oder als Teil eines anderen Namens verwendet wird, der Name durch den entsprechenden Ersatztext ersetzt. Der Name sieht so aus wie ein Variablenname: eine Folge von Buchstaben und Ziffern, die mit einem Buchstaben beginnt. Der Ersatztext ist eine beliebige Zeichenfolge; es brauchen keine Ziffern zu sein. #include <stdio.h> #define LOWER 0 /* untere Grenze der Temperaturtabelle */ #define UPPER 300 1* obere Grenze *1 #define STEP 20 1* Schrittbreite *1 1* Umwandlung von Fahrenheit in Celsius *1 mai nO { int fahr; for (fahr = LER; fahr <= UPPER; fahr = fahr + STEP) printf("X3d %6.1f\n ll , fahr, (S.0/9.0)*(fahr-32»; > Die Größen WWER, UPPER und STEP sind symbolische Konstanten, keine Variablen, und erscheinen deshalb nicht in Vereinbarungen. Namen symbolischer Konstanten wer- den üblicherweise in Großbuchstaben geschrieben, damit sie leicht von Variablennamen, in Kleinbuchstaben, unterschieden werden können. Man beachte, daß am Ende der #define-Zeile kein Semikolon steht. 1.5 Zeicheneingabe und Ausgabe Wir werden jetzt eine Reihe von Programmen betrachten, die alle Operationen mit Zeichen durchführen. Sie werden feststellen, daß viele Programme einfach aufwendigere Versionen der Prototypen sind, die wir hier diskutieren. Das Modell, das von der Standard-Bibliothek für Ein- und Ausgabe unterstützt wird, ist sehr einfach. Ein- oder Ausgabe von Text, unabhängig von wo er kommt oder wohin er geht, wird als Strom von Zeichen behandelt. Ein Zeichenstrom besteht aus ei- ner Folge von Zeichen, die in Zeilen unterteilt sind; jede Zeile besteht aus beliebig vielen Zeichen, eventuell auch keinen, denen ein Zeilentrenner folgt. Die Bibliothek muß dafür sorgen, daß jeder Ein- und Ausgabestrom sich an dieses Modell hält; der C-Programmie- rer, der die Bibliothek benutzt, braucht sich nicht darum zu kümmern, wie Zeilen außer- halb des Programms repräsentiert werden. Die Standard-Bibliothek enthält mehrere Funktionen, die einzelne Zeichen lesen oder schreiben. getcbar und putchar sind die einfachsten davon. getchar liefert bei je- dem Aufruf das nlichste Eingabezeichen vom Zeichenstrom und liefert es als Funktions- wert. Das heißt, nach der Zuweisung c = getchar() enthält die Variable c das nächste Eingabezeichen. Normalerweise werden diese Zeichen von der Tastatur angefordert; Eingabe aus Dateien wird in Kapitel 7 behandelt. Die Funktion putchar gibt jedesmal, wenn sie aufgerufen wird, ein Zeichen aus: putchar(c) gibt den Inhalt der int- Variablen c als Zeichen aus, normalerweise auf dem Bildschirm. Man kann Aufrufe von putchar mit Aufrufen von printf abwechseln; die Ausgabe er- scheint immer in der Reihenfolge, in der die Funktionen aufgerufen wurden. 1.5.1 Dateien kopieren Überraschend viele nützliche Programme benötigen keine Ein- und Ausgaberouti- nen außer getcbar und putebar. Das einfachste Beispiel ist vielleicht das Programm, das seine Eingabe zeichenweise in seine Ausgabe kopiert: 
16 1 Eine .::rsicht in Beispielen ein Zeichen eingeben wh i l e ( Zeichen bedeutet nicht Dateiende eingegebenes Zeichen ausgeben neues Zeichen eingeben Wenn man diesen Algorithmus in C formuliert, erhält man #include <stdio.h> /* Kopierprogramm, Version 1 */ meinO { int c; e = geteharO; while (e 1= EOF) ( putehar(e); e = getehar(); ) ) Der Vergleichsoperator ! = bedeutet ..nicht gleich". Was bei der Tastatur oder dem Bildschirm als Zeichen erscheint ist natürlich wie alles andere, intern als Bit-Muster abgespeichert. Der Typ char wird' dafür verwedet, um Zeichen abzulegen, aber auch jeder andere ganzzahlige Typ kann dafür verwendet werden. Wir haben Int aus einem kleinen, aber dafür umso wichtigeren Grund verwen- det. Das Problem ist, das Ende der Eingabe zu erkennen. Zur Lösung des Problems liefert getchar am Ende der Eingabe einen bestimmten Funktionswert, der nicht mit ei- nem realen Zeichen verwechselt werden kann. Dieser Wert ist EOF. Der symbolische Name EOF steht für ..end of file" oder ..Eingabeende". Wir müssen c unbedingt mit ei- n:m Typ vereinbaren, der groß genug ist, damit jeder Funktionswert von getchar zuge- Wiesen werden kann. Wir können dazu char nicht benutzen, weil c zusätzlich zu allen möglichen char-Werten auch noch EOF darstellen muß. Deshalb benutzen wir den Typ int. EOF ist eine ganze Zahl, die in < stdio,h > vereinbart ist. Der spezielle numerische Wert spielt keine Rolle, solange er nicht mit einem char-Wert übereinstimmt. Da wir die symbolische Konstante verwenden, können wir sicher sein, daß nichts in unserem Pro- gramm von dem speziellen Wert abhängt. Erfahrene C-Programmierer würden das Kopierprogramm noch prägnanter for- mulieren. In C kann jede Zuweisung, wie zum Beispiel e = getehar() in Ausdrücken benutzt werden; der Wert einer Zuweisung ist jeweils gerade der Wert, der an die linke Seite zugewiesen wird. Das bedeutet, daß eine Zuweisung als Teil eines größeren Ausdrucks verwendet werden kann. Wenn man also die Zuweisung eines Zei- chens an c in die Bedingung der while-Schleife aufnimmt, erhält man folgendes Kopier- programm: ; I I 1.5 Zeicheneing" md Aus g abe 17 #include <stdio.h> /* Kopierprogramm, Version 2 */ meinO { int e; while «e . getehar(» ,= EOF) putehar(e); ) Die Bedingung der while-Schleife liest ein Eingabezeichen, weist es an c zu und überprüft dann, ob gerade das Ende der Eingabe vorliegt. Ist dies nicht der Fall, wird die von while abhängige Anweisung ausgeführt, wird also das eingelesene Zeichen ausgegeben. Die while-Schleife wird dann wiederholt. Ist schließlich das Ende der Eingabe erreicht, wird die while-Schleife beendet und damit auch die maln-Funktion. In dieser Version erfolgt die Eingabe zentral - wir benutzen getchar nur noch ein- mal - und damit ist das Programm kürzer. Das Programm ist dann kompakter, und - wenn das Idiom verstanden ist - leichter lesbar. Sie werden diesen Stil oft sehen. (Man kann aber auch übertreiben und undurchdringlichen Programmtext schreiben; wir wollen das möglichst vermeiden.) Die Klammem um die Zuweisung innerhalb der Bedingung sind notwendig. Der Vommg von ! = ist höher als der Vorrang der Zuweisung =. Dies bedeutet, daß ohne Klammern der Vergleich! = vor der Zuweisung = ausgeführt würde. Das heißt, die For- mulierung e = getehar() 1= EOF ist äquivalent zu e . (getehar() ,= EOF) Dies hat den unerwünschten Effekt, daß c den Wert 0 oder 1 erhält, in Abhängigkeit da- von, ob der Aufruf von getchar das Ende der Eingabe erreicht hat. (Davon mehr in Ka- pitel 2.) Aufgabe 1- 6. Prüfen Sie nach, daß der Ausdruck getcharO ! = EOF den Wert 0 oder 1 liefert. 0 Aufgabe 1-7. Schreiben Sie ein Programm, das den Wert von EOF ausgibt. 0 1.5.2 Zeichen zihlen Das nächste Programm zählt Zeichen; es ist eine einfache Erweiterung des Kopier- programms: #include <stdio.h> /* Eingabezeichen zaehlen, Version 1 */ meinO ( lang nci nc .. 0; while (getehar() t= EOF) ++nc; printf(IXld\n", nc)i ) 
18 1 Eine . rsicht in Beispielen 1.5 Zeicheneing und Ausgabe 19 Die Anweisung ++nci tig reagieren, wenn sie ..keine" Eingabe erhalten. Die while- und Cor-Schleifen helfen, bei Grenzbedingungen vernünftig zu reagieren. zeigt einen neuen Operator ++, dieser bedeutet um 1 vergrößern. Wir könnten dies auch als oc = oc+ 1 formulieren, aber ++nc ist präziser und oft effIZienter. Ein analoger Ope- rator -- wird dazu benutzt, um um 1 zu verringern. Die Operatoren ++ und -- können sowohl vor ihren Operanden (++ nc) als auch hinter ihren Operanden (nc++) stehen; wie in Kapitel 2 noch erklärt wird, liefern diese beiden Arten verschiedene Werte in Aus- drücken, aber beide inkrementieren nc. Wir werden vorläufig diese Operatoren vor ihre Operanden stellen. Wir zählen die Zeichen mit Hilfe einer long- Variablen anstelle einer int- Variablen. Ganze Zahlen vom Typ long werden mit mindestens 32 Bits repräsentiert. Obwohl auf einigen Systemen int und long gleich repräsentiert werden, wird auf anderen int mit 16 Bits repräsentiert, mit einem Maximalwert von nur 32767; man würde nur sehr kleine Eingabedateien benötigen, um unseren Zähler vom Typ int zum Überlaufen zu bringen. Ein Formatelement %Id gehört bei printe zu einem long-Argument. Möglicherweise könnten wir noch größere Zahlenwerte darstellen, indem wir einen Zähler vom Typ double (also eine doppelt genaue Doat-Variable) benutzen. Wir wollen auch noch eine Cor-Schleife anstelle von while benutzen, um eine andere Schreibweise für die Schleife zu demonstrieren. #include <stdio.h> /* Eingabezeichen zaehlen, Version 2 */ meinO { 1.5.3 Zeilen zählen Das nächste Programm zählt die Eingabezeilen. Wie vorher erwähnt, sorgt die Standard-Bibliothek dafür, daß ein Eingabetextstrom als Folge von Zeilen erscheint, die jeweils durch einen Zeilentrenner \0 beendet sind. Folglich muß man nur die Zeilen- trenner zählen. #include <stdio.h> /* Eingabezeilen zaehlen */ meinO { int c, nl; nl = 0; while «c = getchar(» 1= EOF) if (c == I \n') ++nl; printf(UXd\n u . nl); double nc; for (ne = 0; getchar() 1= EOF; ++nc) > Von while hängt nunmehr eine ir-Anweisung ab, die ihrerseits den Zählvorgang ++nl kontrolliert. Eine iC-Anweisung überprüft eine Bedingung, die in Klammern einge- schlossen ist. Trifft die Bedingung zu, so wird die abhängige Anweisung (oder eine Reihe von Anweisungen, die in geschweifte Klammern { } eingeschlossen sind) ausgeführt. Wir haben wiederum eingerückt, um zu zeigen, wie die Anweisungen voneinander abhängen. Das doppelte Gleichheitszeichen == drückt in C einen Vergleich auf Gleichheit aus (analog zum einfachen = in Pascal oder .EQ. in Fortran). Das Symbol unterscheidet den Vergleich von der Zuweisung = in C. Vorsicht: C-Neulinge schreiben gelegentlich = und meinen ==. Wie wir in Kapitel 2 sehen werden, ist das Resultat normalerweise ein legaler Ausdruck, also werden Sie keine Warnung bekommen. Ein Zeichen, das in einfache Anführungszeichen eingeschlossen ist, repräsentiert einen ganzzahligen Wert, der dem numerischen Wert des Zeichens im Zeichensatz des Rechners entspricht. Wir bezeichnen dies als Zeichenkonstante, obwohl das nur eine an- dere Schreibweise für kleine ganze Zahlen ist. Beispielsweise ist 'A' eine Zeichenkon- stante; im AScII-Zeichensatz ist dies der Wert 65, eben die Repräsentierung des Buchsta- bens A. Natürlich bevorzugen wir 'A' und nicht 65. Im ersten Fall ist die Bedeutung offen- sichtlich, und außerdem ist der Wert von einem bestimmten Zeichensatz unabhängig. Die Darstellungen mit Gegenschrägstrich, die wir für konstante Zeichenketten ein- geführt haben, sind auch bei Zeichenkonstanten erlaubt. In Bedingungen und arithmeti- schen Ausdrücken steht daher '\n' für den Wert des Zeilentrenner-Zeichens. Der Wert ist 10 in ASCII. Sie sollten genau beachten, daß '\n' ein Einzelzeichen darstellt und in Ausdrücken für einen einfachen ganzzahligen Wert steht; "\n" andrerseits ist eine Zei- chenkette, die zufälligerweise nur ein einzelnes Zeichen enthält, eben den Zeilentrenner. Der Unterschied zwischen Zeichenketten und einzelnen Zeichen wird in Kapitel 2 weiter ausgeführt. AuCgabe 1-8. Schreiben Sie ein Programm, das Leerzeichen, Tabulatorzeichen und Zei- lentrenner zählt. 0 printf("X.Of\n", nc); > printC verwendet das Formatelement 0/01' sowohl für Doat als auch für double-Argumente; %.OC sorgt dafür, daß die Ausgabe keinen Dezimalpunkt und keine Dezimalstellen enthält, da diese Null sind. Eigentlich hängen von der Cor-Schleife keine Anweisungen ab, da alle nötigen Be- rechnungen innerhalb der Bedingung und im Inkrementierungsteil erfolgen. Die Gram- matikregeln von C verlangen allerdings, daß von Cor grundsätzlich eine Anweisung ab- hängt. Das einzelne Semikolon, eine sogenannte leere Anweisung, sorgt dafür, daß diese Regel befolgt wird. Ein solches isoliertes Semikolon schreiben wir auf einer separaten Zeile, damit es sofort sichtbar ist. Wir sollten bei dem Zählprogramm noch folgendes beachten: Enthält die Eingabe keine Zeichen, dann ist die while- oder Cor-Bedingung bereits beim ersten Aufruf von getchar nicht erfüllt, das Programm liefert also den Wert 0, den korrekten Wert. Dies ist sehr wesentlich. An while- und Cor-Schleifen ist sehr praktisch, daß die Bedingung am Anfang der Schleife geprüft wird, bevor die Ausführung der abhängigen Anweisungen beginnt. Gibt es nichts zu tun, dann wird auch nichts getan, auch wenn damit die abhän- gigen Anweisungen gar nicht ausgeführt werden. Programme sollten auch dann vernünf- 
20 1 Eine ,ersicht in Beispielen 1.6 Vektoren 21 ++nw; und daß Zuweisungen von rechts nach links ausgeführt werden. Wir könnten dies auch so formulieren: nl " (nw = (ne = 0»; Der Operator 11 bedeutet ODER, also bedeutet die if-Bedingung if (c == I I 11 c == '\n' 11 c ,,= '\t') "wenn c ein Leerzeichen ist oder c ein Zeilentrenner ist oder c ein Tabulatorzeichen ist". (Zur Erinnerung: die Darstellung mit Gegenschrägstrich \t ist eine grafische Repräsen- tierung des Tabulatorzeichens.) Analog gibt es einen Operator && für UND; && hat Vor- rang vor 11. Ausdrücke, die mit && oder 11 verknüpft sind, werden von links nach rechts bewertet; dabei wird die Bewertung abgebrochen, sobald feststeht, ob die Verknüpfung zutrifft oder nicht. Wenn also c ein Leerzeichen ist, muß nicht mehr weiter untersucht werden, ob ein Zeilentrenner oder ein Tabulatorzeichen vorliegt, folglich werden diese Vergleiche nicht ausgeführt. Dies ist hier nicht besonders wichtig, aber in komplizierte- ren Bedingungen ist es sehr wesentlich, wie wir bald sehen werden. Das Beispiel demonstriert auch eine else-Anweisung, die genau dann ausgeführt wird, wenn die Bedingung in einer if-Anweisung nicht zutrifft. Allgemein hat if-else fol- gende Form: i f ( exprf!ssion statement} else statement2 Genau eine der zwei Anweisungen innerhalb der if-else-Konstruktion wird ausgeführt. Trifft expression zu, so wird statement} ausgeführt; trifft expression nicht zu, dann wird statementz ausgeführt. Jedes statement kann eine einzelne Anweisung oder eine Gruppe von Anweisungen in geschweiften Klammern sein. Unser Programm zum Zählen von Wörtern zeigt ein else, von dem ein if abhängt, das seinerseits zwei Anweisungen kontrol- liert. Aufgabe 1-11. Wie würden Sie das Wörterzählprogramm überprüfen? Welche Art der Eingabe wird am ehesten Fehler aufdecken, falls welche vorhanden sind? 0 Aufgabe 1-12. Schreiben Sie ein Programm, das seine Eingabe ausgibt, und zwar ein Wort pro Zeile. 0 1.6 Vektoren Unser nächstes Programm zählt die einzelnen Ziffern, Zwischenraumzeichen (also Leerzeichen, Tabulatorzeichen und Zeilentrenner) und alle anderen Zeichen. Dies ist natürlich eine künstliche AufgabensteIlung, aber wir können eine Reihe von C-Sprachele- menten in einem Programm zeigen. Die Eingabezeichen fallen in zwölf Kategorien. Deshalb benutzen wir lieber einen Vektor, um die einzelnen Ziffern zu zählen, und nicht zehn verschiedene Variablen. Hier ist eine Version eines solchen Programms: Aufgabe 1-9. Schreiben Sie ein Kopierprogramm, das Folgen von Leerzeichen in der Eingabe durch ein einzelnes Leerzeichen in der Ausgabe ersetzt. 0 Aufgabe 1-10. Schreiben Sie ein Programm, das die Eingabe zur Ausgabe kopiert und dabei jedes Tabulatorzeichen durch \t ersetzt, jeden backspace durch \b und jeden Ge- genschrägstrich durch \ \. Dadurch werden Tabulatorzeichen und backs pace-Zeichen ein- deutig sichtbar. 0 1.5.4 Wörter zählen Unser viertes nützliches Programm zählt Zeilen, Wörter und Zeichen, wobei wir di: einfache Defmition verwenden, daß ein Wort jede Zeichenfolge ist, die weder Leer- zeichen noch Tabulatorzeichen oder Zeilentrenner enthält. (Dies ist eine sehr primitive Version des uNIx-Dienstprogramms wc.) #inelude <stdio.h> #define IN 1 /* in einem Wort */ #define OUT 0 /* ausserhalb eines Wortes */ /* Zeilen, Worte und Zeichen zaehlen */ ma i n() { ;nt c, nl, nw, ne, state; state = OUT; nl " nw = ne = 0; while «c = getchar(» 1= EOF) { ++oc: if (c .." '\n') ++nl; if (c....' , 11 C.. '\n' 11 c.... '\t') stete.. OUT; else if (state "" OUT) ( stete" IN; > > printf(IIXd Xd Xd\n", nl, nw. ne); > Jedesmal, wenn dieses Programm das erste Zeichen in einem Wort erkennt, wird das Wort gezählt. Die Variable state hält fest, ob das Programm im Augenblick ein Wort oder einen Zwischenraum liest; am Anfang ist das Programm "nicht in einem Wort", da- zu gehört der Wert OUT. Wir bevorzugen die symbolischen Konstanten IN und OUT und nicht die Zahlenwerte 1 und 0, da durch die symbolischen Konstanten das Programm leicht.er lesbar wird. In so einem kleinen Programm macht das kaum einen Unterschied, aber m größeren Programmen lohnt sich im Interesse leichterer Verständlichkeit der ge- ringe zusätzliche Aufwand, von vornherein so zu formulieren. Sie werden auch feststel- len, daß umfangreiche Änderungen in Programmen leichter vorzunehmen sind, in denen magische Zahlenwerte nur als symbolische Konstanten eingeführt werden. Die Anweisung nl " nw " ne " 0; weist allen drei Variablen den Wert 0 zu. Dies ist kein Sonderfall, sondern eine Konse- quenz der Tatsache, daß jede Zuweisung ein Ausdruck ist, und somit einen Wert besitzt, 
22 1 Eine ersicht in Beispielen 1.7 Funktionen 23 #include <stdio.h> /* Ziffern, Zwischenraum und andere Zeichen zaehlen */ meinO { int c, i, nwhite, nother; int ndigit[10]; nwhite = nother = 0; for (i = 0; i < 10; ++0 ndigit[i] = 0; while «c = getchar(» 1= EOF) if (c >= '0' && c <= '9') ++ndi git [c-' 0']; else if (c == , , 11 c == '\n' 11 c == '\t') ++ nwhite; else ++ nother ; printf("digits ="); for (i = 0; i < 10; ++0 printf(1I Xd", ndigit[i]); printf(II, white space = Xd, other = Xd\n", nwhite, nother>; Die Folge von Anweisungen if (c >= '0' && c <= '9') ++ndigi t [c-' 0'] ; else if (c == , , 11 c == '\n' 11 c == '\t') ++ nwhite; else ++ nother ; entscheidet, ob ein Zeichen eine Ziffer ist, ein Zwischenraum oder ein anderes Zeichen. Das Muster i f ( condition 1 ) stalmntl else i f ( condition 2 stalmnt 2 ) Wendet man das Programm auf sich selbst an, erhält man folgende Ausgabe:* digits = 9 3 0 0 0 0 0 0 0 1. white space = 126, other = 359 Die Definition int ndigit[10]; vereinbart ndiglt als Vektor mit zehn ganzzaWigen Werten. Vektor indizes beginnen in C immer bei 0, also sind die Elemente ndigit[O], ndlglt[1], ndigit[9]. Dies zeigt sich auch in den for-Schleifen, die diesen Vektor initialisieren und später ausgeben. Ein Index kann ein beliebiger ganzzahliger Ausdruck sein, also insbesondere eine iot- Variable wie I oder eine ganzzahlige Konstante. Dieses Programm hängt kritisch von einigen Eigenschaften der Repräsentierung von Ziffern ab. Beispielsweise entscheidet die Bedingung if (c >= '0' && c <= '9') ob das Zeichen in c eine Ziffer ist. Wenn ja, dann ist ihr numerischer Wert c - '0' else stalmntll kommt häufig  Programmen vor, um eine Auswahl aus mehreren Fällen zu treffen. Von oben angefangen, wird eine Bedingung nach der anderen bewertet, bis eine condition zutrifft; dann wird der zugehörige Anweisungsteil statement ausgeführt, und die Ausführung der gesamten Konstruktion ist beendet. (Jedes statement kann natürlich wie- der aus mehreren Anweisungen innerhalb von geschweiften Klammern bestehen.) Ist keine Bedingung erfüllt, so wird die Anweisung nach dem abschließenden else aus- geführt, sofern eines in der Konstruktion vorhanden ist. FeWen ein abschließendes else und eine davon abhängige Anweisung, wie in unserem Wörterzählprogramm, so fllldet keine Aktion statt. Zwischen dem einführenden If und dem abschließenden else kann ei- ne beliebige Anzahl von else if ( condition > statment Konstruktionen stehen. Stilistisch erscheint es ratsam, diese Konstruktion so anzuord- nen, wie wir vorgeführt haben; wird jedes If gegen das vorhergehende else eingerückt, wandert eine lange Folge von Entscheidungen nach rechts über die Seite hinaus. Im Kapitel 3 diskutieren wir die swilch-Anweisung. Dies ist eine andere Möglich- keit, eine Auswahl unter vielen Fällen zu treffen. swilch ist besonders dann angebracht, wenn geprüft werden muß, ob der Wert eines Int- oder char-Ausdrucks einer von einer Reihe von Konstanten entspricht. Als Kontrast werden wir im Abschnitt 3.4 das vorlie- gende Programm nochmals mit switch formulieren. Aufgabe 1-13. Schreiben Sie ein Programm, das ein Histogramm der Längen der einge- gebenen Wörter ausgibt. Es ist am einfachsten, dieses Histogramm horizontal anzuord- nen; eine vertikale Anordnung erfordert etwas höheren Aufwand. 0 Aufgabe 1-14. Schreiben Sie ein Programm, das die Häufigkeit von verschiedenen Ein- gabezeichen als Histogramm darstellt. 0 1.7 Funktionen In C ist eine Funktion äquivalent zu einem Unterprogramm oder zu einer Funktion in Fortran oder zu einer Prozedur oder Funktion in Pascal. Eine Punktion ist bequem, um eine Berechnung einzukapseln, die dann aufgerufen werden kann, ohne daß man sich Dies funktioniert nur, wenn '0', '1', '9' aufeinanderfolgende, aufsteigende Werte sind. Glücklicherwcise gilt dies in allen Zeichensätzen. Nach Definition sind char-Werte einfach kleine ganze Zahlcn, folglich kann man char- Variablen und -Konstanten exakt wie Int in arithmetischen Ausdrücken verwenden. Dies ist eine naheliegende und bequeme Konvention; beispielsweise ist der Ausdruck c- '0' ganzzahlig mit einem Wert zwischen 0 und 9; diescr Wert korrespondiert zur Ziffer '0' bis '9', die in c gespeichert ist, und kann deshalb als Index für den Vektor ndigit vcrwen- det werden. . Tabulalorzeichen sind im Buch als vier Lcerzeichen dargestellt, zählen aber als ein Zeichen. A.d.Ü. 
24 1 Ei, 'Jbersicht in Beispielen um ihre Implementierung kümmern muß. Mit vernünftig entworfenen Funktionen kann man ignorieren, wie eine Aufgabe gelöst wird; es genügt zu wissen, was getan wurde. In C ist der Gebrauch von Funktionen leicht, bequem und effizient; Sie werden oft sehen, daß eine Funktion definiert und nur einmal aufgerufen wird, einfach weil dadurch ein Stück Programmtext klarer wird. Bisher haben wir nur Funktionen wie prlntr, getchar und putchar benutzt, die uns zur Verfügung gestellt wurden; es ist jetzt an der Zeit, einige eigene Funktionen zu ent- wickeln. Da C keinen Potenz-Operator hat wie .. in Fortran, wollen wir eine Funktions- definition anband der Funktion power(m,n) zeigen, die einen ganzzahligen Wert m mit einer positiven ganzzahligen Zahl n potenziert. Das heißt, der Wert von power(2,5) ist 32. Diese Funktion ist sicher keine praktische Implementierung von .., da sie nur positi- ve Potenzen von kleinen Zahlen bearbeiten kann, aber sie ist zur Illustration gut genug. (Die Standard-Bibliothek enthält eine Funktion pow(x,y), die x Y berechnet.) Wir zeigen hier die Funktion power und ein Hauptprogramm, um sie auszuführen, damit Sie die gesamte Struktur auf einmal sehen. #;nclude <std;o.h> ;nt power ( ;nt m, ;nt n); /* power-Funktion ausprobieren */ me;nO { ;nt ;; for (; = 0; i < 10; ++;) pr;ntf("%d %d %d\n", i, power(2,O, power(-3,;»; return 0; } /* power: base hoch n; n >= 0 */ ;nt power(int base, ;nt n) { ;nt i, p; p = 1; for (; = 1; ; <= n; ++;) p = p * base; return p;' } Eine Funktionsdefinition hat folgende Form: Resultattyp Funktionsname( evtl. Parameterdefinitionen { VereinbQlUngen Anweisungen } Funktionen können in beliebiger Reihenfolge und auch in einer oder mehreren Quellda- teien definiert werden, eine einzelne Funktion kann jedoch nicht auf mehrere Dateien verteilt werden. Wenn das Quellprogramm in mehreren Dateien formuliert wird, werden Sie möglicherweise einiges tun müssen, um das Programm zu übersetzen und zu montie- ren, aber das hängt mit dem Betriebssystem zusammen und ist keine Eigenschaft der Programmiersprache. Wir nehmen deshalb vorläufig an, daß beide Funktionen in einer t 1.7 Funktioner 25 Datei stehen; damit ändert sich nichts von dem, was Sie bisher über den Umgang mit C- Programmen wissen. Die Funktion power wird von main in der Zeile pr;ntfC"%d %d %d\n", ;, power(2,i), power(-3,;»; zweimal aufgerufen. Jeder Aufruf übergibt zwei Argumente an power, und die Funktion liefert jedesmal einen ganzzahligen Wert, der umgewandelt und asgegeben weren soll. In einem Ausdruck ist power(2,i) ein ganzzahliger Wert, genau Wie 2 und i. (Nicht alle Funktionen liefern ein ganzzahliges Resultat; damit beschäftigen wir uns in Kapitel 4.) Die erste Zeile der Funktion power selbst ;nt power(;nt base, ;nt n) definiert die Datentypen und Namen der Parameter und den Typ des Resultats, das die Funktion liefert. Die Namen der Parameter zu power sind innerhalb von power nur lokal bekannt sie sind für andere Funktionen nicht sichtbar: Andere Funktionen können die gleiche; Namen ohne Konflikte benutzen. Dies gilt auch für die lokalen Variablen i und p: Die Variable I in power ist unabhängig von der Variablen i in main. Wir bezeichnen als Parameter eine Variable, die in der Aufzählung zwischen den runden Klammern in der Funktionsdefinition aufgeführt ist, und wir nennen Argument den Wert, der beim Aufruf der Funktion verwendet wird. Manchmal findet man dafür auch die Begriffe Formalparameter und Aktualparameter. power berechnet einen Wert und liefert ihn an main mit Hilfe der return- Anweisung. Nach return kann ein beliebiger Ausdruck stehen: return expression ; Eine Funktion muß keinen Resultatwert liefern; eine return-Anweisung ohne einen nachfolgenden Ausdruck beendet die Ausführung einer Funktion, liefert aber kei.nen nützlichen Wert an den Aufrufer. Gleiches gilt, wenn das Ende des Programmtexts emer Funktion, also die abschließende geschweifte rechte Klammer, erreicht wird. An der auf- rufenden Stelle darf der Wert, den eine Funktion liefert, ignoriert werden. Sie haben sicher bemerkt, daß am Ende von main eine return-Anweisung steht. Da main eine Funktion wie jede andere ist, kann sie einen Wert an den Aufrufer zurück- liefern. Dieser Wert wird an die Umgebung geliefert, in der das Programm ausgeführt wurde. Typischerweise bedeutet der Wert Null, daß das Programm normal beendet wur- de' andere Werte als Null deuten auf ungewöhnlichen oder fehlerhaften Abbruch. Zur V;reinfachung haben wir in unseren maln-Funktionen bisher keine retrn-Anweisungen verwendet. Wir werden sie im folgenden hinzufügen, um daran zu ennnern, daß Pro- gramme ihrer Umgebung ein Resultat zurückliefern sollten. Die Deklaration ;nt power(int m, ;nt n); direkt vor maln vereinbart, daß power eine Funktion ist, die zwei int-Argumente erwartet und Int als Resultat liefert. Diese Deklaration, ein sogenannter Funktionsprototyp (oder kurz Prototyp), muß mit der Definition und den Afrufen vo poer übe,re.instimmen. Es ist ein Fehler, wenn Definition oder Benutzung emer Funktion nicht mit Ihrem Prototyp übereinstimmen. 
26 1 Eil "hersieht in Beispielen Die Parameternamen müssen in Prototyp und Funktionskopf nicht übereinstim- men. Tatsächlich sind die Parameternamen bei einem Funktionsprototyp optional, und wir hätten Int power( Int, Int); schreiben können. Sinnvoll gewählte Namen sind jedoch eine gute Dokumentation, des- halb werden wir sie oft benutzen. Ein Hinweis auf die Geschichte: Die größte Änderung zwischen ANSI-C und frühe- ren Versionen betrifft Deklaration und Definition von Funktionen. In der ursprünglichen Definition von C hätte man die power-Funktion folgendermaßen geschrieben: /* power: base hoch n; n >= 0 */ /* (Version Im alten Stil) */ power(base, n) Int base, n; { Int I, p; p = 1; tor (I = 1; I <= n; ++1) p = p * base; return p; } Die Parameternamen wurden in den Klammern angegeben, und ihr Typ wurde vor der öffnenden geschweiften Klammer deklariert; für nicht deklarierte Parameter wurde der Typ Int angenommen. (Der Funktionsrumpfbleibt unverändert.) Die Deklaration von power am Anfang des Programms hätte folgendermaßen aus- gesehen: Int power<>; Eine Aufzählung der Parameter war nicht erlaubt; folglich konnte der Übersetzer nicht ohne weiteres prüfen, ob power richtig aufgerufen wurde. Tatsächlich nahm man als Voreinstellung an, daß power ein Int-Resultat liefert, folglich hätte man die ganze Dekla- ration häufig weggelassen. Die neue Syntax für Funktionsprototypen macht es dem Übersetzer viel einfacher, Fehler bei Anzahl oder Typ der Argumente zu entdecken. Der alte Stil der Deklaration und Definition funktioniert in ANSI-C noch, wenigstens für eine Übergangszeit. Wir empfehlen jedoch dringend, daß Sie den neuen Stil benutzen, wenn Sie einen Übersetzer haben, der ihn unterstützt. Aufgabe 1-15. Schreiben Sie das Programm zur Temperaturumwandlung aus Abschnitt 1.2 so um, daß eine Funktion zur Umwandlung verwendet wird. 0 1.8 Argumente - Wert übergabe Ein Aspekt von C-Funktionen mag Programmierer überraschen, die an manche an- dere Sprachen gewöhnt sind, insbesondere an Fortran. In C werden Argumente an Funktionen grundsätzlich als Werte übergeben. Dies bedeutet, daß die aufgerufene Funk- tion die Argumentwerte in temporären Variablen erhält und nicht die ursprünglichen Ar- gumente selbst. C verhält sich dadurch etwas anders als Sprachen, bei denen Verweise übergeben werden wie in Fortran, oder als var-Parameter in Pascal. Dort hat die aufge- t 1.9 Zeiehenvekt .1 27 rufene Funktion Zugriff auf das ursprüngliche Argument und nicht nur auf eine lokale Kopie. Der wesentliche Unterschied ist, daß in C die aufgerufene Funktion eine Variable in der aufrufenden Funktion nicht ändern kann; die aufgerufene Funktion kann nur den privaten, temporären Wert ändern. Wertübergabe ist jedoch ein Vorteil, kein Nachteil. Wertübergabe führt normaler- weise zu kompakteren Programmen mit weniger zusätzlichen Variablen, da Parameter in der aufgerufenen Routine als zweckmäßig initialisierte, lokale Variablen benutzt werden können. Hier ist beispielsweise eine Variante von power, die diese Eigenschaft ausnützt: /* power: base hoch n; n>=O; Version 2 */ Int power(int base, int n) { int p; tor (p = 1; n > 0; --n) p = p * base; return p; ) Der Parameter n wird als temporäre Variable benutzt. Mit n wird rückwärts gezählt (ei- ne for-Schleife, die rückwärts läuft), bis der Wert 0 erreicht ist; wir benötigen die Varia- ble I nicht mehr. Unabhängig davon, was wir innerhalb von power mit dem Parameter n anstellen, bleibt das Argument unverändert, mit dem power ursprünglich aufgerufen wur- de. Falls nötig, können wir auch dafür sorgen, daß eine Funktion eine Variable in der aufrufenden Funktion ändern kann. Der Aufrufer muß die Adresse der Variablen liefern (aus technischer Sieht einen Zeigerwert für die Variable), und die aufgerufene Funktion muß den Parameter als Zeiger deklarieren und die eigentliche Variable mit Hilfe des Zeigerwerts erreichen. Wir werden Zeiger in Kapitel 5 besprechen. Für Vektoren ist die Sache anders. Wird der Name eines Vektors als Argument übergeben, dann erhält die Funktion die Adresse des ersten Elements im Vektor - Vek- torelemente werden nicht kopiert. Wird zum übergebenen Adreßwert ein Index hinzu- gefügt, so kann die Funktion beliebige Vektorelemente erreichen und ändern. Dies wird im nächsten Abschnitt behandelt. 1.9 Zeichenvektoren Die häufigsten Vektoren in C sind Vektoren von Zeichen. Wir wollen Zeichenvek- toren und ihre Manipulation illustrieren, indem wir ein Programm schreiben, das eine Reihe von Textzeilen einliest und die längste Zeile ausgibt. Die Programmstruktur ist einfach genug: wh il e ( es gibt noch eine Zeile ) it ( sie ist länger als die bisher längste Zeile speichern Zeilenlänge speichern längste Zeile ausgeben Diese Skizze macht deutlich, daß das Programm auf natürliche Weise in Teile zerfällt. Eine Funktion liest eine neue Zeile ein, eine andere Funktion überprüft sie, eine weitere speichert sie ab, und der Rest des Programms kontrolliert dieses Vorgehen. 
28 1 Eine Üb. _ ;ht in Beispielen Da sich das Problem so leicht strukturieren läßt, sollten wir die einzelnen Funktio- nen auch so formulieren. Schreiben wir also zunächst eine separate Funktion getIine um die nächste Zeile zu lesen. Wir werden versuchen, getIine so zu gestalten, daß die Funk- tion auch für andere Anwendungen nützlich ist. getIine sollte mindestens anzeigen, wenn das Eingabeende erreicht ist; nützlicher wäre es, wenn getIine die Länge der gelesenen Zeile liefert oder Null am Dateiende. Null ist ein akzeptabler Wert für das Dateiende, da Null nie mit einer gültigen Zeilenlänge verwechselt werden kann. Jede Zeile enthält wenigstens ein Zeichen; auch eine Zeile, die nur aus einem Zeilentrenner besteht, hat die Länge 1. Finden wir eine Zeile, die länger ist als alle vorhergehenden Zeilen, so müssen wir diese neue Zeile irgendwo abspeichern. Dies führt zu einer neuen Funktion copy, um die neue Zeile sicher aufzubewahren. Schließlich benötigen wir ein Hauptprogramm, um getIine und copy zu überwa- chen. Hier ist das Resultat: #inelude <stdio.h> #define MAXLINE 1000 /* maximale Laenge einer Eingabezeile */ int getline(ehar line[], int maxline); void eopy(ehar to[], ehar from[]); /* laengste Eingabezeile ausgeben */ mai nO { int len; /* Laenge der momentanen Eingabezeile */ int max; /* bisheriges Maximum */ ehar line[MAXLINE]; /* momentane Eingabezeile */ eher longest[MAXLINE]; /* bisher laengste Zeile */ max = 0; while «len = getline(line, MAXLINE» > 0) if (len > max) ( max = len; eopy(longest, line); } if (max > 0) /* es gab ueberhaupt eine Zeile */ printf(IXs", longest); return 0; } /* getline: Zeile an s, Laenge als Resultat */ int getline(ehar s[J, int lim) { int c, i: for (i=O; i<lim-1 && (e=getehar(»I=EOF && el='\n'; ++i) s[i] = e; if (e == '\n') { s[i] = e; ++i; } s[i] = '\0'; return i; } 1.9 Zeichenvektoren 29 /* eopy: 'from' nach 'tot kopieren; 'to' muss gross genug sein */ void eopy(eher to[], ehar from[]) { int i; i = 0; while «to[i] = from[iJ) != '\0') ++i; } Die Funktionen getline und copy sind am Anfang des Programms deklariert; wir nehmen an, daß für das Programm eine einzige Datei verwendet wird. main und getline liefern sich Information mit Hilfe von zwei Argumenten und ei- nem Resultatwert. In getline werden die Parameter in der Zeile int get!ine(ehar S[], int !im) deklariert, die festlegt, daß der erste Parameter s ein Vektor ist und der zweite, Um, ein ganzzahliger Wert. Wenn die Größe eines Vektors in einer Vereinbarung angegebn wird, bezweckt man damit, Speicherplatz anzulegen. Die Länge des Vektors s muß m getline nicht vereinbart werden, da sie in main festgelegt wird. getline liefert ein Funkti- onsergebnis mit Hilfe einer return-Anweisung, wie das auch die power-Funktion tat. Diese Zeile legt auch fest, daß getline ein int-Resultat liefert; da int die VoreinsteIlung ist, hätte man den Typnamen weglassen können. Manche Funktionen liefern ein nützliches Ergebnis; andere, wie zum Beispiel copy, führen nur gewisse Aktionen aus und liefern keinen Resultatwert. Der Resultattyp von copy ist void; dadurch wird explizit angegeben, daß kein Resultatwert geliefert wird. getline stellt das Zeichen '\0' (das Nu//zeichen, dessen Wert 0 ist) an das Ende des erzeugten Vektors, um so das Ende der Zeichenkette zu markieren. Diese Konvention wird auch in C befolgt: Erscheint eine konstante Zeichenkette wie "hello\n" in einem C-Programm, dann wird dies als Vektor von Zeichen abgelegt, der die Zeichen aus der Zeichenkette enthält und am Ende ein '\0'. Das Formatelement 'f(;s bei printr erwartet als Argument eine Zeichenkette, die in dieser Form gespeichert ist. Auch copy geht davon aus, daß sein Eingabeargument mit '\' ab- geschlossen ist, und kopiert dieses Zeichen in das Ausgabe-Argument. (Aus all diesem folgt, daß '\0' in gewöhnlichem Text nicht vorkommt.) Im Vorbeigehen sollten wir erwähnen, daß selbst ein so kleines Programm wie die- ses einige diffIZile Entwurfsprobleme verursacht: Was sollte main beispielsweise t, wenn eine Eingabezeile geliefert wird, die länger ist als angenommen? getline funktIO- niert korrekt, das heißt, getline sammelt keine weiteren Zeichen, wenn der Zeichenvek- tor gefüllt ist, und dies auch dann, wenn kein Zeilentrenner-Zeichen gefunden wird. Um festzustellen, ob die Eingabezeile zu lang ist, kann main die Zeilenlänge und das letzte Zeichen überprüfen und dann beliebig verfahren. Um unser Beispiel überschaubar zu halten, haben wir dieses Problem ignoriert. 
30 1 Einl ,ersicht in Beispielen 1.10 Externe Va len und Gültigkeitsbereich 31 Ruft man getline auf, so weiß man im voraus nicht, wie lang eine Eingabezeile sein kann; folglich schützt getllne gegen Überschreiben des Argumentvektors. Andrerseits weiß der Aufrufer der eopy-Funktion bereits (oder kann es jedenfalls herausfinden), wie lang die beteiligten Zeichenkelten sind. Wir haben daher beschlossen, keine Fehlerbe- handlung in eopy vorzunehmen. Aufgabe 1-16. Ändern Sie das Hauptprogramm so ab, daß es für beliebig lange Zeilen die Länge und so viel Text als möglich korrekt ausgibt. 0 Aufgabe 1 -17. Schreiben Sie ein Programm, das alle Eingabezeilen ausgibt, die länger als 80 Zeichen sind. 0 Aufgabe 1-18. Schreiben Sie ein Programm, das Leerzeichen und Tabulatorzeichen am Ende von Eingabezeilen entfernt und das auch völlig leere Zeilen unterdrückt. 0 Aufgabe 1-19. Schreiben Sie eine Funktion reverse(s), die die Zeichenkette s umkehrt. Benutzen Sie diese Funktion dazu, ein Programm zu schreiben, das seine Eingabe zeilen- weise umkehrt. 0 wir das Programm zur Erkennung der längsten Zeile so verändern, daß line, longest und max externe Variablen sind. Dazu müssen wir die Aufrufe, Deklarationen und Aktions- teile aller drei Funktionen ändern. #include <stdio.h> #define MAXLINE 1000 /* maximale Laenge einer Eingabezeile */ int maxi /* bisheriges Maximum */ eh ar line[MAXLINE]; /* momentane Eingabezeile */ ehar longest[MAXLINE]; /* bisher laengste Zeile */ int getline(void); void eopy(void); /* laengste Eingabezeile ausgeben; spezielle Version */ mai nO { int len; extern int maxi extern ehar longest[]; 1.10 Externe Variablen und Gültigkeitsbereich Variablen in main wie line, longest etc., sind privat oder lokal in main. Da diese Variablen innerhalb von main vereinbart sind, hat keine andere Funktion auf sie direkten Zugriff. Gleiches gilt für die Variablen in anderen Funktionen; beispielsweise ist die Va- riable i in getline völlig verschieden von der Variablen i in eopy. Jede lokale Variable in einer Funktion wird nur erzeugt, wenn die Funktion aufgerufen wird und verschwindet, wenn die Funktion verlassen wird. Aus diesem Grund werden solche Variablen norma- lerweise als automatische Variablen bezeichnet, entsprechend der Terminologie in ande- ren Sprachen. Auch wir werden in Zukunft die Bezeichnung "automatisch" für diese lo- kalen Variablen benutzen. (In Kapitel 4 wird die Speicherklasse staUe diskutiert, in der lokale Variablen ihre Werte zwischen Funktionsaufrufen beibehalten.) Da die Existenz von automatischen Variablen von Funktionsaktivierungen abhängt, behalten automatische Variablen ihre Werte von einem Funktionsaufruf zum nächsten nicht bei. Am Beginn einer Funktion müssen automatische Variablen jeweils explizit mit Werten belegt werden. Geschieht dies nicht, ist ihr Wert undefmiert. Alternativ zu automatischen Variablen können Variablen defmiert werden, die extern zu allen Funktionen sind, das heißt, Variablen, die per Namen in jeder Funktion beliebig verfügbar sind. (Dieser Mechanismus gleicht COMMON in Fortran oder Varia- blen in Pascal, die im äußersten Block deklariert sind.) Da externe Variablen global ver- fügbar sind, können sie anstelle von Argumenten dazu benutzt werden, Daten zwischen Funktionen zu übergeben. Außerdem existieren externe Variablen permanent, sie sind in ihrer Lebensdauer nicht abhängig von Funktionsaufrufen. Auch nach Abschluß eines Funktionsaufrufs behalten sie den Wert bei, der ihnen innerhalb der Funktion zugewie- sen wurde. Eine externe Variable muß definiert werden, genau einmal, außerhalb von allen Funktionen; dadurch wird ihr Speicherplatz reserviert. Die Variable muß auch in jeder Funktion deklariert werden, in der sie benutzt werden soll; dabei wird der Typ der Varia- blen angegeben. Eine solche Deklaration erfolgt entweder explizit mit einer extern- Anweisung, oder implizit durch Kontext. Um dieses Konzept zu verdeutlichen, wollen max = 0; while «len = getline(» > 0) if (len > max) ( max = len; eopy( ) ; } if (max > 0) /* es gab ueberhaupt eine Zeile */ printf("%s". longest); return 0; } /* getline: spezielle Version */ int getline(void) { int e. i; extern ehar tine[]; for (i = 0; i < MAXLINE-1 && (e=getehar(» 1= EOF && e 1= '\n'; ++i) l ine[i] = e; if (e == '\n') { line[i] = e; ++i; } line[i] = '\0'; return i; } /* eopy: spezielle Version */ void eopy(void) { int i; extern ehar li ne []. longest []; i = 0; while « longest Ci] = line[i]) ! = '\0') ++i; } I.A. B. G. Biblio!hek 
32 1 Eine y' rsicht in Beispielen 1.10 Externe Var; 'n und Gültigkeitsbereich 33 Die externen Variablen in maln, getllne und copy werden in den ersten Zeilen die- ses Beispiels defIniert, hier wird der Datentyp festgelegt und Speicherplatz reserviert. Syntaktisch gleichen externe Definitionen den Definitionen von lokalen Variablen, aber da externe Definitionen außerhalb von Funktionen erfolgen, sind die Variablen extern. Eine Funktion kann eine externe Variable erst benutzen, wenn der Name dieser Varia- blen innerhalb der Funktion bekannt ist. Dies kann mit Hilfe einer extern-Deklaration in der Funktion erreicht werden; diese Deklaration ist identisch zur Definition, jedoch wird noch das reservierte Wort extern hinzugefügt. Unter bestimmten Umständen kann die extern-Deklaration unterbleiben. Er- scheint die Definition einer externen Variablen in der Ouelldatei bevor die Variable in ei- ner Funktion benutzt wird so braucht man keine extern-Deklaration in der Funktion. Die extern-Deklarationen  maln, getllne und copy sind daher redundant. Üblicherwei- se defIniert man alle externen Variablen am Anfang der Ouelldatei und benutzt dann kei- ne extern-Deklarationen. Besteht ein Programm aus mehreren Ouelldateien und ist eine Variable etwa in der ersten Datei definiert, wird aber in der zweiten und dritten Datei benutzt, dann müs- sen in der zweiten und dritten Datei extern-Deklarationen stehen, um die Verweise auf die Variable über die Ouelldateien hinweg zu verbinden. Üblicherweise werden extern- Deklarationen von Variablen und Funktionen in einer separaten Datei gesammelt, einer sogenannten Definitionsdatei (header file), die dann mit einer #Include-Anweisung am Anfang jeder Ouelldatei vom C-Preprozessor eingefügt wird. Per Konvention wird die Endung .b für DefInitionsdateien verwendet. Die Funktionen der Standard-Bibliothek werden zum Beispiel in DefIßitionsdateien wie < stdio.b > deklariert. Dieser Komplex wird ausführlich in Kapitel 4 diskutiert, die Bibliothek selbst wird in Kapitel 7 und An- hang B vorgestellt. Da die speziellen Versionen von getllne und copy keine Argumente haben, sollten ihre Prototypen am Anfang der Datei logischerweise getline() und copy() sein. Zwecks Kompatibilität mit älteren C-Programmen sieht der Standard jedoch eine leere Aufzäh- lung als Deklaration vom "alten" Stil an und schaltet jegliche Überprüfung der Argu- mentliste ab; das Wort vold muß für eine explizit leere Liste verwendet werden. Dies wird in Kapitel 4 weiter diskutiert. Beachten Sie, daß wir die Wörter Definition und Deklaration sehr sorgfältig benut- zen, wenn wir in diesem Abschnitt die Vereinbarung von externen Variablen diskutieren. Definition bezeichnet die Vereinbarung, in der für eine Variable Speicherplatz reserviert wird; Deklaration bezeichnet die Vereinbarungen, die zwar die Variable beschreiben, aber eben keinen Speicherplatz reservieren. Übrigens besteht eine Tendenz, alle nur denkbaren Variablen extern zu vereinba- ren, denn das vereinfacht anscheinend die Zusammenarbeit zwischen Funktionen - Pa- rameterlisten sind kurz, und Variablen sind immer verfügbar, wenn wir sie benötigen. Aber externe Variablen sind immer verfügbar, sogar dann, wenn wir sie nicht benutzen wollen. Sich zu sehr auf externe Variablen zu verlassen, ist höchst gefährlich, denn es führt zu Programmen, deren Datenverbindungen absolut nicht offensichtlich sind - Va- riablen können an unerwarteten Stellen und sogar unabsichtlich verändert werden, und das Programm ist sehr schwer zu ändern. Die zweite Fassung des Programms zur Erken- nung der längsten Zeile ist stilistisch schlechter als die erste, zum Teil aus den gerade er- wähnten Gründen, und zum Teil, da die allgemeine Verwendbarkeit zweier nützlicher Funktionen dadurch eingeschränkt wird, daß in ihnen explizit die Namen der Variablen erscheinen, die beeinflußt werden. Bis hierher haben wir behandelt, was man den konventionellen Kern von C nennen könnte. Mit dieser Handvoll Sprachelemente kann man nützliche Programme von be- achtlicher Größe realisieren, und es wäre wohl eine gute Idee, wenn Sie jetzt eine Pause einlegen würden, um dies auch zu tun. Die folgenden Aufgaben schlagen Ihnen Pro- gramme vor, die etwas aufwendiger sind als die in diesem Kapitel besprochenen. Aufgabe 1- 20. Schreiben Sie ein Programm detab, das Tabulatorzeichen in der Eingabe durch die korrekte Anzahl von Leerzeichen ersetzt, um zur nächsten Tabulatorposition vorzurücken. Nehmen Sie dazu eine feste Anzahl von Tabulatorpositionen an, beispiels- weise alle n Spalten. Sollte n eine Variable oder eine symbolische Konstante sein? 0 Aufgabe 1- 21. Schreiben Sie ein Programm entab, das Folgen von Leerzeichen durch die minimale Anzahl von Tabulatorzeichen und Leerzeichen ersetzt, um die gleichen Zwischenräume zu erzielen. Benutzen Sie die gleichen Tabulatorpositionen wie für detab. Wenn entweder ein Tabulatorzeichen oder ein einzelnes Leerzeichen genügen würde, um eine Tabulatorposition zu erreichen, was sollte bevorzugt werden? 0 Aufgabe 1- 22. Schreiben Sie ein Programm, das lange Eingabezeilen in zwei oder mehr kürzere Zeilen teilt, und zwar nach dem letzten Zeichen in einem Wort, das noch ganz vor der Spalte n in der Eingabe steht. Sorgen Sie dafür, daß Ihr Programm sich auch für sehr lange Eingabezeilen vernünftig verhält, und daß auch Zeilen verarbeitet werden können, die keine Leerzeichen oder Tabulatorzeichen vor der angegebenen Spalte ent- halten. 0 Aufgabe 1- 23. Schreiben Sie ein Programm, das alle Kommentare aus C-Programmen entfernt. Dabei sollten Sie nicht vergessen, Zeichenketten (in Doppelanführungszeichen) und Zeichenkonstanten (in einfachen Anführungszeichen) korrekt zu behandeln. 0 Aufgabe 1- 24. Schreiben Sie ein Programm, das ein C- Programm auf elementare Syn- taxfehler überprüft, wie zum Beispiel falsch verschachtelte runde Klammern, geschweifte Klammern und eckige Klammern. Vergessen Sie dabei nicht, Anführungszeichen, Dop- pelanführungszeichen und Kommentare korrekt zu behandeln. (Es ist nicht leicht, ein völlig allgemeingültiges Programm für diesen Zweck zu formulieren.) 0 
35 2 Datentypen, Operatoren und Ausdrücke Variablen und Konstanten sind die grundsätzlichen Datenobjekte, die ein Pro- gramm manipuliert. Vereinbarungen führen die Variablen ein, die benutzt werden sol- len, und legen fest, welchen Typ diese Variablen besitzen und vieUeicht aur.h, welchen Anfangswert. Operatoren kontrollieren, was mit Werten geschieht. In Ausdrücken wer- den Variablen und Konstanten mit Operatoren verknüpft, um neue Werte zu produzie- ren. Der Datentyp eines Objekts legt seine Wertemenge und die Operatoren fest, die darauf anwendbar sind. Von diesen Bausteinen handelt das vorliegende Kapitel. Im ANSI-Standard wurden viele kleine Änderungen und Erweiterungen an den ele- mentaren Typen und Ausdrücken vorgenommen. Es gibt nun aUe ganzzahligen Datenty- pen signed und unsigned, also mit oder ohne Vorzeichen, und es gibt Notationen für vor- zeichenlose Konstanten und für hexadezimale Zeichenkonstanten. Gleitpunktarithmetik kann auch mit einfacher Genauigkeit erfolgen; außerdem ist der Datentyp long double für höhere Genauigkeit verfügbar. Konstante Zeichenketten können während der Über- setzung aneinandergehängt werden. Die schon lange existierenden Aufzählungen (enumerations) wurden auch formal in die Sprache aufgenommen. Objekte können mit const vereinbart werden, um zu verhindern, daß sie verändert werden können. Die Re- geln für automatische Umwandlungen zwischen den arithmetischen Typen wurden erwei- tert, um den umfangreicheren Satz an Datentypen abzudecken. 2.1 Variablennamen Obgleich wir das nicht im ersten Kapitel gesagt haben, gibt es Einschränkungen in bezug auf die Namen von Variablen und symbolischen Konstanten. Namen bestehen aus Buchstaben und Ziffern; dabei muß das erste Zeichen ein Buchstabe sein. Der Unter- strich .. " zählt als Buchstabe; dieses Zeichen ist manchmal nützlich, um die Lesbarkeit von langen Variablennamen zu verbessern. Das erste Zeichen eines Variablennamens soUte jedoch kein Unterstrich sein, da Bibliotheksfunktionen oft solche Namen verwen- den. Groß- und Kleinbuchstaben werden unterschieden; somit sind x und X zwei ver- schiedene Namen. TraditioneUerweise verwendet man in C Kleinbuchstaben für Varia- blennamen und Großbuchstaben für symbolische Konstanten. Mindestens die ersten 31 Zeichen eines internen Namens sind signifikant. Bei ex- ternen Namen, wie den Namen von Funktionen und globalen Variablen, werden unter Umständen auch weniger als 31 Zeichen unterschieden, denn externe Namen können von velschiedenen Assemblern und Ladern benutzt werden, auf die die Sprachdefinition kei- nen Einfluß nehmen kann. Für externe Namen garantiert der Standard nur, daß minde- stens sechs Zeichen, aber dabei nicht unbedingt Groß- und Kleinbuchstaben, unterschie- den werden. Worte wie Ir, else, int, noat usw. sind reserviert und können nicht als Varia- blennamen benutzt werden. Reservierte Worte müssen kleingeschrieben werden. Vernünftigerweise wählt man Variablennamen so, daß sieden Zweck einer Varia- blen andeuten, und daß eine Verwechslung durch Tippfehler unwahrscheinlich wird. Wir benutzen gern kurze Namen für lokale Variablen, insbesondere für Schleifenindizes, und längere Namen für externe Variablen. 
36 2 Datentypen, 0 1toren und Ausdrücke 2.3 Konstanten 37 2.2 Datentypen und Speicherbedarf In C gibt es nur wenige elementare Datentypen: char ein Byte, kann ein Zeichen aus dem Zeichensatz der Maschine aufnehmen. int ein ganzzahliger Wert, üblicherweise in der für die Maschine "natürlichen" Größe. float ein einfach genauer Gleitpunktwert. double ein doppelt genauer Gleitpunktwert. Zusätzlich gibt es Varianten dieser elementaren Typen. short und long beziehen sich auf ganzzahlige Werte: short int sh; long int counter; In diesen Vereinbarungen kann das Wort int ausgelassen werden; normalerweise ge- schieht dies. Mit short und long sollen verschieden lange ganzzahlige Werte zur Verfügung ste- hen, soweit dies praktikabel ist; int wird normalerweise die "natürliche" Größe für eine bestimmte Maschine sein. short belegt oft 16 Bits, long 32 Bits und int entweder 16 oder 32 Bits. Es steht jedem Übersetzer frei, sinnvolle Größen für seine Maschine zu wählen, nur mit den Einschränkungen, daß short und int wenigstens 16 Bits haben, long minde- stens 32 Bits, und daß short nicht länger als int und int nicht länger als long sein darf. signed oder unsigned können auf char oder jeden Integer-Typ angewandt werden. unsigned-Werte sind immer positiv oder Null, und ihre Werte gehorchen den arithmeti- schen Regeln modulo 1!', dabei ist n die Anzahl der Bits für einen Typ. Wenn zum Bei- spiel char-Werte mit 8 Bits repräsentiert werden, haben Variablen vom Typ unsigned char Werte zwischen 0 und 255, signed char dagegen Werte zwischen -128 und 127 (auf einer Maschine mit 2-Komplement-Darstellung). Ob für einfache char-Variablen signed oder unsigned angenommen wird, ist maschinenabhängig; druckbare Zeichen sind je- doch immer positiv. iong double bezeichnet Gleitpunktrechnung mit besonders hoher Genauigkeit (extended precision). Wie bei ganzen Zahlen, ist die Größe von Gleitpunktobjekten im- plementierungsabhängig. Ooat, double und long double könnten für eine, zwei oder drei verschiedene Größen stehen. Die Standard-DefInitionsdateien < limits.h > und < Ooat.h > enthalten symbolische Konstanten für alle Größen. Dort sind auch andere Eigenschaften der Maschine und des Übersetzers ersichtlich. Im Anhang B werden diese erläutert. Aurgabe 2 -1. Schreiben Sie ein Programm zur Bestimmung des Wertebereichs von Va- riablen vom Typ char, short, int und long, sowohl für slgned als auch unsigned, indem Sie die entsprechenden Werte aus den Standard-Defmitionsdateien ausgeben und durch direkte Berechnung. Schwieriger, falls Sie sie berechnen: bestimmen Sie den Wertebe- reich der verschiedenen Gleitpunkttypen. 0 2.3 Konstanten Eine ganzzahlige Konstante wie 1234 hat den Typ int. Eine long-Konstante wird mit dem Buchstaben I oder L am Ende geschrieben, zum Beispiel 123456789L. Ist eine normale ganzzahlige Konstante zu groß für den Typ int, so gilt auch sie als long. Vorzei- chenlose Konstanten werden mit u oder U am Ende geschrieben; die Endung ul oder UL bezeichnet unsigned long. Gleitpunktkonstanten enthalten einen Dezimalpunkt (123.4) oder einen Exponen- ten (le - 2) oder beides; ohne ein besonderes SuffIx sind sie vom Typ double. Die Endung rodel' F bezeichnet Konstanten vom Typ Ooat, das SuffIx I oder L vereinbart long double. Ganzzahlige Konstanten können statt dezimal auch oktal (in Basis 8) und hexadezi- mal (in Basis 16) formuliert werden. Beginnt eine ganzzahlige Konstante mit 0 (Null), so wird sie in Basis 8 interpretiert; beginnt die Konstante mit Ox oder OX, so gilt Basis 16. Beispielsweise kann der Dezimalwert 31 als 037 in Basis 8 und OxH oder OX1F in Basis 16 dargestellt werden. Auch nach hexadezimalen und oktalen Konstanten kann L für long und U für unsigned folgen; OXFUL ist eine Konstante vom Typ unsigned long mit dem dezimalen Wert 15. Eine Zeichenkonstante ist ganzzahlig und wird als ein Einzelzeichen innerhalb von Anführungszeichen geschrieben, wie etwa 'x'. Der Wert einer Zeichenkonstanten ist der numerische Wert des Zeichens im Zeichensatz der Maschine. Beispielsweise hat die Zei- chenkonstante '0' für die Ziffer Null im AscII-Zeichensatz den Wert 48, was nichts mit dem numerischen Wert 0 zu tun hat. Schreibt man '0' anstelle eines numerischen Werts wie 48, der vom Zeichensatz abhängig ist, so ist das Programm unabhängig vom jeweili- gen numerischen Wert und leichter zu lesen. Zeichenkonstanten können in numerischen Operationen genau wie andere Integer-Werte verwendet werden; sie werden jedoch am häufIgsten zu Vergleichen mit anderen Zeichen verwendet. Gewisse Zeichen können in Zeichenkonstanten und in konstanten Zeichenketten mit Hilfe von Ersatzdarstellungen wie etwa \n (Zeilentrenner) angegeben werden. Diese Ersatzdarstellungen sehen zwar wie zwei Zeichen aus, stellen aber nur ein Zeichen dar. Darüber hinaus kann ein beliebiges, byte-großes Bit-Muster mit '\000' dargestellt werden, dabei ist 000 eine Folge von ein bis drei oktalen Ziffern (0...7), oder mit '\xhh' dabei ist hh eine Folge von einer oder mehreren hexadezimalen Ziffern (0...9, a...r, A...F). Somit könnten wir folgendes schreiben: #define VTAB '\013' /* ASCII Vertikal-Tabulator */ #define BELL '\007' /* ASCII Klingelzeichen */ oder auch hexadezimal; #define VTAB '\xb' #define BELL '\x7' /* ASCII Vertikal-Tabulator */ /* ASCII Klingelzeichen */ . Nationale Zeichen, zum Beispiel Umlaute oder Akzente, benötigen eine 8-Bit-Darstellung und können als signed dlar negative Werte annehmen. A.d.O. 
38 2 Datentypen, Opf )ren und Ausdrücke 2.4 Vereinbar, n 39 Hier sind alle Ersatzdarstellungen: \a Klingelzeichen \b backspace \f Seitenvorschub \n Zeilentrenner \r Wagenrücklauf \t Tabulatorzeichen \v Vertikal-Tabulator Die Zeichenkonstante '\0' steht für das Zeichen mit Wert 0, das sogenannte Null- zeichen (NUL). '\0' wird oft statt 0 geschrieben, um zu betonen, daß sich ein bestimmter Ausdruck mit Zeichen befaßt, der numerische Wert ist aber trotzdem Null. Ein konstanter Ausdruck ist ein Ausdruck, an dem nur Konstanten beteiligt sind. Solche Ausdrücke werden vom Übersetzer bewertet und nicht zur Laufzeit, folglich kön- nen diese Ausdrücke überall dort benutzt werden, wo Konstanten auftreten können, wie bei \" Gegenschrägstrich Fragezeichen Anführungszeichen Doppelanführungszeichen oktale Zahl hexadezimale Zahl /* strlen: liefert die Laenge von s */ int strlen(char s[]) { \\ \1 \' int i; i = 0; while (s[O 1= '\0') ++i; return i; \000 \xhh "hello," 11 world tl } strlen und andere Funktionen zur Bearbeitung von Zeichenketten sind in der Standard- DefInitionsdatei < string.h > vereinbart. Sie sollten sehr sorgfältig zwischen einer Zeichenkonstante und einer Zeichenkette mit einem einzelnen Zeichen unterscheiden: 'x' ist nicht das Gleiche wie "x". Die Zei- chenkonstante 'x' ist ein ganzzahliger Wert und stellt den numerischen Wert des Buchsta- bens x im Zeichensatz der Maschine dar. Die konstante Zeichenkette "x" ist ein Zeichen- vektor und enthält ein Zeichen (den Buchstaben x) und ein Nullzeichen '\0'. Es gibt noch eine andere Art von Konstanten, die Aufziihlungskonstanten (enumeration constants). Eine Aufzählung ist eine Folge von konstanten ganzzahligen Werten, wie etwa enum boolean { NO, YES }; Der erste Name in einer enum-Liste hat den Wert 0, der nächste 1 und so weiter, wenn keine expliziten Werte angegeben sind. Wenn manche Werte nicht angegeben sind, set- zen nicht explizit angegebene Werte die aufsteigende Reihenfolge vom letzten expliziten Wert ab fort, wie hier im zweiten Beispiel: enum escapes { BELL = '\a', BACKSPACE = '\b', TAB = '\t ' , NEWLINE = '\n'. VTAB = '\v', RETURN = '\r' }; enum months { JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, OEC }; /* FEB hat den Wert 2, MAR den Wert 3, etc. */ Namen in verschiedenen Aufzählungen müssen sich unterscheiden. Die Werte in einer Aufzählung müssen nicht verschieden sein. Aufzählungen sind bequem, um konstante Werte mit Namen zu verknüpfen, als Al- ternative zu #define mit dem Vorteil, daß die Werte implizit generiert werden können. Obwohl Variablen vom 1YP enum vereinbart werden dürfen, müssen die Übersetzer nicht prüfen, daß das, was in solch einer Variablen abgelegt wird, ein zulässiger Wert für die Aufzählung ist. Trotzdem - Variablen mit enum-1YP bieten die Möglichkeit zur Prü- fung und sind deshalb oft besser als #define. Eine Testhilfe könnte außerdem die Fähig- keit besitzen, Werte von enum- Variablen symbolisch auszugeben. 2.4 Vereinbarungen Alle Variablen müssen vor Gebrauch vereinbart werden, allerdings können manche Vereinbarungen implizit aus dem Kontext folgen. Eine Vereinbarung gibt einen 1YP an und enthält eine Liste von einer oder mehreren Variablen dieses 1YPs, wie etwa int lower, upper, step; char c, line[1000]; #define MAXLINE 1000 char line[MAXLINE+1]; oder #define LEAP 1 /* bei Schaltjahren */ int days[31+28+LEAP+31+30+31+30+31+31+30+31+30+31]; Eine konstante Zeiclrenkette (string literal) ist eine Folge von beliebig vielen Zei- chen (auch keinen), die von Doppelanführungszeichen umgeben ist, wie etwa "Ich bin eine Zeichenkette" oder "" /* leere Zeichenkette */ Die Doppelanführungszeichen sind nicht Teil der Zeichenkette, sondern begrenzen sie nur. Die gleichen Ersatzdarstellungen gelten für Zeichenkonstanten wie für konstante Zeichenketten; \" stellt das Doppelanführungszeichen dar. Konstante Zeichenketten können bei der Übersetzung aneinandergehängl werden: ist äquivalent zu "hello, world" Damit kann man lange konstante Zeichenketten gut auf mehrere Zeilen im Programm- text verteilen. Formal betrachtet ist eine konstante Zeichenkette ein Vektor von Zeichen. Die in- terne Repräsentierung einer Zeichenkette hat ein Nullzeichen '\0' am Ende, folglich wird als Speicherplatz ein Zeichen mehr benötigt, als die Anzahl der Zeichen zwischen den Doppelanführungszeichen. Durch diese Darstellung ist die Länge einer Zeichenkette nicht begrenzt, aber Programme müssen eine Zeichenkette ganz durchlaufen, um ihre Länge zu bestimmen. Die Funktion strlen(s) aus der Standard-Bibliothek liefert die Länge einer Zeichenkette s ohne das abschließende '\O'-Zeichen. Hier ist unsere Versi- on: 
40 2 Datentypen, Open en und Ausdrücke 41 Variablen können beliebig auf mehrere Vereinbarungen verteilt werden; die vorherge- henden Listen könnte man auch so schreiben: Der Operator % kann auf float oder double-Werte nicht angewendet werden. Für nega- tive Operanden ist maschinenabhängig, in welcher Richtung bei / abgeschnitten wird und welches Vorzeichen das Resultat von % erhält. Auch die Aktionen bei Verlassen des Wertebereichs (overflow oder underflow) hängen von der jeweiligen Maschine ab. Die binären Operatoren + und - haben den gleichen Vorrang, der geringer ist als der Vorrang von ., / und %, welcher wiederum geringer ist als der Vorrang der unären Operatoren + und -, also von positiven und negativen Vorzeichen. Arithmetische Ope- rationen werden von links her zusammengefaßt. Tabelle 2-1 am Ende dieses Kapitels faßt Vorrang und Assoziativität aller Operato- ren zusammen. int lower; i nt upper; int step; eher e; ehar line[1000]; So ist der Programmtext zwar länger, aber man kann bequem einen Kommentar zu jeder Vereinbarung hinzufügen oder später Änderungen vornehmen. Eine Variable kann bei ihrer Vereinbarung auch initialisiert werden. Folgt dem Namen ein Gleichheitszeichen und ein Ausdruck, wird der Ausdruck als lnitialisierung verwendet: ehar ese = '\\'; int i = 0; int limit = MAXLINE+1; float eps = 1.0e-5; Gehört die fragliche Variable nicht zur automatischen Speicherklasse, findet die In- itialisierung nur einmal statt, sozusagen bevor das Programm ausgeführt wird; die Initiali- sierung muß dann ein konstanter Ausdruck sein. Eine explizit initialisierte automatische Variable wird bei jedem Eintritt in die Funktion oder in den Block initialisiert, zu dem sie gehört; die Initialisierung kann ein beliebiger Ausdruck sein. Nach Voreinstellung wer- den externe und statische Variablen auf Null initialisiert. Automatische Variablen ohne explizite Initialisierung haben undefinierte (d.h., sinnlose) Werte. Mit dem Attribut const kann bei der Vereinbarung einer Variablen angegeben wer- den, daß sich ihr Wert nicht ändert. Bei einem Vektor bedeutet const, daß die Elemente nicht verändert werden. eonst double e = 2.71828182845905; eonst eh ar msg[] = "warning: "; 2.6 Vergleiche und logische Verknüpfungen Die Vergleichsoperatoren sind > >= < <= const kann auch bei Vektorparametern verwendet werden, um anzugeben, daß die Funk- tion den Vektor nicht verändert: int strlen(eonst ehar[]); Versucht man, eine const-Variable zu ändern, ist das Resultat implementierungsabhän- gig. 2.5 Arithmetische Operatoren Die binären arithmetischen Operatoren sind +, -, ., / und der Operator %, der den Rest nach ganzzahliger Division liefert. Bei Integer-Division wird ein Bruchteil ab- geschnitten. Der Ausdruck x X y liefert den Rest, der entsteht, wenn x durch y dividiert wird, und ist daher Null, wenn y gerade x exakt teilt. Beispielsweise ist ein Jahr ein Schaltjahr, wenn die Jahreszahl durch 4, aber nicht durch 100 teilbar ist, nur sind Vielfache von 400 doch Schaltjahre. Deshalb if «year X 4 == 0 && year X 100 1= 0) 11 year X 400 == 0) printf("%d ist ein Schal tjahr\n". year); else printf("%d ist kein Sehaltjahr\n", year); Diese Operatoren haben alle gleichen Vorrang. Die Äquivalenzoperatoren == 1= haben geringeren Vorrang. Vergleiche haben geringeren Vorrang als arithmetische Ope- ratoren. Daher wird ein Ausdruck wie i < !im -1 als I < (!im -1) bewertet, was man auch erwarten würde. Interessanter sind die logischen Operatoren && und 11. Ausdrücke, die mit && oder I1 verknüpft sind, werden strikt von links nach rechts bewertet, und zwar nur solan- ge, bis das Resultat der logischen Verknüpfung feststeht. Die meisten C-Programme ba- sieren auf diesen Eigenschaften. Beispielsweise ist hier eine Schleife aus der Eingabe- funktion getline, die wir in Kapitell formuliert haben: for (i=O; i<lim-1 && (e=getehar(» != '\n' && e != EOF; ++i) s[i] = e; Bevor ein neues Zeichen eingelesen wird, muß man prüfen, daß im Vektor s auch Platz vorhanden ist, folglich muß der Test I < !im -1 erfolgen, bevor ein Zeichen eingelesen wird. Wenn dann kein Platz mehr vorhanden ist, können wir auch kein weiteres Zeichen einlesen. Analog sollten wir c nicht mit EOF vergleichen, bevor getchar aufgerufen wurde: Deshalb müssen Aufruf und Zuweisung erfolgen, bevor das Zeichen in c geprüft wird. Der Vorrang von && ist größer als der Vorrang von 11, und beide haben geringe- ren Vorrang als die Vergleichs- und Äquivalenzoperatoren, folglich benötigen Ausdrücke wie i<lim-1 && (e = geteharC» 1= '\n' && e != EOF keine zusätzlichen Klammern. Da jedoch der Vorrang von! = höher ist als der Vorrang der Zuweisung, sind im Ausdruck Ce = geteher(» 1= '\n' die Klammern nötig, um das beabsichtigte Resultat herbeizuführen: Zuweisung an c und erst dann Vergleich zu '\n'. 
42 2.7 Typumwar ngen 43 2 Datentypen, ( ratoren und Ausdrücke Nach DefInition ist der numerische Wert einer Vergleichsoperation 1, falls der Ver- gleich zutrifft, und 0, falls er nicht zutrifft. Der unäre Negationsoperator ! liefert 0, wenn sein Operand nicht 0 ist, und 1, wenn sein Operand 0 ist. Der Operator! wird häutig bei Konstruktionen wie if (!valid) verwendet, anstelle von if (val id == 0) Man kann schwer verallgemeinern, was besser ist. Angaben wie !valid lesen sich leicht (..wenn nicht gültig"), aber kompliziertere Formulierungen sind oft schwer verständlich. Aufgabe 2 - 2. Schreiben Sie eine Schleife, die zu der obigen for-Schleife äquivalent ist, ohne die Operatoren && oder 11 zu verwenden. 0 2.7 Typumwandlungen Hat ein Operator Operanden verschiedenen Typs, dann werden ihre Werte nach einer kleinen Anzahl von Regeln in einen gemeinsamen Datentyp umgewandelt. Im all- gemeinen fInden implizit nur solche Typumwandlungen statt, die einen ..schmäleren" Operanden in einen ..breiteren" umwandeln ohne dabei Information zu verlieren, wie et- wa die Umwandlung eines ganzzahligen Werts in einen Gleitpunktwert in einem Aus- druck wie f + i. Sinnlose Ausdrücke, wie etwa ein Doat-Wert als Vektorindex, sind verbo- ten. Ausdrücke, die zu Informationsverlust führen könnten, wie die Zuweisung eines län- geren Integer-Typs an einen kürzeren oder eines Gleitpunkttyps an einen Integer-Typ, können eine Warnung hervorrufen, aber sie sind nicht illegal. Ein char- Wert ist nur eine kleine ganze Zahl, also können char- Werte beliebig in arithmetischen Ausdrücken benutzt werden. Damit können manche Zeichentransforma- tionen sehr flexibel formuliert werden. Als Beispiel betrachten wir diese naive Imple- mentierung der Funktion atoi, die eine Ziffernkette in ihr numerisches Äquivalent um- wandelt. /* lower: c in Kleinbuchstaben umwandeln' ASCII */ int lower( int c) . { if (c >= 'A' && c <= 'l') return c + lai - 'A'; else return c; } Dies funktioniert im AscII-Zeichensatz, da sich die jeweiligen Groß- und Kleinbuchsta- ?en um einen konstanten Wert unterscheiden, und da jedes Alphabet zusammenhängend 1st - zwischen A und Z liegen ausschließlich Buchstaben. Letzteres gilt jedoch nicht für den EBCDIC-Zeichensatz, also würde diese Funktion für EBCDIC mehr als nur Buchstaben umwandeln. Die Standard- DefInitionsdatei < ctype.h >, die in Anhang B beschrieben wird defI- niert einen Satz von Funktionen für Prüfungen und Umwandlungen, die vom Zeichnsatz unabhängig sind. Zum Beispiel liefert die Funktion tolower(c) für einen Großbuchstaben c.. de Wert des azugehöri.gen Klein.uchstabens., also ist tolower ein portabler Ersatz fur die oben gezeigte Funktlon lower. Ahnlich kann c >= '0' && c <= '9' durch int i. n; n = 0; for (i = 0; s[i] >'" '0' && s[i] <- '9'; ++i) n = 10 * n + Cs[i] - '0'); return n; iSdigitCc) ersetzt werden. Wir werden von nun an die Funktionen von <ctype.h> benutzen. Bei der Umwandlung von Zeichen in numerische Werte ist eine Feinheit zu beach- ten. C defIniert nicht, ob Variablen vom Typ char numerische Werte mit oder ohne Vor- zeichen sind. Kann bei Umwandlung eines char-Werts in einen int-Wert je eine negative Zahl entstehen? Die Antwort zu dieser Frage ist je nach Maschinenarchitektur verschie- den. Bei manchen Maschinen wird ein char-Wert, dessen höchstes Bit 1 ist durch Pro- pagierung des Vorzeichens in einen negativen Integer-Wert verwandelt. Bei 'anderen Sy- stemen wird ein char-Wert dadurch in einen int-Wert umgewandelt, daß links Bits mit Wert 0 hinzukommen, also ist das Resultat immer positiv. Die DefInition von C garantiert jedoch, daß kein Zeichen aus dem druckbaren Standard-Zeichensatz der jeweiligen Maschine einen negativen Wert besitzt · so daß die- s Zeichen in Ausdrücken immer positive Größen sind. Speichert man jedoch beliebige Btt-Muster als char-Werte, so können diese auf manchen Maschinen negativ und auf an- deren positiv erscheinen. Für Portabilität sollte man signed oder unsigned angeben, wenn andere Daten als Zeichen in char- Variablen gespeichert werden sollen. Vergleiche wie i > j und logische Verknüpfungen mit && und 11 haben nach DefI- nition den Wert 1, falls sie zutreffen, und 0, falls nicht. Also wird in der Zuweisung d = c >= '0' && c <= '9' d auf 1 gesetzt, falls c eine Ziffer ist, und auf 0, falls nicht. Funktionen, wie isdigit kön- ne? jedoch jeden von Null verschiedenen Wert für ..wahr" liefern. In einer Bedingung bel If, while, for usw., bedeutet ..wahr" einfach ..nicht Null", also ist das gleichbedeutend. /* atoi: liffernkette s in int umwandeln */ int atoiCchar s[]) { } Wie wir in Kapitell schon besprochen haben, liefert der Ausdruck s[i] - '0' den numerischen Wert des Zeichens, das in s[i] gespeichert ist, da die Werte von '0', '1' etc. eine zusammenhängende, aufsteigende Folge bilden. Ein weiteres Beispiel einer Umwandlung von char in Int-Werte ist die Funktion lower, die ein einzelnes Zeichen in einen Kleinbuchstaben umwandelt - allerdings nur im ASCII-Zeichensatz. Ist das Argument kein Großbuchstabe, so liefert lower diesen Wert unverändert. . Naonale ichen, zu Beispiel Umlaute und Akzente, benötigen eine 8-Bit-Darstellung. Sie gelten bei Funkhonen WIe tol4Mer mcht als Buchstaben und können als slgned char negative Werte annehmen. A.d.Ü. 
44 2 Datentypen, 0 itoren und Ausdrücke 2.7 TypumwaJ ngen 45 Implizite arithmetische Umwandlungen funktionieren etwa so, wie man erwarten müßte. Im allgemeinen wird bei binären Operatoren wie + oder *, wenn zwei Operan- den mit verschiedenem Typ vorliegen, der Wert vom "niedrigeren" in den "höheren" Typ umgewandelt, bevor die Operation ausgeführt wird. Das Resultat hat dann den höheren Typ. Im Abschnitt 6 im Anhang A werden die Typumwandlungsregeln genau aufgeführt. Falls jedoch keine unsigned-Operanden beteiligt sind, genügen die folgenden vereinfach- ten Regeln: Wenn einer der Operanden long double ist, wird der andere in long double um- gewandelt. Ist andernfalls einer der beteiligten Operanden double, so wird der andere Ope- rand in double umgewandelt. Ist dagegen einer der beiden Operanden Ooat, so wird der andere ebenfalls in Ooat umgewandelt. Andernfalls werden char und short in int umgewandelt. Falls dann einer der Operanden long ist, wird der andere in long umgewandelt. Man beachte, daß Ooat-Werte in einem Ausdruck nicht automatisch in double- Werte verwandelt werden; dies ist anders als in der früheren Definition von C. Im allge- meinen arbeiten mathematische Funktionen, wie die in < math.h >, mit doppelter Ge- nauigkeit. Ooat wird hauptsächlich verwendet, um bei großen Vektoren Speicherplatz zu sparen, und, seltener, um Rechenzeit bei Maschinen zu sparen, bei denen doppelt genaue Arithmetik besonders teuer ist. Wenn unsigned-Operatoren beteiligt sind, sind die Typumwandlungsregeln kompli- zierter. Das Problem liegt darin, daß Vergleiche zwischen Werten mit und ohne Vorzei- chen maschinenabhängig sind, da sie von den Größen der verschiedenen Integer-Typen abhängen. Nehmen wir zum Beispiel an, daß int 16 Bits und long 32 Bits hat, dann ist -tL< 1U, weil 1U vom Typ unsigned in den Typ signed long umgewandelt wird. And- rerseits ist -tL> 1UL, weil in diesem Fall -lL in unsigned long umgewandelt wird und deshalb als große positive Zahl erscheint. Bei Zuweisungen fmden Typumwandlungen statt; der Wert auf der rechten Seite wird in den Typ der linken Seite umgewandelt, und das ist der Typ des Resultats. Wie oben beschrieben, wird ein Zeichen mit oder ohne Propagierung des Vorzei- chens in einen ganzzahligen Wert umgewandelt. Längere Integer-Werte werden in kürzere oder in char- Werte umgewandelt, indem die überzähligen Bits von links her unterdrückt wetden. Also bleibt bei int i; cher c; i = Ci c = i; der Wert von c unverändert. Dies gilt unabhängig davon, ob das Vorzeichen propagiert wird oder nicht. Wenn jedoch die Reihenfolge der Zuweisungen vertauscht wird, kann Information verlorengehen. Ist x ein Ooat- und i ein int-Objekt, dann verursachen sowohl x = I als auch I = x Typumwandlungen; bei Umwandlung von Ooat in Int wird ein Bruchteil abgeschnitten. Ob bei der Umwandlung von double in Ooat der Wert gerundet oder abgeschnitten wird, ist im plementierungsabhäDgig. Da ein Argument eines Funktionsaufrufs ein Ausdruck ist, finden Typumwandlun- gen auch statt, wenn Argumente an Funktionen übergeben werden. Gibt es keinen Pro- totyp für die Funktion, werden char und short in lot umgewandelt und Ooat in double. Deshalb haben wir Parameter auch dann als lot und double deklariert, wenn die Funkti- on mit char und Ooat aufgerufen wurde. Schließlich kann man explizite Typumwandlungen in einem beliebigen Ausdruck mit einer unären Umwandlungsoperation, einem sogenannten cast., erzwingen. Bei ( typt!-namt! ) t!Xpf't!ssion wird der Wert des Ausdrucks unter Benutzung der oben angegebenen Typumwandlungs- regeln in den angegebenen Typ umgewandelt. Exakt bedeutet eine explizite Umwand- lungsoperation dasselbe, als wenn der Ausdruck einer Variablen vom angegebenen Typ zugewiesen wird, die dann anstelle der ganzen Konstruktion verwendet wird. Beispiels- weise erwartet die Bibliotheksfunktion sqrt ein double-Argument und liefert sinnlose Re- sultate, wenn versehentlich etwas anderes übergeben wird. (sqrt ist in < math.h > dekla- riert.) Ist also 0 ein ganzzahliger Wert, dann kann mit sqrt«double) n) der Wert von 0 in double umgewandelt werden, bevor er als Argument an sqrt überge- ben wird. Man beachte, daß die Umwandlungsoperation den Wert von n im verlangten Typ liefert; n selbst wird dabei nicht verändert. Wie aus der Tabelle am Ende dieses Ka- pitels hervorgeht, hat die explizite Umwandlungsoperation den gleichen hohen Vorrang wie andere unäre Operatoren. Wenn Parameter in einem Funktionsprototyp deklariert werden, wie das normaler- weise geschehen sollte, bewirkt die Deklaration eine automatische Umwandlung aller Ar- gumente, wenn die Funktion aufgerufen wird. Gilt also zum Beispiel der folgende Proto- typ für sqrt double sqrt(double); dann wird beim Aufruf root2 .. sqrt(2); die Int-Konstante 2 in einen double-Wert 2.0 umgewandelt, ohne daß dazu eine explizite Umwandlungsoperation nötig ist. Die Standard-Bibliothek enthält eine portable Implementierung eines Pseudo-Zu- fallszahlengenerators und eine Funktion um den Ausgangswert zu initialisieren; in rand gibt es eine Umwandlungsoperation: unsigned long int next .. 1; 1* rend: liefert Pseudo-Zufellszehl im Bereich 0..32767 *1 int rend(void) ( next .. next * 1103515245 + 12345; return (unsigned int)(next/65536) X 32768; } . Zu deullch Girbend, eine sehr plastische Umschreibung. 
46 2 Datentypen, Oper 'en und Ausdrücke 2.9 Bit-Manipu )nen /* srand: setze Ausgangswert fuer rande) */ void srand(unsigned int seed) ( 47 next = seed; Das Zeichen in Position i wird untersucht. Ist es nicht c, dann wird das Zeichen aus Posi- tion i in Position j kopiert, und nur daran anschließend wird j inkrementiert, um auf eine neue verfügbare Position zu zeigen. Dies ist vollkommen äquivalent zu . if (s[i) != e) ( s[j) = sei); j++: } Aufgabe 2-3. Schreiben Sie die Funktion htol(s), die eine Zeichenkette mit hexadezi- malen Ziffern (einschließlich eines optionalen Ox oder OX) in den entsprechenden ganz- zahligen Wert umwandelt. Dabei sind die zulässigen Ziffern 0 bis 9, abis fund Abis F.D 2.8 Inkrement- und Dekrement-Operatoren C verfügt über zwei ungewöhnliche Operatoren zur Inkrementierung und Dekre- mentierung von Variablen. Der Inkrement-Operator ++ addiert 1 zu seinem Operan- den, der Dekrement-Operator -- subtrahiert 1. Wir haben häufig ++ dazu verwendet, Variablen zu inkrementieren, zum Beispiel if (e == '\n') ++nl; } Ein ,,:eiteres ähnliches Beispiel findet sich in der getline-Funktion, die wir in Kapi- tell geschrieben haben. Dort können wir if (e == '\n') ( sei) = e; ++1; Ungewöhnlich ist, daß ++ und -- sowohl vor als auch nach ihren Operanden an- gegeben werden können (PräfIX-Notation ++0 oder PostflX-Notation 0++). In bei den Fäl- len wird die Variable 0 inkrementiert. Allerdings wird 0 im Ausdruck ++ 0 inkremen- tiert, bevor der resultierende Wert verwendet wird, während 0 bei n++ inkrementiert wird, nachdem der Wert von 0 benutzt wurde. Wird also der Resultatwert weiterverwen- det, so sind die Ausdrücke ++ 0 und 0++ verschieden. Hat 0 zum Beispiel den Wert 5, dann erhält bei } ersetzen durch die kompaktere Formulierung if (e == '\n') s[i++) = e; x = n++; Als drittes Beispiel betrachten wir die Standard-Funktion strcat(s,t), die die Zei- chenkette t an das Ende der Zeichenkette sanhängt. strcat geht davon aus, daß in s gengend Platz vohanden ist, um beide Zeichenketten aufzunehmen. So wie wir sie ge- schrieben haben, liefert strcat kein Funktionsresultat; die Version aus der Standard-Bi- bliothek liefert einen Zeiger auf die resultierende Zeichenkette. /* streat: t an das Ende von s anhaengen; s muss dabei gross genug sein */ void streat(ehar s[), ehar t[) ( x den Wert 5; bei x = ++n; int i, j; i = j = 0; while (s[i) 1= '\0') /* Ende von s finden */ erhält x aber den Wert 6. 0 ist anschließend in beiden Fällen 6. Die Inkrement- und De- krement-Operatoren können nur auf Variablen angewendet werden; ein Ausdruck wie (i + j)++ ist illegal. Wird kein Resultatwert, sondern nur der Inkrement-Effekt benötigt, wie in if (e == '\n') nl++; i++; while «s[i++) = t[j++) != '\0') /* t kopieren */ int i. j; } Nachde jeweils ein Zeihen aus t nach s kopiert worden ist, wird der Inkrement-Opera- tor ++ In PostflX-NotatJon auf i und j angewendet, damit diese Indizes im nächsten Durchgang durch die Schleife wieder auf neue Zeichen zeigen. Aufgabe 2-4. Schreiben Sie eine Variante von squeeze(sl,s2), die jedes Zeichen aus sl entfernt, das in der Zeichen kette s2 vorkommt. 0 Aufgabe 2-5, Schreiben Sie eine Funktion any(sl,s2), die die Position des ersten Zei- chens in der Zeichenkette si als Resultatwert liefert, das in der Zeichenkelte s2 vor- kommt. Enthält si kein Zeichen aus s2, so soll der Resultatwert -1 sein. (Die Biblio- theksfunktion strpbrk macht dasselbe, liefert aber einen Zeiger auf die Stelle.) 0 2.9 Bit-Manipulationen C verfügt über sechs Operatoren für Bit-Manipulationen; diese Operatoren können nur auf Integer-Operanden angewendet werden, also auf char, short, int und long, und zwar signed oder uosigned. dann sind PräfIX- und PostflX-Notation gleichbedeutend. Es gibt jedoch Situationen, wo genau einer der Resultatwerte benötigt wird. Betrachten wir beispielsweise die Funktion squeeze(s,c), die alle Kopien des Zeichens c aus der Zeichenkette s entfernt. /* squeeze: alle Zeichen e in s loesehen */ void squeeze(char s[), int e) { for (i = j = 0; sei) != '\0'; iH) if (s[i) != e) S[jH) = sei); s[j) = '\0'; } 
48 2 Datentypen, Oper n und Ausdrücke 2.10 Zuweisunge' d Ausdrücke 49 & UND- Verknüpfung von Bits I ODER- Verknüpfung von Bits Exklusive-ODER- Verknüpfung von Bits « Bit-Verschiebung nach links » Bit-Verschiebung nach rechts Bit-Komplement (unär) Die UND- Verknüpfung von Bits, der Operator &, wird oft dazu benutzt, eine Aus- wahl von Bits auf 0 zu setzen. Beispielsweise löscht n = n & 0177; in n alle Bits mit Ausnahme der letzten sieben. Mit dem ODER-Operator I setzt man Bits auf 1: x = x I SET_ON; schaltet in x genau die Bits ein, die in SET_ON auf 1 gesetzt sind. Die Exklusive-oDER-Verknüpfung von Bits, der Operator ", setzt jede Bit-Position auf 1, in der seine Operanden verschiedene Bit-Werte haben, und auf 0, wenn sie gleich sind. /* getbits: n Bits ab position p */ unsigned getbits(unsigned x. int p. int n) { return (x » (p+1-n» & -(-0 « n); Man muß die Bit-Verknüpfungen & und I von den logischen Verknüpfungen && und 11 unterscheiden, denn die logischen Verknüpfungen berechnen eine Bedingung von links nach rechts. Hat beispielsweise x den Wert 1 und y den Wert 2, dann ist x & y Null, aber x && y ist Eins. Die Shift-Operatoren « und » verschieben ihren linken Operanden um so viele Bit-Positionen nach links oder rechts, wie der rechte Operand angibt, der positiv sein muß. Also bewegt x « 2 den Wert von x um zwei Positionen nach links und schiebt Null-Bits nach; dies ist äquivalent zu einer Multiplikation mit 4. Wird ein Wert mit Typ unsigned nach rechts geschoben, so wird immer Null nachgeschoben. Wird ein vorzei- chenbehafteter Wert nach rechts geschoben, so wird auf manchen Systemen das Vorzei- chen-Bit nachgeschoben (arithmetic shift) und auf anderen Systemen Null-Bits (logical shift). Der unäre Operator - komplementiert die Bits in einem ganzzahligen Wert; das heißt, ein Bit mit Wert 1 wird in 0 verwandelt und umgekehrt. Zum Beispiel setzt x .. x & -077 die letzten sechs Bits auf Null. Man beachte, daß x & .077 von der Wortlänge des Rech- ners unabhängig ist und damit besser als etwa x & 0177700, wo davon ausgegangen wird, daß x eine Größe mit 16 Bits ist. Die portable Formulierung verursacht zur Aus- führungszeit des Programms keinen zusätzlichen Aufwand, denn .077 ist ein konstanter Ausdruck und kann bereits vom Übersetzer bewertet werden. Um einige Bit-Operatoren vorzuführen, betrachten wir die Funktion getbits(x,p,n), die aus x ein Feld mit n Bits liefert (entsprechend nach rechts geschoben), das in Position p beginnt. Wir gehen davon aus, daß die Bit-Position 0 das rechte Ende von x bezeichnet und daß n und p vernünftige positive Werte sind. Beispielsweise liefert getbits(x,4,3) die drei Bits in den Positionen 4, 3 und 2, entsprechend nach rechts geschoben. } Der Ausdruck x» (p+1-n) verlagert das gewünschte Feld an das rechte Ende eines Worts. .0 besteht nur aus Bits mit dem Wert 1; schiebt man dies mit .0«0 um n Bit- Positionen nach links, so entsteht eine Maske, die in den rechten n Positionen 0 enthält und sonst 1; wird dieser Wert schließlich mit. komplementiert, so entsteht eine Maske, die rechts in n Bit-Positionen 1 enthält. Aufgabe 2-6. Schreiben Sie eine Funktion setbits(x,p,n,y), die als Resultat den Wert x liefert, wobei die n Bits ab Position p durch die n am weitesten rechts stehenden Bits von y ersetzt sind. 0 Aufgabe 2-7. Schreiben Sie eine Funktion invert(x,p,n), die beim Wert von x genau die n Bits komplementiert (also von 1 in 0 und umgekehrt verwandelt), die in Bit-Position p beginnen. Die anderen Bits sollen unverändert bleiben. 0 Aufgabe 2-8. Schreiben Sie eine Funktion rightrot(x,n), die den Integer-Wert x um n Bit-Positionen nach rechts rotiert. 0 2.10 Zuweisungen und Ausdrücke Im Ausdruck i .. i + 2 wird die Variable auf der linken Seite sofort am Anfang der rechten Seite wiederholt; dies kann kompakter als i += 2 formuliert werden. Der Operator += ist ein sogenannter Zuweisungsoperator. Für die meisten binären Operatoren (also für Operatoren, die wie + einen rechten und linken Operanden besitzen) existieren entsprechende Zuweisungsoperatoren op =, wobei op aus folgender Operatorliste stammt: + * / X « » & Sind exprl und expr2 Ausdrücke, dann ist exprl op" expr2 äquivalent zu exprl = ( expTl ) op ( expr2 ) abgesehen davon, daß exprl nur einmal bewertet wird. Man beachte dabei die Klam- mern um expr2: x *= y + 1 ist äquivalent zu x = x * (y + 1) und nicht zu x .. x * y + 1 
50 2 Datentypen, 0 .toren und Ausdrücke 2.12 Vorrang UD ihenfolge bei Bewertungen 51 int b; for (b = 0; x 1= 0; x »= 1) if (x & 01) b++; return b; rl 1 r2 : r3 wird zunächst exprl berechnet. Ist der Wert nicht null, trifft also die Bedingung zu, dann wird der Ausdruck expr2 berechnet und dessen Wert ist dann das Resultat des bedingten Ausdrucks. Andernfalls wird exprJ berechnet und das ist das Resultat. Nur einer der Ausdrücke expr2 und exprJ wird bewertet. Um also an z das Maximum von a und b zu- zuweisen, formulieren wir z . (a > b) 1 a : b; /* z = max(a, b) */ Zu beachten ist, daß der bedingte Ausdruck tatsächlich ein Ausdruck ist und wie jeder andere Ausdruck verwendet werden kann. Haben expr2 und exprJ verschiedene Typen, wird der Resultattyp durch die Umwandlungsregeln festgelegt, die früher in die- sem Kapitel beschrieben wurden. Ist zum Beispiel fein Doat-Wert und n ein Int-Wert, dann hat der Ausdruck (n > 0) 1 f : n als Resultat einen Doat-Wert, und zwar unabhängig davon, ob n positiv ist oder nicht. In einem bedingten Ausdruck muß der erste Ausdruck, also die Bedingung, nicht in Klammern eingeschlossen werden, da der Vorrang von? : sehr niedrig ist, gerade noch höher als der Vorrang von Zuweisungen. Trotzdem sollten wir Klammern benutzen, da sie die Bedingung leichter sichtbar machen. Ein bedingter Ausdruck führt oft zu knappem Code. Beispielsweise gibt die fol- gende Schleife n Elemente eines Vektors aus, und zwar 10 Elemente pro Zeile, mit ei- nem Leerzeichen zwischen Spalten und mit einem Zeilentrenner nach jeder Zeile (auch nach der letzten). for (I = 0; I < n; 1++) prlntf("X6dXc", e[i], (iX10==9 11 i==n-1) ? '\n' : ' '); Ein Zeilentrenner wird nach jedem zehnten Element ausgegeben und auch nach dem letzten Element. Nach jedem anderen Element steht ein Leerzeichen. Dies sieht viel- leicht verzwickt aus, aber es ist kompakter als die äquivalente Formulierung mit If-else. Ein weiteres gutes Beispiel ist printf(IISie haben %ci Teil%s.\n", n, n==1 1 1111 : "e"); Aufgabe 2 -10. Ändern Sie die Funktion lower, die Großbuchstaben in Kleinbuchstaben umwandelt, so daß ein bedingter Ausdruck verwendet wird anstelle einer if-else- Anweisung. 0 Beispielsweise zählt die Funktion bItcount die Anzahl Bits mit Wert 1 in ihrem ganzzahligen Argument: /* bitcount: 1-Bits zaehlen */ Int blteount(unsigned x) { } Da der Parameter x als unslgned deklariert ist, ist sicher, daß beim Verschieben nach rechts Null-Bits und nicht Vorzeichen-Bits nachgeschoben werden, und zwar unabhängig von der Maschine, auf der das Programm ausgeführt wird. Ganz abgesehen von der prägnanten Formulierung reflektieren zusammengesetzte Zuweisungsoperatoren eher die menschliche Denkweise. Wir sagen schließlich ..Addiere 2 zu I" oder ..Vergrößere I um 2" und nicht ..Nimm i, addiere 2 und schreibe das Resultat zurück nach 1." Deshalb ist der Ausdruck I += 2 der Formulierung i = i+2 vorzuziehen. Darüber hinaus wird in einem komplizierten Ausdruck wie yyval [yypv[p3+p4] + yypv[p1+p2]] += 2 der Text durch dcn zusammengesetzten Zuweisungsoperator leichter verständlich: Der Leser muß nicht mühsam feststellen, daß zwei lange Ausdrücke tatsächlich gleich sind, oder sich gar überlegen, warum sie nicht gleich sind. Schließlich kann ein zusammenge- setzter Zuweisungsoperator dem Übersetzer helfen, effizienten Code zu generieren. Wir haben bereits gesehen, daß ein Zuweisungsoperator den zugewiesenen Wert als Resultat liefert und in Ausdrücken verwendet werden kann; das häufigste Beispiel ist: while «e = getehar(» != EOF) Die anderen Zuweisungsoperatoren (+=, - = etc.) können ganz analog in Ausdrücken verwendet werden, dies ist jedoch weniger häufig. In allen solchen Ausdrücken ist der Resultattyp einer Zuweisung immer der Typ ihres linken Operanden, und der Resultatwert ist der Wert nach der Zuweisung. Aufgabe 2-9. In einem Zahlensystem, das auf dem 2-Komplement beruht, löscht der Ausdruck x &= (x-1) das rechteste Bit mit Wert 1 in x. Erklären Sie warum. Benutzen Sie diesen Trick, um eine schnellere Version von bitcount ZU realisieren. 0 2.U Vorrang und Reihenfolge bei Bewertungen Tabelle 2-1 faßt die Regeln für Vorrang und Assoziativität aller Operatoren zusam- men; sie enthält auch Operatoren, die wir bisher nicht besprochen haben. Innerhalb ei- ner Zeile haben Operatoren den gleichen Vorrang. Die Zeilen sind mit abnehmendem Vorrang angeordnet, also haben zum Beispiel *, / und % den gleichen Vorrang, und die- ser Vorrang ist größer als der des binären + und -. Der ..Operator" () steht für Funkti- onsaufrufe. Mit den Operatoren -> und. werden Komponenten einer Struktur ausge- wählt; diese Operatoren werden, wie auch sizeof (Größe eines Objekts) in Kapitel 6 be- sprochen. In Kapitel 5 behandeln wir * (Inhalt bei Verweis mit einem Zeiger) und & (Adresse eines Objekts) und in Kapitel 3 wird der Komma-Operator behandelt. 2.11 Bedingter Ausdruck Die Anweisungcn if (a > b) z = a; else z = b; weisen das Maximum von a und b an z zu. Der bedingte Ausdruck, der mit dem Opera- torpaar ..? :", formuliert wird, bietet eine Alternative für diese und ähnliche Konstruktio- nen. Im Ausdruck 
52 2 Datentypen, Op' oren und Ausdrücke 2.12 Vorrang l Q.eihenfolge bei Bewertungen 53 Tabelle 2-1. Vorrang und Assoziativität der Operatoren Assoziativität von links her von rechts her von links her von links her von links her von links her von links her von links her von links her von links her von links her von links her von rechts her von rechts her von links her Unär haben +, - und * mehr Vorrang als binär. Zu beachten ist, daß der Vorrang der Operatoren zur Bit-Manipulation &? ... und I geringer ist als der Vorrang der Vergleiche == und! =. Daraus folgt, daß beim Uberprü- fen bestimmter Bits, wie etwa if «)( & MASIC) == 0) ... die Bit-Verknüpfungen in Klammern eingeschlossen werden müssen, damit korrekte Re- sultate entstehen. Wie bei den meisten anderen Sprachen ist auch für C nicht definiert, in welcher Reihenfolge die Operanden eines Operators bewertet werden. (Ausnahmen sind &&, 11, ?: und der Komma-Operator.) Beispielsweise kann in der Anweisung )( = fO + gO; f vor oder nach g bewertet werden; wenn also f oder g eine Variable ändert, von der je- weils die andere Funktion abhängt, dann kann x von der Reihenfolge der Bewertungen abhängen. Zwischenresultate können in temporären Variablen gespeichert werden, um eine bestimmte Bewertungsreihenfolge zu erzwingen. Analog ist auch die Reihenfolge nicht festgelegt, in der die Argumente für einen Funktionsaufruf bewertet werden, also kann die Anweisung printf("%d %d\n", ++n, power(2, n»; /* FALSCH */ bei verschiedenen Übersetzern verschiedene Resultate liefern, je nachdem, ob n inkre- mentiert wird, bevor power aufgerufen wird. Die Lösung ist natürlich Operatoren o [] -> . 1 - ++ -- + - * & (o/pe) sizeof * / x + « » < <= > >= 1= & I && 11 1: = += -= *= /= X= &= = 1= «= »= gen, so kann die Reihenfolge, in der die beteiligten Variablen verändert werden, sehr subtil das Resultat des Ausdrucks beeinflussen. Eine unglückliche Situation zeigt sich in s[i] = i++; Die Frage ist, ob der alte Wert von i oder der neue Wert als Index benutzt wird. Über- setzer können dies verschieden interpretieren und können folglich je nach Interpretation verschiedene Resultate liefern. Der Standard legt die meisten dieser Dinge absichtlich nicht fest. Es bleibt dem Übersetzer überlassen, wann Nebenwirkungen (Zuweisung an Variablen) stattfmden, da die bestmögliche Reihenfolge sehr stark von der Maschinenar- chitektur abhängt. (Der Standard legt fest, daß alle Nebenwirkungen der Argumente stattfmden, bevor eine Funktion aufgerufen wird, aber das würde im dem oben erwähn- ten Aufruf von printf nicht helfen.) Die Moral ist, daß es schlechter Programmierstil in jeder Sprache ist, wenn man so formuliert, daß die Bewertungsreihenfolge eine Rolle spielt. Man muß natürlich wissen, was man vermeiden soll, aber wenn Sie nicht wissen, wie gewisse Dinge auf verschiede- nen Maschinen realisiert werden, werden Sie nicht in Versuchung geraten, aus einer be- stimmten Implementierung einen Vorteil ziehen zu wollen. ++ni printf("%d %d\n". n, power(2, n»; Funktionsaufrufe, verschachtelte Zuweisungen und die Inkrement- und Dekre- ment-Operatoren verursachen ..Nebenwirkungen" - als Neb.enprodukt der Berec?nung eines Ausdrucks wird eine Variable verändert. Verursacht em Ausdruck NebenWlrkun- ... \\" " .' \. J.. 
55 3 Kontrollstrukturen Kontrollstrukturen defInieren die Reihenfolge, in der Berechnungen durchgeführt werden. In früheren Beispielen haben wir bereits die häufIgsten Kontrollstrukturen ken- nengelernt; im vorliegenden Kapitel erklären wir den Rest und präzisieren die früher be- sprochenen. 3.1 Anweisungen und Blöcke Aus einem Ausdruck wie x = 0 oder i++ oder printr(...) wird eine Anweisung, wenn ein Semikolon folgt: )( = 0; ;++; printf( ... ); In C steht ein Semikolon am Ende einer Anweisung; in Sprachen wie Pascal steht es nur zwischen Anweisungen. Die geschweiften Klammern { und } dienen dazu, Vereinbarungen und Anweisun- gen in einen Block zusammenzufassen. Ein solcher Block ist syntaktisch äquivalent zu ei- ner einzelnen Anweisung. Ein offensichtliches Beispiel sind die geschweiften Klammern, die die Anweisungen einer Funktion umgeben; andere Beispiele sind geschweifte Klam- mern, die mehrere Anweisungen nach Ir, else, while oder ror zusammenfassen. (Varia- blen kann man in jedem Block vereinbaren; dies werden wir in Kapitel 4 besprechen.) Nach der rechten geschweiften Klammer, die einen Block abschließt, steht kein Semiko- lon. 3.2 if-else Mit der Ir-else-Anweisung werden Entscheidungen formuliert. Der else-Teil ist op- tional; formal gilt folgende Syntax: i f ( expression ) statement 1 else statement2 expression wird berechnet. Trifft die Bedingung zu (hat also expression einen von 0 ver- schiedenen Wert), so wird statement 1 ausgeführt. Trifft die Bedingung nicht ZU (hat also expression den Wert 0), so wird statement 2 ausgeführt, falls ein else-Teil vorhanden ist. Da bei Ir einfach der numerische Wert eines Ausdrucks getestet wird, sind be- stimmte Abkürzungen möglich. Am offensichtlichsten ist dabei i f ( expression ) anstelle von i f ( expression ,= 0) Manchmal ist dies klar und übersichtlich, manchmal aber werden die Vorgänge eher ver- schleiert. Da der else-Teil einer Ir-else-Anweisung optional ist, entsteht eine Mehrdeutigkeit, wenn ein else-Teil in einer verschachtelten Folge von Ir-Anweisungen fehlt. Dem wird t  . 
56 dadurch begegnet, daß der else- Teil imer mi d<:m letzten If verbunden wird, für das noch kein else-Teil existiert. BeispielsweISe gehort m I f (n > 0) if (a > b) z = a; etse z = b; il . U:'e wir durch Einrücken angedeutet haben. Sollten Sie das der else-Te zum mneren , Wl d' entspre nicht gemeint haben, dann müsse? Sie geschweifte Klammern benutzen, um te - chenden Zugehörigkeiten zu erZWIngen: I f (n > 0) { if (a > b) z = a; 3 Kontrollstrukturen 3.3 else-i! 57 ist so häufig, daß wir sie kurz gesondert besprechen wollen. Diese Folge von if- Anweisungen ist die allgemeinste Art, eine Entscheidung unter vielen Alternativen zu formulieren. In der angegebenen Reihenfolge wird eine expression nach der anderen be- wertet; sobald eine dieser Bedingungen zutrifft, wird die abhängige Anweisung aus- geführt, und damit ist die Ausführung der gesamten Kette beendet. Wie immer, steht statement für eine einzelne Anweisung oder eine Gruppe von Anweisungen in geschweif- ten Klammern. Der letzte else-Teil behandelt den Fall, daß keine der früheren Bedingungen zu- trifft. Ist in einem solchen Fall keine explizite Aktion notwendig, kann else statement } auch fehlen, oder man kann diesen Teil dazu benutzen, eine ..unmögliche" Bedingung als Fehler zu erkennen. Um' eine Entscheidung mit drei Alternativen zu illustrieren, zeigen wir hier eine Funktion für binäre Suche, die feststellt, ob ein bestimmter Wert x in dem sortierten Vek- tor v vorkommt. Dabei müssen die Elemente von v in aufsteigender Reihenfolge ange- ordnet sein. Die Funktion liefert die Position, also eine Zahl zwischen 0 und n-1, bei der x in v vorkommt, und -1 falls nicht. Die binäre Suche vergleicht zuerst den Eingangswert x mit dem mittleren Element des Vektors v. Falls x kleiner als der mittlere Wert ist, beschränkt sich die Suche auf die untere Hälfte des Vektors, ansonsten auf die obere Hälfte. In jedem Fall wird im näch- sten Schritt x mit dem mittleren Element der gewählten Hälfte verglichen. Dieser Vor- gang der Zweiteilung wird solange fortgesetzt, bis der Wert gefunden wird oder bis der Bereich leer ist. /* blnsearch: x In v[O] <= v[1] <= .'. <= v[n-1] finden */ Int blnsearch( int x, Int v[], Int n) { } else z = b; Der Konflikt ist in Situationen wie der folgenden besonders bösartig: if (n >= 0) for (I = 0; I < n; 1++) if (s[l] > 0) ( prlntf("..."); return I; else /* FALSCH */ prlntf(IIFehler -- n Ist negatlv\n"); .. . iß t .. dlich, W as wir möchten läßt jedoch den Ubersetzer E' nrücken zeigt zwar unm vers an , . Ar k \t und so wird der else-Teil mit der inneren If-Anweisung verknüpft. Dtese  von F:Wer kann schwierig zu fmden sein; es empfiehlt sich, geschweifte Klammern bel ver- schachtelten If-Anweisungen zu verwenden. Beachten Sie übrigens, daß in If (a > b) z = a; else z = b; h = in Semikolon steht. Dies liegt daran, daß gramatisch af I eine Anwisung t, Zund a e Ausdruck als Anweisung wie "Z = a;" muß Immer mit emem Semikolon abgeschlossen werden. 3.3 else-i! Die Konstruktion I f ( expression ) statement e I se I f ( expression statement e I se if ( expression statement else if ( expression statement else statement Int low, high, mld; low = 0; high = n - 1; while (low <= high) { mid = (low+hlgh) / 2; if (x < v[mld]) high = mid - 1; else if (x> v[mld]) low = mld + 1; else /* gefunden */ return mld; } Die wesentliche Entscheidung ist bei jedem Schritt, ob x kleiner, größer oder gerade gleich dem mittleren Element v[mld] ist; dies ist eine natürliche Anwendung für einc else-If-Kette. Aufgabe 3 -1. Für unsere binäre Suche finden zwei Vergleiche in der Schleife statt, wenn einer genügen würde (für den Preis weiterer Vergleiche außer halb der Schleife). } return -1; /* nicht gefunden */ 
58 3 Kontrollstrukturen 3.5 Schleifen - die und for 59 Schreiben Sie eine Version mit nur einem Vergleich in der Schleife, und messen Sie den Unterschied in der Laufzeit. 0 int e, i, nwhite, nother, ndigit[10]; nwhite = nother = 0; for (i = 0; i < 10; i++) ndigit[i] = 0; while «e = getehar(» 1= EOF) { switeh (e) ( e8se '0': ease '1': ease '2': ease '3': ease '4': ease '5': ease '6': ease '7': ease '8': ease '9': ndigi t[e-'O'] ++; break; esse I I: ease '\n': ease '\ t ' : nwhite++; break; default: nother++; break; Die break-Anweisung sorgt dafür, daß der switch unmittelbar verlassen wird. Da case-Marken nur Textpositionen bezeichnen, wird ein Programm an einer case-Marke vorbei weiter ausgeführt, nachdem eine Alternative erledigt ist, es sei de, wir formulie- ren eine elizite Aktion, um den switch zu verlassen. break und return sind die häufig- sten AnweISungen, mit denen ein switch verlassen wird. Mit break kann man auch whlle-, for- oder do-Schleifen vorzeitig verlassen; dies wird später in diesem Kapitel be- sprochen. Daß von einer Alternative in switch unmittelbar zur nächsten übergegangen wird, hat Vor- und Nachteile. Vorteilhaft ist, daß dadurch mehrere case-Marken vor einer ein- zelnen Alternative stehen können, wie im vorliegenden Beispiel bei den Ziffern. Dafür muß aber ormalerweise jede Alternative mit einer break-Anweisung beendet werden, um zu verhindern, daß implizit die nächste Alternative erreicht wird. Aus einer Alterna- tive zur nächsten überzugehen, ist nicht unbedingt robust; wird das Programm modifi- ziert, ergeben sich leicht Probleme. Abgesehen von mehreren case-Marken für eine ein- zelne Alternative, sollten implizite Übergänge sparsam eingesetzt werden, und zwar mit Kommentar. Sie sollten sich auch angewöhnen, eine break-Anweisung nach der letzten Alterna- tive, in unserem Beispiel also nach derauIt, einzufügen, obgleich dies aus logischer Sicht unnötig ist. Wird später eine weitere Alternative am Ende angefügt, so kann sich dieses Stück defensiven Programmierens als Rettung erweisen. Aufgabe 3-2. Schreiben Sie eine Funktion escape(s,t), die Zeichen wie Zeilentrenner und Tabulatorzeichen durch entsprechende Ersatzdarstellungen wie \n und \t ersetzt, während sie die Zeichenkette t nach s kopiert. Benutzen Sie eine switch-Anweisung. Schreiben Sie auch für die umgekehrte Operation eine Funktion, die Ersatzdarstellungen in reale Zeichen umwandelt. 0 3.4 switch Die switch-Anweisung ist eine Auswahl unter mehreren ternativen, die. untr- sucht ob ein Ausdruck einen von mehreren konstanten ganzzahllgen Werten besitzt; 1St dies der Fall, so wird entsprechend verzweigt. swi teh ( t!Xprf!ssion ) { ease const -t!Xpr: statements ease const -t!Xpr: statements def au I t : statements } Jeder Alternative geht eine Reihe von case-Marken mit ganzzahligen Kotanten oder konstanten Ausdrücken voraus. Hat die expression bei switch den Wert emer der case- Konstanten, so wird die Ausführung des Programms bei dieser case-Marke fortgesetzt. Alle case-Konstanten müssen voneinander verschieden sein. Wenn keine der Konstanten paßt, wird bei der derault-Marke fortgefahren. Die drauIt:Marke. ist opti.onal; ist keine vorhanden und paßt keine der Konstanten, so findet Im sWltch keme AktIOn statt. case und default können in beliebiger Reihenfolge angeordnet werden. In Kapitel 1 haben wir ein Programm formuliert, das Ziffern, Zwischenräume und alle anderen Zeichen gezählt hat. Dazu haben wir eine Kette von If ... else if ... else ver- wendet. Hier ist das gleiche Programm mit einem switch: #include <stdio.h> main() /* Ziffern, Zwisehenraeume und andere Zeichen zaehlen */ { 3.5 Schleifen - while und for Den whlle- und for-Schleifen sind wir bereits begegnet. Bei wh i I e ( t!Xprf!ssion ) statement wird zunächst eine Bedingung bewertet. Ist expression von 0 verschieden so wird statement, die abhängige Anweisung, ausgeführt und anschließend expression rneut be- wertet. Dieser Zyklus wiederholt sich, bis schließlich expressioll den Wert 0 hat. Danach wird die Ausführung des Programms hinter statemellt fortgesetzt. Die ror-Anweisung for ( exprl ; expr2 exp r 3) statement } ist äquivalent zu t!Xprl ; while ( expr2 ) { statement t!Xp r 3 ; } printf("digits ="); for (i = 0; i < 10; i++) printf(1I Xejll, ndigit [i]); printf(II, white spaee = Xej, other = Xej\n", nwhite, nother); return 0; } mit Ausnahme des Verhaltens von continue, das in Abschnitt 3.7 beschrieben wird. } 
60 3 Kontrollstrukturen 3.5 Schleifen - i/e und for 61 Grammatisch betrachtet sind die drei Teile von for jeweils Ausdrücke. In aller Re- gel sind exprl und expr3 Zuweisungen oder Funktionsaufrufe und expr2 ist ein Vergleich. Jeder dieser drei Teile kann fehlen, jedoch müssen die Semikolons verbleiben. Fehlen exprl oder expr3, so werden sie einfach in der äquivalenten while-Formulierung ausgelas- sen. Fehlt die Bedingung expr2, so gilt sie immer als wahr. for (;;) ( #include <ctype.h> /* atoi: Ziffernkette s in int umwandeln; Version 2 */ int atoi(char se]) { } ist also eine ..unendliche" Schleife, die vermutlich durch Anweisungen wie break oder return beendet wird. Ob man while oder for bevorzugt, beruht weitgehend auf persönlicher Vorliebe. Beispielsweise gibt es in while ((c = getcharO) ... I , 11 c ... '\n' 11 c == '\t') /* Zwischenraum ueberspringen */ weder Initialisierung noch Reinitialisierung, also erscheint while natürlicher. for ist zu bevorzugen, wenn eine einfache Initialisierung und Reinitialisierung vor- genommen werden sollen, denn for faßt die kontrollierenden Anweisungen der Schleife zusammen und macht sie am Anfang der Schleife sichtbar. Dies ist besonders offensicht- lich bei int i, n, sign; for (i = 0; isspace(s[i]); i++) ; /* Zwischenraeume ueberlesen */ sign = (s[i] == '') ? -1 : 1; if (s[i] == '+' 11 s[i] == '-') /* Vorzeichen */ i++i for (n = 0; isdigit(s[i]); i++) n = 10 * n + (s[i] - '0'); return sign * n; for (i = 0; i < n; i++) } Di Standard-ibliothek enthält eine aufwendigere Funktion strtol zur Umwandlung von ZeIchenketten m long-Werte; siehe Abschnitt 5 im Anhang B. Die Kontrollausdrücke für eine Schleife zusammenzuhalten, bringt Vorteile - ganz besonders, wenn mehrere Schleifen verschachtelt sind. Die nachfolgende Funktion be- utzt Shell's Algorithmus, um einen Vektor von iot-Werten zu sortieren. Die grundsätz- lie Idee bei diesem Algorithmus, den D. L. Shell 1959 erfunden hat, ist es, in den frühen Phaeo Elemente zu v.ergleichen, die weit auseinander liegen; einfachere Aus- tusch-Sor!lerverfahren vergletchen meistens nur benachbarte Elemente. Shell's Algo- n.thm neIgt dazu, große Störungen der Reihenfolge schnell zu eliminieren, damit haben die sp.ateren hasen der Sortierung weniger zu tun. Der Abstand zwischen zwei Elemen- ten, die verglichen werden, wird allmählich auf 1 verringert. In dieser letzten Phase wird aus Shell's Algorithmus ein Austauschverfahren für benachbarte Elemente. /* shellsort: v[0]...v[n-1] aufsteigend sortieren */ void shellsort(int v[J, int n) { Diese Ausdrucksweise ist üblich in C, um die ersten 0 Elemente eines Vektors zu bear- beiten, ist also analog zur Do-Schleife in Fortran oder zum for in Pascal. Es gibt jedoch gewisse Unterschiede: Die kontrollierende Variable und die Grenzen einer for-Schleife in C können aus der Schleife heraus beeinflußt werden, und der Wert der kontrollieren- den Variablen I bleibt erhalten, wie auch immer die Schleife beendet wird. Da bei for beliebige Ausdrücke angegeben werden können, beruhen for-Schleifen nicht ausschließ- lich auf arithmetischen Folgen. Es ist jedoch trotzdem schlechter Stil, wenn man Berech- nungen, die keine Beziehung zueinander haben, in die Initialisierung oder die Reinitiali- sierung einer for-Anweisung packt; diese Teile von for reserviert man besser für Opera- tionen zur Kontrolle der Schleife. Als größeres Beispiel ist hier eine weitere Fassung von atol, der Funktion, die das numerische Äquivalent einer Zeichenkette liefert. Diese Fassung ist etwas allgemeiner, als die im Kapitel 2; am Beginn der Zeichenkette können Zwischenraumzeichen auftre- ten sowie ein optionales + oder - als Vorzeichen. (In Kapitel 4 folgt dann noch die Funktion atof, die die gleiche Umwandlung für Gleitpunktwerte vornimmt.) Die Programmstruktur reflektiert das Eingabeformat: Zwischenraum überlesen, falls vorhcmden Vorzeichen fes/hal/en, falls vorlranden Zahlellwert eilllesell und umwandeln Jeder Teil des Programms erledigt seine eigene Aufgabe und hinterläßt klare Verhältnis- se für den nächsten Schritt. Der ganze Vorgang wird beendet, sobald ein Eingabezeichen gefunden wird, das nicht mehr Teil einer Zahl sein kann. int gap, i, L t; for (gap = n/2; gap> 0; gap /= 2) for (i .. gap; i < n; 'i++) for (j=i-gap; j>=O && v[j]>v[j+gap]; j-=gap) { t .. v[j]; v[j] .. v[j+gap]; v[j+gap] .. t; } } Es gibt hier drei verschachtelte Schleifen. Die äußerste Schleife kontrolliert den Abstand zwischen zwei. lementen, die jeweils verglichen werden; dieser Abstand wird von n/2 ausge.hend beI Jedem Durchgang halbiert, bis schließlich 0 erreicht ist. Die mittlere Schlee geh! über die Elemente. Die innerste Schleife vergleicht alle Paare von Elemen- tn, die zuemander den Abstand gap besitzen, und vertauscht die Elemente der Paare die noch ni.cht  der richtigen Reihenfolge angeordnet sind. Da gap schließlich de Wert 1 erreIcht, smd zum Schluß alle Elemente korrekt sortiert. Man beachte daß auch di äußeste Schleife in die allgemeine Form einer for-Schleife paßt, obgleich 'hier keine anthmettsche Folge durchlaufen wird. 
62 Kontrollstrukturen Ein letzter C-Operator ist das Komma, das am häufigsten in for-Anweisungen vor- kommt. Zwei Ausdrücke, die durch Komma getrennt sind, werden von links nach rechts bewertet; Datentyp und Resultatwert sind dann der Typ und Wert des rechten Opern- den Man kann also in einer for-Anweisung jeweils mehrere Ausdrücke in den verschle- denn Teilen unterbringen, um beispielsweise zwei Indizes gleichzeitig zu bearbeiten. Dies wird in der folgenden Funktion reverse(s) illustriert, die die Zeichenkette s an Ort und Stelle umkehrt. #include <string.h> 1* reverse: s an Ort und Stelle umkehren */ void reverse(char se]) { int c, i, j; for (i = 0, j = strlen(s)-1; < j; i++, j--) { C . sei]; sm = s[j]; s[j] = c; } } Funktionsargumente, Variablen in Deklarationen usw. werde zw durch K?mma ge- trennt; dabei liegt jedoch kein Komma-Operator vor und folglich wud auch nicht unbe- dingt von links nach rechts bewertet. Komma-Operatoren sollten sparsam verwendet werden. Am besten passen si, wenn Konstruktionen eng zusammengehören, wie in der for-Schleife von reverse od:r m Makros, wo eine Berechnung mit mehreren Teilen als ein einfacher Ausruck erschemen muß. Ein Komma-Operator wäre auch beim Tauschen von Elementen m reverse ange- bracht, denn der Tausch kann als eine Operation angesehen werden: for (i = 0, j = strlen(s)-1; i < j; i++, j--) C = s[i], sei] = s[j], s[j] = c; Aufgabe 3-3. Schreiben Sie eine Funktion expand(sl,s2), die eine Abkürzung e -z aus der Zeichenkette sl in der Zeichenkette s2 durch die äquivalente vollständige Ltste abc...xyz darstellt. Berücksichtigen Sie dabei Groß- und Kleinbuchstaben sowie Ziffern, und akzeptieren Sie auch Angaben wie a - b - c oder a - zO - 9 und auch - a - z. Am An- fang oder Ende sollte - wörtlich genommen werden. 0 3.6 Schleifen - do-while Wie wir in Kapitell besprochen haben, wird die Abbrchbeingung von while. und for am Anfang der Schleife geprüft. Im Gegensatz dazu Wlfd bel do-wile, der ntten Schleifenkonstruktion in C, das Abbruchkriterium am Ende der Schleife nach Jedem Durchgang geprüft; die abhängige Anweisung der Schleife wird daher immer wenigstens einmal ausgeführt. Für die do-Schleife gilt folgende Syntax: do statement wh i l e ( expression ); Die abhängige Anweisung (statement) wird ausgeführt, anschließ:nd wird die Bedinng (expression) bewertet. Trifft sie zu, so wird die abhängige Anweisung erneut ausgefuhrt 3.7 break und co .Ie 63 usw. Ist schließlich die Bedingung nicht mehr erfüllt, wird die Schleife beendet. Abgese- hen von der Interpretation der Bedingung, ist do-while äquivalent zur repeat-until- Anweisung in Pascal. Die Erfahrung zeigt, daß do-while wesentlich seltener benutzt wird, als while und for. Trotzdem ist diese Anweisung ab und zu nützlich, wie zum Beispiel in der folgenden Funktion Itoa, die einen Zahlenwert in eine Zeichenkette verwandelt (also die Umkeh- rung von atoi). Die Aufgabe ist ein bißchen komplizierter als es zunächst den Anschein hat, da die einfachen Methoden die Ziffern in der falschen Reihenfolge generieren. wir produzieren deshalb die umgekehrte Zeichenkette und drehen sie anschließend um. /* itoa: n in Ziffernkette s umwandeln */ void itoa(int n, char sC]) {, int i, sign; if «sign = n) < 0) /* Vorzeichen notieren */ n = -ni /* n positiv machen */ i = 0; do { /* Ziffern von rechts her generieren */ s[i++] = n X 10 + '0'; /* naechste Ziffer */ } while «n 1= 10) > 0); /* entfernen */ if (sign < 0) s[i++] = 1_'; sei] = '\0'; reverse(s); } Die do-while-Schleife ist hier notwendig oder mindestens praktisch, da wenigstens ein Zeichen im Vektor s angelegt werden muß, auch wenn n den Wert Null hat. Wenn von do-while nur eine einzelne Anweisung abhängt, so braucht diese nicht in geschweifte Klan:'mern eingeschlossen zu werden. Wir verwenden trotzdem geschweifte Klammern, damIt beim flüchtigen Lesen while nicht für den Anfang einer while-Schleife gehalten wird. A.ufgabe 3-4. Werden ganzzahlige Werte im 2-Komplement repräsentiert, dann funktio- niert unsere Fassung von itoa für die größte negative Zahl nicht korrekt das heißt für den Wert (2 Wortgröße-l ) E ki n S . . h .. d '. ' - . raren le warum nIC t. An ern Sie Itoa so ab daß auch dieser Wert korrekt ausgegeben wird, unabhängig von der jeweiligen Maschine'. 0 Aufgabe 3-5. Schreiben Sie die Funktion itob(n,s,b), die die ganze Zahl n in Basis b in der Zeichenkette s ablegt. Zum Beispiel legt itob(n,s,16) die Zahl n als hexadezimale Ziffern in der Zeichenkette s ab. 0 Aufgabe 3-6. Schreiben Sie eine Version von itoa, die drei Argumente statt nur zwei akzeptiert. Das dritte Argument legt die minimale Breite der Ausgabe fest; nach Um- ,:andlung muß die Zeichenkette links mit Leerzeichen aufgefüllt werden, falls nötig, um sie genügend breit zu machen. 0 3.7 break und continue Es ist gelegentlich nützlich, wenn man Schleifen anders verlassen kann als nur durch Abbruchkriterien am Anfang oder Ende. Mit der break-Anweisung kann man for-, while- und do-Schleifen vorzeitig verlassen, genau wie eine switch-Anweisung. break , .;'l J 
64 3 KontroUstrukturen sorgt dafür, daß die innerste umgebende Schleife oder switch-Anweisung sofort verlassen wird. Die folgende Funktion trim entfernt Leerzeichen, Tabulatorzeichen und Zeilen- trenner vom Ende einer Zeichenkette; dabei wird break dazu benutzt, eine Schleife zu verlassen, wenn ganz rechts kein Zwischenraumzeichen mehr gefunden wird. /* trlm: Zwlschenraeune 81ft Ende entfernen */ int trlm(char s[]) { Int n; for (n = strlen(s)-1; n >z 0; n--) i f (s [n] I" I I && s [n] I = I \t' && s [n] 1= '\n') break; s[n+1] z '\0'; return n; ) strlen liefert die Länge der Zeichenkette. Die for-Schleife beginnt beim letzten Zeichen und sucht rückwärts nach dem ersten Zeichen, das weder Leerzeichen noch Ta- bulatorzeichen oder Zeilentrenner ist. Die Schleife wird abgebrochen, wenn ein entspre- chendes Zeichen gefunden wird, oder wenn n negativ wird (das heißt, wenn die ganze Zeichenkette abgesucht wurde). Sie sollten sich vergewissern, daß dies auch dann kor- rekt ist, wenn die Zeichenkette leer ist oder nur aus Zwischenraumzeichen besteht. Die continue-Anweisung gleicht break, wird jedoch weniger oft verwendet. continue sorgt dafür, daß die nächste Wiederholung der umgebenden for-, while- oder do-Schleife unmittelbar begonnen wird. Bei while und do bedeutet dies, daß sofort wie- der die Bedingung bewertet wird; bei for wird als nächstes die Reinitialisierung durch- geführt. continue bezieht sich nur auf Schleifen, nicht auf die switch-Anweisung. continue innerhalb einer swltch-Anweisung innerhalb einer Schleife sorgt dafür, daß die Schleife wiederholt wird. Beispielsweise behandelt das folgende Programmfragment nur die nicht-negativen Elemente im Vektor a; negative Werte werden übergangen. for (I = 0; i < n; 1++) { If (a(l] < 0) /* negative Elemente uebersprlngen */ cont I nue; /* nicht-negative Elemente bearbeiten */ ) Die continue-Anweisung wird oft benutzt, wenn der anschließend folgende Teil einer Schleife so kompliziert ist, daß die Umkehrung einer Bedingung und weiteres Einrücken zu einer sehr tiefen Verschachtelung im Programm führen würden. 3.8 goto und Marken C verfügt auch über eine beliebig zu mißbrauchende goto-Anweisung und Marken, zu denen gesprungen werden kann. Formal ist goto niemals notwendig, und man kann fast immer leicht ohne goto-Anweisungen auskommen. Wir haben in diesem Buch goto nicht verwendet. Trotzdem gibt es einige Situationen, in denen goto angebracht sein könnte. Am häufigsten kann man damit die Ausführung einer tief verschachtelten Struktur beenden, 3.8 goto und t 'ken 65 wie zum Beispiel zwe Sch.leifn uf einmal verlassen. break kann hierbei nicht benutzt werden, da break nur Jeweils die mnerste Schleife beendet. Es ergibt sich also: for ( ... ) for ( ... ) { I f ( Katastrophe goto error; > error: Oberreste abräumen Diese Programmstruktur ist nützlich, wenn die Fehlerbehandlung nicht trivial ist d wenn Fehler an verschiedenen Stellen auftreten können. ' un Do inMark hat die gleiche Frm wie ein. Variablenname; anschließend folgt ein :pe p t. .?I.e arke k or Jeder Anweisung in der gleichen Funktion wie goto ste en. Der Gültlgkeltsberelch emer Marke ist die ganze Funktion. Betracten  als weiteres Beispiel das Problem, herauszufinden, ob zwei Vekto- ren a und b em gleiches Element haben. Eine mögliche Lösung ist for (I = 0; I < n; 1++) for (j = 0; j < m; j++) if (a[l] == b[j]) goto found; /* kein gleiches Element gefunden */ found : /* eines gefunden: a[1] == b[j] */ . M ann ein Progr mit oto grundsätzlich auch ohne goto formulieren, aller- dmgs benotlgt man dazu V1el1eth eme sätzliche Variable, oder man muß einige Bedin- gungen mehrfach angeben. BelSPlelswetse kann man die Vektorsuche so umformulieren: found = 0; for (I = 0; i < n && !found; i++) for j = 0; j < m && !found; j++) If (a[l] == b[j]) found = 1; if (found) /* eines gefunden: a[l] == b[j] */ else /* kein gleiches Element gefunden */ .Mit eini.gen wenigen Ausnahmen, wie den hier zitierten, ist Code mit goto- Anwelsu?gen tm allgemeinen schwieriger zu verstehen und zu pflegen, als Code ohne goto. .WIr wollen zwar nicht dogmatisch werden, aber es scheint doch, daß goto- Anweisungen wenn überhaupt, dann selten benutzt werden sollten. t j 
67 4 Funktionen und Programmstruktur Mit Hilfe von Funktionen zerlegen wir große Problemstellungen in kleinere. Da- mit können wir auf dem aufbauen, was andere vor uns programmiert haben, und müssen nicht selbst wieder von neuem beginnen. Zweckmäßige Funktionen verbergen die De- tails einer Operation vor den Teilen eines Programms, die darüber nicht Bescheid wissen müssen; dadurch wird das Ganze klarer, und es ist weniger schwierig, Änderungen vorzu- nehmen. C wurde so entworfen, daß Funktionen effizient werden und einfach zu benutzen sind. Im allgemeinen bestehen C-Programme aus vielen kleinen Funktionen und nicht aus wenigen großen. Ein Programm kann in einer oder mehreren Ouelldateien angeord- net werden. Die Ouelldateien können separat übersetzt und dann gebunden werden, zu- sammen mit früher übersetzten Funktionen aus Bibliotheken. Wir wollen diesen Prozeß hier nicht besprechen, da die Details sich je nach System unterscheiden. Im Bereich der Deklaration und Definition von Funktionen wurde C im ANSI- Standard am sichtbarsten geändert. Wie wir bereits im Kapitel 1 sahen, kann man nun den Typ von Parametern bei der Deklaration einer Funktion vereinbaren. Die Syntax der Funktionsdefinition hat sich auch geändert, so daß nun Deklaration und Definition zu- sammenpassen. Dadurch kann ein Übersetzer jetzt viel mehr Fehler entdecken als früher. Wenn die Parameter korrekt vereinbart sind, werden außerdem die entsprechen- den Typumwandlungen automatisch vorgenommen. Der Standard macht die Regeln zum Gültigkeitsbereich von Namen klarer; insbe- sonders verlangt er, daß ein externes Objekt nur einmal definiert wird. Die Initialisie- rung wurde verallgemeinert: automatische Vektoren und Strukturen können nun initiali- siert werden. Auch der C-Preprozessor wurde verbessert. Zu den neuen Fähigkeiten des Pre- prozessors gehören ein kompletterer Satz von Anweisungen zur bedingten Übersetzung, eine Möglichkeit, konstante Zeichenketten aus Makroargumenten zu erzeugen und eine genauere Kontrolle über den Ablauf der Makro-Expansion. 4.1 Grundbegriffe Als erstes wollen wir ein Programm entwerfen und schreiben, das jede Eingabezei- le druckt, die ein spezielles "Muster", eine Zeichenkette, enthält. (Dies ist ein Spezialfall des UNiX-Dienst programms grep.) Sucht man zum Beispiel die Buchstabenfolge "ould" in den folgenden Zeilen Ah Lovel could you and I with Fate conspire To grasp this sorry Scheme of Things entire, Would not we shatter it to bits -- and then Re-mould it nearer to the Heart's Oesire! so erhält man Ah Love! could you and I with Fate conspire Would not we shatter it to bits -- and then Re-mould it nearer to the Heart's Oesire! 
68 4 Funktionel . d Programm struktur Die Arbeit läßt sich leicht in drei Teile aufteilen: wh il e ( es gibt noch eine üile ) i f ( die Zeile enthält das Suchmuster ) Zeile ausgeben Man kann dies selbstverständlich alles in main codieren; es ist jedoch besser, die natürliche Struktur auszunutzen und jeden Teil als separate Funktion zu realisieren. Drei kleine Teile sind leichter zu bearbeiten als ein großer, denn irrelevante Details kann man in den Funktionen verbergen, und das Risiko von unerwünschten Wechselwirkungen wird minimiert. Schließlich können die Einzelteile auch noch in anderen Programmen nütz- lich sein. wh i l e ( es gibt noch eine Zeile ) ist getline, eine Funktion, die wir in Kapitell geschrieben haben, und Zeile ausgeben ist printf, eine Funktion, die schon jemand für uns zur Verfügung gestellt hat. Folglich müssen wir nur eine Routine schreiben, die entscheidet, ob die Zeile das Muster enthält. Wir lösen dieses Problem, indem wir eine Funktion strindex(s,t) schreiben, die die Position (den ..Index") in der Zeichenkette s liefert, wo die Zeichenkette t beginnt, oder auch -1, wenn s das Muster t nicht enthält. Da in C Vektoren bei Position 0 beginnen, sind Indexwerte 0 oder positiv und ein negativer Wert wie -1 ist praktisch, um Mißerfolg anzuzeigen. Wenn wir später raffiniertere Methoden zur Mustererkennung benötigen, dann müssen wir nur strindex ersetzen; das restliche Programm kann unverändert blei- ben. (In der Standard-Bibliothek gibt es eine Funktion strstr, die strindex ähnelt; sie lie- fert jedoch einen Zeiger und keinen Index.) Mit diesem Entwurf sind die Details des Programms sehr einfach. Hier ist das Ganze, damit Sie sehen können, wie die Teile zusammenpassen. Momentan ist das Such- muster eine konstante Zeichenkette - nicht unbedingt ein sehr allgemeiner Mechanis- mus. Wir werden in Kürze besprechen, wie man Zeichenvektoren initialisiert, und in Ka- pitelS zeigen wir, wie man das Muster als Parameter bei der Programmausführung über- gibt. Wir haben hier auch eine leicht veränderte Version von getline; Sie sollten sie mit der Version in Kapitell vergleichen. #include <stdio.h> #define MAXLINE 1000 /* maximale Laenge der EIngabezeile */ int getl ine(char l ine[]. int max); int strindex(char source[]. char searchfor[]); char pattern [] = "ould"; /* Suchnuster * / /* alle Zeilen mit suchnuster finden */ main() { char line[MAXLINE]; int found = 0; while (getline(line, MAXLINE) > 0) if (strindex(line, pattern) >= 0) ( pr i ntf(IXs" , l ine); founcl++; ) return found; ) 4.1 Grundbeg 69 /* getline: Zeile an s, Laenge als Resultat */ int getline(char s[], int lim) { int c, i; i = 0; while (--lim > 0 && (c=getchar(» 1= EOF && c 1= '\n') s[i++] = c; if (c == '\n') s [i++] = c; s[i] = '\0'; return i; ) /* strindex: Position von t in s liefern. -1 falls nicht da */ ;nt strindex(char s[], char t[]) { int i, j. k; for (; = 0; s[i] 1= '\0'; ;++) { for (j=i, k=O; t[k]I='\O' && s[j]==t[k]; j++, k++) if (k > 0 && t[k] == '\0') return ;; ) return -1; ) Jede Funktionsdefmition hat folgende Form: Funktionstyp Funktionsname ( Parameterdeklarotionen ( VereinbtuUllgen und Anweisungen ) Verschiedene Teile können fehlen. Eine minimale Funktion ist durmv() () Diese Funktion tut nichts und liefert nichts. Eine leere Funktion wie diese ist manchmal nützlich als Platzhalter während der Programm entwicklung. Wnn der R;sultattyp weg- gelassen wird, wird int angenommen. Ein Programm besteht nur aus Definitionen von Variablen und Funktionen. Die Funktionen tauschen untereinander Information aus mit Hilfe von Argumenten und Re- sultaerten und über. externe Variablen. Die Funktionen können in beliebiger Reihen- folge m der Quelldatei angeordnet sein, und das Programm kann aus mehreren Quellda- teien bestehen, wenn eine Funktion nicht auf mehrere Dateien aufgeteilt wird. Die return-Anweisung dient dazu, einen Wert von der aufgerufenen Funktion zum Aufrufer als Resultat zu liefern. Nach return kann ein beliebiger Ausdruck folgen: return expression ; Falls ötig, wird expression in den Resultattyp der Funktion umgewandelt; expression wird oft mit Klammern umgeben, aber sie sind optional. Es steht dem Aufrufer frei, den Resultatwert zu ignorieren. Nach return braucht auch kein Ausdruck zu stehen; in diesem Fall wird dem Aufrufer kein Wert geliefert. Ebenso wird die Ausführung einer Funktion beendet, und dem Aufrufer wird kein Resul- tatwert geliefert, wenn bei Ausführung der Funktion die abschließende rechte geschweif- 
70 4 Funktion' nd Programm struktur te Klammer erreicht wird. Es ist nicht illegal, aber vermutlich problematisch, wenn eine Funktion an einer Stelle ein Resultat liefert und an einer anderen Stelle nicht. Auf alle Fälle ist der.. Wert" einer Funktion, die keinen liefert, sicherlich nicht sinnvoll. Das Mustersuchprogramm liefert aus main als Statusinformation die Anzahl Tref- fer. Dieser Wert steht der Umgebung zur Verfügung, von der das Programm aufgerufen wurde. Wie man ein C-Programm übersetzt und bindet, das aus mehreren Quelldateien besteht, ist je nach System unterschiedlich. Bei einem uNIX-System beispielsweise erle- digt diese Aufgabe das ce-Kommando, das in Kapitell erwähnt wurde. Angenmmen, unsere drei Funktionen stehen in drei Dateien, genannt main.c, get/ine.c und stn1ldex.c. Dann übersetzt das Kommando cc mein.c getline.c strindex.c die drei Dateien, legt die sogenannten Objekt module in den Da!eien main.o, getline.o und strindex.o ab und bindet dann alle drei als aU5führbare Datei namens a.ollt. Wenn zum Beispiel main.c einen Fehler enthält, kann diese. Datei alein nochmals bersetzt werden, und das Resultat kann mit den früher produzierten Objektmodulen mtt folgen- dem Kommando gebunden werden: cc mein.c getline.o strindex.o Nach Konvention unterscheidet ce an Hand der Endungen ...c" und ...0" Quelldateien von Montagecodedateien. Aufgabe 4 -1. Schreiben Sie die Punktion strrindex(s,t) die die erste Position von rechts her liefert, bei der tins vorkommt, oder auch -1, falls mcht. 0 4.2 Funktionen ohne ganzzahliges Resultat Bisher haben unsere Beispielfunktionen entweder keinen Wert (void) oder einen int-Wert geliefert. Was ist, wenn eine Funktion einen anderen Resultattyp liefen .uß? Viele numerische Funktionen wie sqrt, sin und cos liefern double; andere spezlahslerte Funktionen liefern andere Resultattypen. Um zu illustrieren, wie man dies bewerkstel- ligt, wollen wir die Funktion atof(s) schreiben und benutze.n, di die Zeihenkette s in ih,r doppelt genaues Gleitpunktäquivalent umwandelt. atof It eine Erwetterung vn atOl, von der wir verschiedene Versionen in Kapitel 2 und 3 gezeigt haben; atof verarbeitet op- tional ein Vorzeichen und einen Dezimalpunkt; die Ziffernfolgen vor und nach dem De- zimalpunkt können beliebig fehlen oder vorhanden sein. Unsere Vesion ist keine Einga- be-Umwandlungsroutine von hoher Qualität; dies würde mehr Platz In Anspruch nehmn als wir hier aufwenden möchten. Die Standard-Bibliothek enthält eine Funktion atof; sie ist in der Definitionsdatei < stdlib.h > deklariert. Als erstes muß atof seinen Resultattyp selbst deklarieren, da dieser nicht int ist. Der Typname steht vor dem Funktionsnamen: #include <ctype.h> /* atof: Ziffernkette s nach double umwandeln */ double atof(char se]) { double val. power; int i, sign; for (i = 0; isspace(s[i]); i++) /* Zwischenraum uebergehen */ sign'= (s[i] == '-') 1 -1 : 1; 4.2 Funktion( Ime ganzzahliges Resultat 71 if (s[i] == '+' 11 s[i] == '-') i++; for (val = 0.0; isdigit(S[i]); i++) val = 10.0 * val + (s[i] - '0'); if (s[i] == '.') i++; for (power = 1.0; isdigit(s[i]); i++) { val = 10.0 * val + (s[i] - '0'); power *= 10.0; } return sign * val/power; } Als zweites, und dies ist genauso wichtig, muß der Aufrufer ebenfalls wissen, daß atof kein Int-Resultat liefert. Eine Möglichkeit ist, atof explizit in der aufrufenden Funk- tion zu deklarieren. Eine solche Deklaration zeigt der folgende primitive Taschenrech- ner (der gerade zur Überprüfung eines Bankkontos genügt). Er liest pro Zeile eine Zahl, der optional ein Vorzeichen vorausgehen kann, und addiert alle Zahlen auf. Nach jeder Eingabe wird die momentane Summe ausgegeben. #include <stdio.h> #define MAXLINE 100 /* rudimentaerer Taschenrechner */ meinO ( double sum, atof(char []); char Li ne [MAXLI NE] ; int getline(char line[]. int mex); sum = 0; while (getline(line, MAXLINE) > 0) printf(l\tXg\n", sum += atofCl ine»; return 0; } Die Vereinbarung double sumo atof(char []); legt fest, daß sum eine double-Variable ist und daß atof eine Funktion ist, die einen Para- meter vom Typ char[] hat und double als Resultat liefert. Die Funktion atof muß konsistent deklariert und definiert werden. Wenn atof selbst und der Aufruf in main verschiedene Typen in der gleichen Quelldatei haben, wird der Fehler vom Übersetzer erkannt. Wird jedoch (was wahrscheinlicher ist) atof separat übersetzt und in main nicht explizit deklariert, so würde der Unterschied nicht entdeckt werden: atof würde ein double-Resultat liefern, das main dann (ohne Deklaration) als int-Wert behandelt und sinnlose Resultate würden folgen. In Anbetracht dessen, daß wir gesagt haben, daß Deklarationen zu Defmitionen passen müssen, könnte dies überraschend sein. Eine Inkonsistenz kann aber dadurch entstehen, daß ohne einen Funktionsprototyp eine Funktion implizit deklariert wird, wenn sie erstmals in einem Ausdruck vorkommt: sum += atof(line) Wenn ein Name, der bisher noch nicht vereinbart wurde, in einem Ausdruck vorkommt und wenn ihm eine linke Klammer folgt, wird er durch diesen Kontext als Funktionsname .,' f 
72 4 Funktionen . Programmstruktur 4.3 Externe Vari ;n 73 deklariert. Es wird angenommen, daß die Funktion int als Resultat liefert. Keine n- nahmen werden über die Parameter getroffen. Auch wenn eine FunktionsdeklaratIon keine Parameterangaben enthält, wie in double atofO; bedeutet das ebenfalls daß keine Annahmen über die Parameter von atof getroffen wer- den sollen; die Überp;üfung der Parameter wird für atof ganz ausgeschaltet. Diese spe- zielle Bedeutung der leeren Parameterliste soll ermöglichen, daß man ältere C-Program- me mit neuen Übersetzern bearbeiten kann. Aber es ist keine gute Idee für neue Pro- gramme. Wenn eine Funktion Argumente akzeptiert, sollten Parameter deklariert wer- den; wenn sie keine Argumente akzeptiert, benutzt man void. Mit Hilfe von atof, korrekt deklariert, können wir atoi schreiben (also eine Zei- chenkette in int umwandeln): /* atoi: Ziffernkette s nach int umwandeln, mit atof */ int atoi(char se]) ( double atof(char sC]); return (int) atof(s); Wir werden später_sehen, wie man externe Variablen und Funktionen definiert, die nur innerhalb einer einzigen Quelldatei sichtbar sind. Da man auf externe Variablen global zugreifen kann, sind sie eine Alternative zu Funktionsargumenten und Resultatwerten, um Daten zwischen Funktionen zu übertra- gen. Jede Funktion kann auf eine externe Variable zugreifen, indem sie ihren Namen verwendet, wenn dieser Name irgendwie vereinbart wurde. Müssen sehr viele Variablen mehreren Funktionen zur Verfügung stehen, sind ex- terne Variablen bequemer und effizienter als lange Argumentlisten. Wie wir jedoch schon in Kapitel 1 erklärt haben, sollte man dieses Konzept vorsichtig verwenden, denn es kann sich negativ auf die Programmstruktur auswirken und kann zu Programmen mit zu vielen Datenpfaden zwischen Funktionen führen. Externe Variablen sind auch nützlich wegen ihres größeren Gültigkeitsbereichs und längerer Lebensdauer. Automatische Variablen existieren intern in einer Funktion; sic werden erzeugt, wenn die Funktion aufgerufen wird, und verschwinden, wenn die auszu- führende Funktion beendet ist. Externe Variablen sind dagegen permanent, deshalb be- halten sie ihre Werte von einem Funktionsaufruf zum nächsten. Wenn also zwei Funktio- nen Daten gemeinsam benutzen müssen und wenn keine Funktion die andere aufruft, ist es oft am einfachsten, wenn die gemeinsam benutzten Daten in externen Variablen ste- hen und nicht als Argumente herumgereicht werden. Wir wollen dies mit einem größeren Beispiel vertiefen. Wir beschreiben dazu einen Taschenrechner, der die Operatoren +, -, · und / realisiert. Da es einfacher zu implementieren ist, benutzt dieser Taschenrechner umgekehrte polnische Notation und nicht InfIX. (Umgekehrte polnische Notation wird von einigen Taschenrechnern benutzt sowie in Sprachen wie Forth und Postscript.) In umgekehrter polnischer Notation folgt jeder Operator seinen Operanden; ein InfIX-Ausdruck wie (1 - 2) * (4 + 5) } Beachten Sie die Struktur der Vereinbarungen und die return-Anweisung. Der Wert des Ausdrucks bei return expression ; wird in den Resultattyp der Funktion umgewandelt, bevor die Ausführung de Fukti.on beendet wird. Daher wird der Wert von atof, also ein double-Wert, automatisch m mt umgewandelt, wenn dieser Wert bei return auftritt, da die Funktion atoi ein int-Resultat liefert. Diese Operation führt jedoch eventuell zu Informationsverls, deshai? warnen einige Übersetzer davor. Die Umwandlungsoperation (int) sagt expl1Zlt, daß dtese Ope- ration beabsichtigt ist, und unterdrückt jede Warnung. Aufgabe 4 - 2, Erweitern Sie atof so, daß auch Werte der Form 123.45e-6 akzeptiert werden, bei denen einem Gleitpunktwert optional der Buchstabe e oder E und ein Exponent, wieder optional mit Vorzeichen, folgen kann. 0 4.3 Externe Variablen Ein C-Programm besteht aus einer Reihe von externen Objekten, die entweder Va- riablen oder Funktionen sind. Dabei wird das Adjektiv "extern" als Kontrast zu "intern" benutzt. Letzteres beschreibt die Parameter und Variablen, die innerhalb von Funktio- nen defmiert sind. Externe Variablen sind außer halb von allen Funktionen definiert und stehen daher potentiell vielen Funktionen zur Verfügung. Funktionen selst sind imer extern da C nicht erlaubt daß Funktionen innerhalb von anderen Funktionen defimert werde. Nach Voreinstellng haben externe Variablen und Funktionen die Eigenscaft, daß alle Verweise auf sie mit gleichem Namen auch den gleichen Gegenstand bezeich- nen, sogar aus Funktionen heraus, die separat übersetzt wurden. .(Der Standard .nennt diese Eigenschaft exteme Bindung.) In dieser Hinsicht verhalten stch externe Yaflablen wie die COMMoN-Regionen in Fortran oder wie Variablen im äußersten Block m Pascal. wird als 1 2 - 4 5 + * eingegeben. Klammern sind nicht notwendig; die Notation ist eindeutig, wenn wir wis- sen, wieviele Operanden jeder Operator erwartet. Die Implementierung ist einfach. Jeder Operand wird auf einen Stack gebracht; kommt ein Operator an, so wird die entsprechende Anzahl Operanden (nämlich zwei bci binären Operatoren) vom Stack geholt, der Operator wird angewendet, und das Resultat wird wieder auf den Stack gebracht. Im obigen Beispiel werden 1 und 2 auf den Stack gebracht und dann durch ihre Differenz - 1 ersetzt. Anschließend werden 4 und 5 auf den Stack gebracht und dann durch ihre Summe 9 ersetzt. Schließlich ersetzt das Pro- dukt von -1 und 9, nämlich -9, diese zwei Werte auf dem Stack. Das oberste Element auf dem Stack wird entfernt und ausgegeben, wenn das Ende der Eingabezeile erreicht wird. Als Programmstruktur ergibt sich also eine Schleife, die die richtige Operation für jeden Operator und Operanden durchführt, wenn er ankommt: 
74 4 Funktionen Programmstruktur 4.3 Externe Vari ;n 75 wh i I e ( nächsterOperator oder Operand bedeutet nicht Dateiende i f ( Zahl ) auf den Stack else if ( Operator ) Operanden rom Stack holen Operation ausfi1Jmn Resultal auf den Stack else if ( Zeilenende ) Wen rom Stack holen und ausgeben else Fehler Objekte auf den Stack zu bringen (push) und vom Stack zu holen (pop) ist trivial, fügt man jedoch Fehlererkennung und Fehlerbehandlung dazu, so werden diese Opera- tionen aufwendig genug, daß man lieber jede als eigene Funktionen realisiert und nicht den Code überall im Programm dupliziert. Außerdem sollte es eine separate Funktion geben, die den nächsten Operator oder Operanden aus der Eingabe liefert. Bisher noch nicht diskutiert wurde die wesentliche Entwurfsentscheidung: wo be- fmdet sich der Stack, das heißt, welche Routinen greifen direkt auf ihn zu? Eine Mög- lichkeit ist, den Stack'in main zu definieren, und den Stack und die momentane Position auf dem Stack an die Routinen zu übergeben, die den Stack ansprechen. Andrerseits braucht main nichts über die Variablen zu wissen, die den Stack kontrollieren; main soll- te nur push und pop verwenden. Wir haben deshalb den Stack und die zugehörige Infor- mation als externe Variablen realisiert, auf die nur die Funktionen push und pop, nicht aber main, zugreifen können. Diese Skizze nun in ein Programm umzusetzen, ist leicht genug. Wenn wir uns vor- läufig das Programm in einer einzigen Ouelldatei vorstellen, wird sie etwa so aussehen: #includes #defines /* Taschenrechner mit umgekehrter polnischer Notation */ mai nO { Funktionsdeklarotionen für ma i n mai nO ( ... ) externe Variablen für push und pop void push(double f) ( ... ) double pop(void) ( ... ) i nt getop( char s []) ( ... ) von getop aufgerufene Funktionen Später werden wir erklären, wie man das auf zwei oder mehr Ouelldateien verteilen kann. Die Funktion main besteht aus einer Schleife mit einem großen switch, abhängig vom Typ des Operators oder Operanden; diese Verwendung von switch ist wahrschein- lich typischer als das Beispiel in Abschnitt 3.4. #include <stdio.h> #include <stdlib.h> int type; double opZ; char S[MAXOP]; while «type = getop(s» != EOF) { switch (type) ( case NUMBER: push(atof(s»; break; case '+': push(popO + popO); break; ca se -.-: push(popO * popO); break; case '-': opZ = popO; push(pop() - opZ); break; case '/': opZ = pop(); if (opZ 1= 0.0) push(pop() / opZ); else printf(lIerror: zero divisor\n"); break; case '\n': printf("\tX.8g\n". popO); break; default: printf(lIerror: unknown conmand Xs\n". s); break; ) ) return 0; ) Da + und · kommutative Operatoren sind, ist die Reihenfolge irrelevant, in der die vom Stack geholten Operanden kombiniert werden, bei den Operatoren - und / müssen je- doch die rechten und linken Operanden unterschieden werden. Bei push(pop() - pop(»; /* FALSCH */ ist die Reihenfolge undefmiert, in der die zwei Aufrufe von pop ausgeführt werden. Um die richtige Reihenfolge zu garantieren, muß man den ersten Wert an eine temporäre Variable zuweisen, wie wir das in maiD gemacht haben. #define MAXVAl 100 /* maximale Stack-laenge */ #define MAXOP #def i ne NUMBER 100 '0' /* fuer atof() */ /* max. laenge von Operand oder Operator */ /* Anzeige: eine Zahl wurde entdeckt */ int sp = 0; double val [MAXVAl]; /* naechste freie Stack-Position */ /* Stack fuer die Operanden */ int getop(char []); void push(double); double pop(void); 
76 4 Funktionen 1 Programmstruktur 4.3 Externe Varial 77 ) . Was für Funktionen sind getch und ungetch? Oft stellt ein Programm, das eine mgabe verarbeitet, erst dann fest, daß es genügend eingelesen hat, wenn bereits zuviel emgelesen wurde. Ein Beispiel dafür ist das Sammeln einer Ziffernfolge für eine Zahl: Dr Zahlenert ist erst vollständig, wenn das erste Zeichen eingelesen wird, das keine Zfer me 1St. Dann hat aber das Programm bereits ein Zeichen zuviel gelesen, ein Zeichen, mit dem das Programm im Augenblick nichts anfangen kann. Das Problem wäre einfach zu lösen, wenn man die unerwünschten Zeichen auch wieder "aus-lesen" könnte. Immer wenn das Programm ein Zeichen zuviel liest, könnte es dieses Zeichen in die Eingabe zurückstellen, damit der Rest des Programms so tun kann, als ob dieses Zeichen noch nicht gelesen wurde. Erfreulicherweise kann man diese Operation leicht dadurch simulieren, daß man ein Paar kooperierender Funktionen schreibt. getch liefert das nächste Eingabezeichen, das untersucht werden soll; ungetch stellt Zeichen in die Eingabe zurück, die dann die nächsten Aufrufe von getch liefern, be- vor neue Eingabe gelesen wird. Wie diese Funktionen zusammenarbeiten, ist einfach. ungetch speichert die zurückgestellten Zeichen in einem gemeinsamen Puffer, einem Zeichenvektor. getch liest aus diesem Puffer, wenn sich dort Zeichen befmden; getchar wird aufgerufen, wenn der Puffer leer ist. Es muß dann noch eine Indexvariable geben, die die Position des ak- tuellen Zeichens im Puffer angibt. Da der Puffer und die Indexvariable von getch und ungetch gemeinsam benutzt werden und ihre Werte zwischen Aufrufen beibehalten müssen, müssen diese Variablen extern für beide Funktionen sein. Also können wir getch, ungetch und ihre gemeinsamen Variablen folgendermaßen implementieren: #define BUFSIZE 100 char buf[BUFSIZE]; /* Puffer fuer ungetch() */ int bufp. 0; /* naechste freie position in buf */ int getch(void) /* naechstes (eventuell zurueckgestelltes) Zeichen holen */ ( /* push: f auf den Stack bringen */ void push(double f) { if (sp < MAXVAl) val [sP++] = f; else printf("error: stack full. can't push Xg\n". 0; ) /* pop: Wert vom Stack holen und liefern */ double pop(void) { if (sp > 0) return val [--sp]; else ( printf(lIerror: stack ty\n"); return 0.0; ) Eine Variable ist extern, wenn sie außerhalb jeder Funktion definiert wird. Also werden der Stack und der Stack-Index, die push und pop gemeinsam benutzen müssen, außerhalb von diesen Funktionen definiert. main selbst benutzt den Stack oder den Stack-Index nicht - die Repräsentierung kann verborgen werden. Betrachten wir jetzt die Implementierung von getop, die Funktion, die den näch- sten Operator oder Operanden bereitstellt. Die Aufgabe ist einfach. Zwischenräume müssen überlesen werden. Ist dann das nächste Zeichen weder eine Ziffer noch ein De- zimalpunkt, soll es als Operator geliefert werden. Andernfalls muß eine Folge von Zif- fern (unter denen sich ein Dezimalpunkt befinden kann) gesammelt werden, und das Re- sultat ist NUMBER, als Hinweis, daß eine Zahl aufgefunden wurde. #include <ctype.h> int getch(void); void ungetch(int); /* getop: naechsten oparator oder numerischen Operanden holen */ int getop(char s[]) { return (bufp > 0) 7 buf[--bufp] : getchar(); int i, c; ) void ungetch(int c) /* Zeichen zurueckstellen */ { if (bufp >= BUFSIZE) printf(lIungetch: too many characters\n"); else buf[bufp++] = c; while «s[O] = c = getch(» == , , 11 c == '\t') s[1] = '\0'; if (Iisdigit(c) && c != '.') return c; /* keine Zahl */ i = 0; if (isdigit(c» /* ganzzahligen Teil sammeln */ while (isdigit(s[++i] = C = getch(») if (c == '.') /* Oezimalstellen sammeln */ while (isdigit(s[++i] = C = getch(») ) Die Standard-Bibliothek enthält eine Funktion ungetc, die ein Zeichen zurückstellen kann; wir werden sie in Kapitel 7 vorstellen. Wir haben einen Vektor als Puffer benutzt und nicht nur ein einzelnes Zeichen, um einen allgemeineren Ansatz zu zeigen. Aufgabe 4-3. Da der grundsätzliche Rahmen gegeben ist, kann der Taschenrechner leicht erweitert werden. Fügen Sie den Modulo-Operator % und Vorkehrungen für nega- tive Zahlen hinzu. 0 Aufgabe 4-4. Fügen Sie Kommandos hinzu, um das oberste Element des Stacks auszu- geben ohne es zu entfernen, um das oberste Element zu duplizieren und um die obersten beiden Elemente zu vertauschen. Fügen Sie ein Kommando hinzu, das den Stack leert. o s[i] = '\0'; if (c ! = EOF) ungetch(C)i return NUMBER; ) 
78 4 Funktionen I, Programmstruktur 4.4 Regeln zum G ;keitsbereich 79 Aufgabe 4 - S. Fügen Sie Zugriffe auf Bibliotheksfunktionen wie sin, exp und pow hinzu. Siehe < math.b > im Anhang B, Abschnitt 4. 0 Aufgabe 4 - 6. Fügen Sie Kommandos zum Umgang mit Variablen hinzu. (Man kann leicht 26 Variablen mit Einzelbuchstaben als Namen unterstützen.) Fügen Sie eine Va- riable für den zuletzt ausgegebenen Wert hinzu. 0 Aufgabe 4-7. Schreiben Sie eine Funktion ungets(s), die eine ganze Zeichenkette in die Eingabe zurückstellt. Soll ungets über buf und bufp Bescheid wissen oder sollte nur ungetch benutzt werden? 0 Aufgabe 4-8. Nehmen wir an. daß nie mehr als ein Zeichen zurückgestellt wird. Än- dern Sie getch und ungetch entsprechend. 0 Aufgabe 4-9. Unsere Funktionen getch und ungetch behandeln die Zurückstellung von EOF nicht korrekt. Entscheiden Sie, wie diese Funktionen sich verhalten sollen, wenn EOF zurückgestellt wird, und implementieren Sie Ihren Entwurf. 0 Aufgabe 4-10. Ein anderer Entwurf benutzt getline, um eine ganze Eingabezeile zu le- sen; damit werden getch und ungetch unnötig. Ändern Sie den Taschenrechner so, daß er diesen Ansatz benutzt. 0 mai nO < ... } 4.4 Regeln zum Gültigkeitsbereich Die Funktionen und externen Variablen, aus denen ein C-Programm besteht, müs- sen nicht alle gleichzeitig übersetzt werden; der Ouelltext des Programms kann aus ver- schiedenen Dateien bestehen, und früher übersetzte Routinen können aus Bibliotheken dazugebunden werden. Unter anderem interessieren folgende Fragen: · Wie schreibt man Vereinbarungen, damit die Variablen während der Überset- zung korrekt vereinbart werden? · Wie ordnet man die Vereinbarungen an, damit alle Teile beim Laden des Pro- gramms korrekt gebunden werden? · Wie werden Vereinbarungen organisiert, so daß es nur eine Kopie von ihnen gibt? · Wie werden externe Variablen initialisiert? Wir werden diese Punkte diskutieren, indem wir das Taschenrechnerprogramm umorga- nisieren und auf mehrere Ouelldateien verteilen. Aus praktischer Sicht ist der Taschen- rechner zu klein, als daß sich Aufteilen lohnt, aber er illustriert die Probleme sehr schön, die bei größeren Programmen auftreten. Der Gültigkeitsbereich eines Namens ist der Teil des Programms, in dem der Name benutzt werden kann. Für eine automatische Variable, die am Anfang einer Funktion vereinbart ist, ist der Gültigkeitsbereich die Funktion, in der der Name vereinbart ist. Lokale Variablen mit gleichem Namen in verschiedenen Funktionen haben nichts mitein- ander zu tun. Gleiches gilt für die Parameter der Funktion, die im Endeffekt lokale Va- riablen sind. Der Gültigkeitsbereich einer externen Variablen oder Funktion reicht von dem Punkt, wo sie vereinbart wird, bis zum Ende der Datei, die gerade übersetzt wird. Wer- den etwa main, sp, val, pu sb und pop zusammen in einer Datei in der vorher gezeigten Reihenfolge definiert, das heißt int sp = 0; double val [MAXVAL]; void push(double f) < .., } double pop(void) < ... } dann können die Variablen sp und val in push und pop einfach dadurch benutzt werden, daß ihre Namen angegeben werden; keine weiteren Deklarationen sind nötig. Aber we- der sind diese Namen in main sichtbar, noch etwa push und pop selbst. Soll jedoch eine externe Variable verwendet werden, bevor sie defmiert wird, oder wird sie in einer anderen Ouelldatei definiert, als in der, in der sie benutzt wird, dann ist eine extern-Deklaration notwendig. Man muß genau die Deklaration einer externen Variablen von ihrer Definition un- terscheiden. Eine Deklaration legt die Eigenschaften einer Variablen (vor allem ihren Datentyp) fest; eine Defmition sorgt auch noch für Speicherplatz. Erscheinen die Zeilen int sp; double val [MAXVAL]; außerhalb von allen Funktionen, dann definieren sie die externen Variablen sp und val, sorgen für Speicherplatz und dienen außerdem als Deklaration für den Rest dieser Ouelldatei. Andrerseits deklarieren die Zeilen extern int sp; extern double val[]; für den Rest der Ouelldatei, daß sp eine int- Variable und val ein double-Vektor ist (des- sen Größe anderswo festgelegt wird); hier werden jedoch die Variablen nicht erzeugt, und es wird kein Speicherplatz reserviert. In allen Ouelldateien für ein Programm zusammen darf es nur eine Definition für eine externe Variable geben; andere Dateien können extern-Deklarationen enthalten, um auf diese Variable zu verweisen. (In der Datei mit der Definition dürfen auch noch extern-Deklarationen stehen.) Vektorgrößen müssen bei der Definition angegeben wer- den, bei einer extern-Deklaration sind sie optional. Eine externe Variable kann nur in ihrer Definition initialisiert werden. Bei dem vorliegenden Programm ist es unwahrscheinlich, aber die Funktionen push und pop könnten in einer Datei definiert werden, und die Variablen val und sp könnten in einer anderen definiert und initialisiert werden. Dann wären die folgenden Definitionen und Deklarationen notwendig, um sie zu verbinden: in der ersten Datei: extern int sp; extern double val[]; void push(double f) < .., } double pop(void) < ... } in der zweiten Datei: int sp = 0; double val [MAXVAL]; 
4.6 statie 80 4 Funktionen u Programmstruktur 81 Da die extern-Deklarationen in der ersten Datei vor und außerhalb der Funktionsdefmi- tionen liegen, gelten sie deshalb für alle Funktionen gemeinsam; ein Satz Deklaraionen genügt für die ganze Datei. Die gleiche Anordnung würde auch gebraucht, wenn die De- fmitionen von sp und val erst nach deren Benutzung in einer gemeinsamen Datei stehen. stack. c #include <stdio.h> #include "eale.h" #define MAXVAL 100 int sp = Oi double val [MAXVAL] i void push(double f) ( Man muß abwägen zwischen dem Wunsch, daß jede Datei nur Zugriff auf die In- formation hat, die sie für ihre Aufgabe benötigt, und der rauhen Wirklichkeit, daß es schwieriger ist, mehr Defmitionsdateien zu unterhalten. Bis zu einer mittleren Pro- grammgröße ist es vermutlich am besten, eine einzige Definitionsdatei zu haben, die alles enthält, was zwei beliebige Teile des Programms gemeinsam benötigen; das ist die Ent- scheidung, die wir hier getroffen haben. Für ein wesentlich größeres Programm bräuchte man mehr Struktur und mehr Defmitionsdateien. 4.6 static Die Variablen sp und val in staek.c sowie buf und bufp in geteh.c sind zur privaten Benutzung der Funktionen in ihren jeweiligen Quelldateien vorgesehen, und niemand sonst sollte auf sie zugreifen. Eine statie- Vereinbarung, die auf eine externe Variable oder Funktion angewandt wird, begrenzt den Gültigkeitsbereich dieses Objekts auf den Rest der Quelldate die übersetzt wird. statie dient also bei externen Vereinbarungen dazu, Namen wie buC und bufp in der getch-ungetch-Kombination zu verbergen; solche Namen müssen extern sein, damit sie gemeinsam benutzt werden können, sie sollten je- doch für Benutzer von getch und ungeteh unsichtbar sein. Um solche Variablen zu erzeugen, stellt man der normalen Vereinbarung das Wort statie voran. Werden die zwei Routinen und die zwei Variablen in einer Datei übersetzt, statie ehar buf[BUFSIZE] i /* Puffer fue'r ungetehO */ statie int bufp = Oi /* naeehste freie Position in buf */ int geteh(void) ( ... > void ungeteh(int e) { ... > dann kann keine andere Routine buf und bufp benutzen und diese Namen kollidieren nicht mit den gleichen Namen in anderen Quelldateien für das gleiche Programm. Ge- nauso können die Variablen verborgen werden, die push und pop für die Stack- Verarbei- tung benutzen: sp und val werden static vereinbart. Eine externe statie-Vereinbarung wird meistens für Variablen benutzt, aber sie kann genauso gut für Funktionen verwendet werden. Normalerweise sind Funktionsna- men global und für jeden Teil des ganzen Programms sichtbar. Wenn eine Funktion je- doch statie vereinbart wird, ist ihr Name außer halb der Datei unsichtbar in der sie ver- einbart ist. ' Eine statie- Vereinbarung kann auch für interne Variablen verwendet werden. In- terne statie- Variablen sind in einer bestimmten Funktion lokal vorhanden, genauso wie automatische Variablen; anders als automatische Variablen bleiben sie jedoch erhalten und werden nicht für jeden Aufruf der Funktion neu erzeugt. Dies bedeutet, daß interne static- Variablen als private, permanente Speicher innerhalb einer Funktion dienen kön- nen. 4.5 Defmitionsdateien Überlegen wir jetzt, wie man das Taschenrechnerprogramm auf mehrere Quellda- teien verteilt, so wie man das machen würde, wenn jede seiner Komponenten wesentlich größer wäre. Die Funktion main würde in eine Datei kommen, die wir main.e nennen; push, pop und ihre Variablen kommen in eine zweite Datei stack.e; getop kommt in eine dritte getop.e. Schließlich kommen getch und ungetch in eine vierte Datei etch.e;. wir trennen sie von den anderen, denn in einem realistischen Programm würden Sie aus emer getrennt übersetzten Bibliothek stammen. Wir müssen noch etwas überlegen - die Defmitionen und Deklarationen, die die Dateien gemeinsam enthalten. Wir wollen dies so gut wie möglich zentralisieren, so daß es nur ein Exemplar gibt, das man korrekt erzeugen und korrekt halten muß, wenn das Programm weiterentwickelt wird. Dementsprechend schreiben wir dises .gemeinsae Material in eine Definitionsdatei (ein sogenanntes header file), ealc.h, die bei Bedarf em- gefügt wird. (Die #include-Anweisung wird in Abschnitt 4.11 beschrieben.) Das resul- tierende Programm sieht dann etwa so aus: ealc.h #define NUMBER '0' void push(double)i double pop(void)i int getop(ehar [])i int geteh(void)i void ungeteh(int)i main.c gttop.e #include <stdio.h> #include <etype.h> #Include "eale.h" Int getop(ehar sr]) { #include <stdio.h> #include <stdlib.h> #include "eale.h" Idefi ne MAXOP 100 mai nO ( > > gttch.e #Include <stdio.h> #deflne BUFSIZE 100 ehar buf[BUFS!ZE] i Int bufp = Oi int geteh(void) ( > void ungeteh(int e) ( > > double pop(void) { > Aufgabe 4-11. Verändern Sie getop, so daß ungetch nicht benötigt wird. Hinweis: ver- wenden Sie eine interne statie- Variable. 0 4.7 register Eine register-Vereinbarung informiert den Übersetzer, daß die fragliche Variable häufig benutzt werden wird. Eigentlich sollen register-Variablen in den Registern der 
82 4 Funktion md Programmstruktur 4.9 lnitialisierun 83 Maschine unterhalten werden, dies kann zu kleineren und schnelleren Programmen führen. Übersetzer dürfen diesen Hinweis aber ignorieren. Eine register-Vereinbarung hat folgende Form: regi ster i nt x; register ehar e; register kann sich nur auf automatische Variablen und die Parameter einer Funktion be- ziehen. Im letzteren Fall sieht eine Deklaration so aus: f(register unsigned m. register long n) ( int x; int y; f(double x) { double y; register int i; } bezieht sich innerhalb der Funktion r der Name x auf den Parameter, der als double ver- einbart ist; außerhalb von r bezieht sich der Name x auf die extern als Int vereinbarte Va- riable. Gleiches gilt für die Variable y. Es ist guter Stil, wenn man Variablennamen vermeidet, die Namen in einem weiter außen liegenden Gültigkeitsbereich verbergen; das führt sehr leicht zu Durcheinander und Fehlern. 4.9 Initialisierung Initialisierungen wurden bisher nebenbei schon oft erwähnt, aber immer am Rand von anderen Diskussionen. In diesem Abschnitt werden einige der Regeln zusammenge- faßt, nachdem wir jetzt die verschiedenen Speicherklassen kennengelernt haben. Externe und statie- Variablen, die nicht explizit initialisiert werden, haben bei Be- ginn der Programmausführung den Wert 0; automatische und register-Variablen haben undefmierte, das heißt, sinnlose Werte. Einfache Variablen können in einer DefInition initialisiert werden; dabei folgt dem Namen ein Gleichheitszeichen und ein Ausdruck: } In der Praxis gibt es Einschränkungen für register-Variablen, die die Realitäten der Maschine reflektieren. Nur wenige Variablen jeder Funktion könnn in Registern ange- legt werden, und nur bestimmte Datentypen sind daei erla. Uerhlige register- Vereinbarungen sind jedoch harmlos, da das Wort reglstr bel. uberzahhge oder vrbo- tenen Vereinbarungen ignoriert wird. Man kann auch nIcht dte Adresse eler .reglster- Variablen berechnen (dieser Komplex wird in Kapitel 5 behandelt), unabhanglg davon, ob eine Variable wirklich in einem Register angelegt ist. Die speziellen Einschränkungen bezüglich Anzahl und Typen von register-Variablen sind von Maschine zu Maschine ver- schieden. 4.8 Blockstruktur C hat keine Blockstruktur im Sinne von Pascal oder ähnlichen Sprachen, da Fun.k- tionen nicht innerhalb von anderen Funktionen defmiert werden dürfen. Andrerseits können Variablen wie bei einer Blockstruktur innerhalb einer Funktion defIniert werden. Unmittelbar nach der linken geschweiften Klammer am Anfang eines Blocks können Va- riablen vereinbart und initialisiert werden. Dies gilt für jeden Block, nicht nur für den äußersten Block, aus dem jeweils eine Funktion besteht. Derartig vereibart ariable.n verbergen alle Variablen gleichen Namens aus äußere? löcke, nd se eXlstteren bIs zur entsprechenden rechten geschweiften Klammer. BeIspIelsweIse ISt bel i f (n > 0) { int i; /* neues "i" definieren */ for (i = 0; i < n; i++) } der Gültigkeitsbereich der Variablen I der "wahre" Teil der r-Anweisung; d.ieses I ist u- abhängig von allen anderen Variablen nmens I ae.rhl.b dles.es Blcks: Eme auoat- sche Variable die in einem Block defImert und tnlttahslert wIrd, WIrd Jedesmal Imttah- siert wenn de Block neu ausgeführt wird. Eine staUc- Variable wird nur bei der ersten Ausführung des Blocks initialisiert. Automatische Variablen, wie auch formale Parameter, verbergen externe Variablen und Funktionen mit gleichem Namen. Nach den folgenden Vereinbarungen int x = 1; ehar squote = '\"; long day = 1000L * 60L * 60L * 24L; /* Millisekunden/Tag */ Für externe und staUe-Variablen muß die lnitialisierung ein konstanter Ausdruck sein; sie werden einmal initialisiert, im Prinzip bevor das Programm abläuft. Automatische und register-Variablen werden jedesmal initialisiert, wenn die Funktion oder der Block neu ausgeführt werden. Bei automatischen und register-Variablen braucht die lnitialisierung keine Kon- stante zu sein: hier kann ein beliebiger Ausdruck angegeben werden, der sich nur auf früher definierte Werte bezieht, und sogar Funktionsaufrufe sind möglich. Beispielsweise könnten die Initialisierungen im binären Suchprogramm im Abschnitt 3.3 so formuliert werden int binseareh(int x, int v[], int n) { int low = 0; int high = n - 1; int mid; } anstelle von int low, high, mid; low = 0; high = n - 1; 
84 4 Funktione ld Programmstruktur 4.10 Rekursion 85 Die lnitialisierungen von automatischen Variablen sind effektiv nur eine Abkürzung für Zuweisungsanweisungen. Was man bevorzugt, ist hauptsächlich eine Geschmacksfrage. Wir haben im allgemeinen explizite Zuweisungen verwendet, da Initialisierungen bei De- fmitionen schwerer erkennbar und weiter weg von dem Punkt sind, wo sie benutzt wer- den. if (n / 10) printd(n / 10); putchar(n X 10 + '0'); Hier ist die Vektorgröße fünf (vier Zeichen und das abschließende '\0'). 4.10 Rekursion C-Funktionen können rekursiv benutzt werden; das heißt, eine Funktion kann sich selbst entweder direkt oder indirekt aufrufen. Betrachten wir das Ausgeben einer Zahl als Zeichenkette. Wie wir früher erwähnt haben, werden die Ziffern in der verkehrten Reihenfolge erzeugt: die Ziffern am rechten Ende kennt man vor den Ziffern am linken Ende, aber sie müssen genau umgekehrt ausgegeben werden. Es gibt zwei Lösungen für dieses Problem. Einmal kann man die Ziffern in der Reihenfolge in einem Vektor speichern, in der sie generiert werden, und sie dann in um- gekehrter Reihenfolge ausgeben, wie wir dies im Abschnitt 3.6 bei itoa getan haben. Die Alternative dazu ist eine rekursive Lösung, bei der printd zuerst sich selbst aufruft, um alle führenden Ziffern abarbeiten zu lassen, und dann selbst die nachfolgende Ziffer aus- gibt. Auch diese Version kann allerdings bei der größten negativen Zahl schiefgehen. #include <stdio.h> /* printd: n dezimal ausgeben */ void printd(int n) ( ) Ruft eine Funktion sich selbst rekursiv auf, dann erhält jede Aktivierung einen neu- en Satz aller ihrer automatischen Variablen, unabhängig vom vorhergehenden Satz. Bei printd(I23) erhält die erste Aktivierung von printd das Argument n = 123. Sie übergibt 12 an einen zweiten Aufruf von printd, der seinerseits 1 an einen dritten übergibt. Die dritte Aktivierung von printd gibt 1 aus und kehrt zum zweiten Aufruf zurück. Dieses printd gibt 2 aus und kehrt zum ersten Aufruf zurück. Dieser gibt 3 aus und hört auf. Ein weiteres gutes Beispiel zur Rekursion ist Quicksort, ein Sortieralgorithmus, der 1962 von C. A. R. Hoare entwickelt wurde. Aus einem vorgegebenen Vektor wird ein Element ausgewählt und die anderen werden in zwei Untermengen aufgeteilt - eine Menge mit Elementen, die kleiner als das ausgewählte Element sind, und eine mit Ele- menten, die größer oder gleich sind. Das gleiche Verfahren wird dann rekursiv auf die beiden Untermengen angewandt. Hat eine Untermenge weniger als zwei Elemente muß sie nicht mehr sortiert werden; dadurch hört die Rekursion auf. ' Unsere Version von Quicksort ist nicht die schnellstmögliche, aber sie eine der ein- fachsten. Wir benutzen das mittlere Element jedes Teilvektors zur Auf teilung. /* qsort: sortiere v[left]...v[right] in aufsteigende Reihenfolge */ void qsort(int v[], int left, int right) ( int i, last; void swap(int v[], int i, int j); if (Ieft >= right) /* nichts zu tun, wenn der Vektor */ return; /* weniger als zwei Elemente enthaelt */ swap(v, left. (Ieft + right)/2); /* bewege gewaehltes Element */ last = left; /* nach v[O] */ for (i = left+1; i <= right; i++) /* aufteilen */ if (v[i] < v[left]) swap(v, Hlast, 0; swap(v, left, last); /* hole gewaehltes Element zurueck */ qsort(v, left, last-1); qsort(v, last+1, right); Ein Vektor kann dadurch initialisiert werden, daß seiner Defmition eine Liste von lnitialisierungswerten folgt, die mit geschweiften Klammern umgeben und durch Komma getrennt ist. Um zum Beispiel einen Vektor days mit der Anzahl Tage jedes Monats zu initialisieren, schreibt man: int days[] = ( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ); Ist die Größe eines Vektors nicht angegeben, so berechnet sie der Übersetzer, indem er die lnitialisierungen zählt, hier sind das 12. Werden weniger lnitialisierungswerte angegeben als für einen Vektor nötig sind, so werden die restlichen Elemente bei externen, statischen und automatischen Vektoren auf o gesetzt. Zu viele lnitialis\erungen sind ein Fehler. Leider kann man nicht angeben, daß ein lnitialisierungswert wiederholt verwendet werden soll; auch kann ein Element in der Mitte eines Vektors nicht initialisiert werden, ohne daß alle vorhergehenden Werte auch angegeben werden. für Zeichenvektoren gibt es eine besondere lnitialisierung; staU der geschweiften Klammern und Kommas kann eine konstante Zeichenkette angegeben werden: char pattern[] = "ould"; ist eine Abkürzung für die längere, aber äquivalente Formulierung char pattern[] = ( '0', 'u', 'I', 'd', '\0 ' ); ) Wir habe die Tauschoperation in einer eigenen Funktion swap untergebracht, da sie in qsort drelDlal vorkommt. /* swap: vertausche v[i] und v[j] */ void swap(int v[], int i, int j) ( int t; t = v[i]; v[i] = v[j]; v[j] = t; i f (n < 0) ( putchar('-'); ) Die .Standard-Bibliothek enthält eine Version von qsort, die Objekte mit beliebigem Typ sortieren kann. n = -n; ) 
86 4 Funktionen l Programmstruktur 4.11 Der C-Preproz. ,r 87 Rekursion spart vielleicht keinen Speicherplatz, da es irgendwo einen Stack für die bearbeiteten Werte geben muß. Rekursion ist auch nicht schneller. Aber eine rekursive Lösung ist kompakter und oft wesentlich leichter zu schreiben und zu verstehen als eine nicht-rekursive Lösung. Rekursion ist besonders bequem bei rekursiv defInierten Daten- strukturen wie Bäumen; wir werden in Abschnitt 6.5 ein gutes Beispiel dazu sehen. Aufgabe 4-12. Verwenden Sie die Ideen von printd dazu, eine rekursive Version von itoa ZU schreiben; das heißt, verwandeln Sie einen ganzzahligen Wert in eine Zeichenket- te mit Hilfe einer rekursiven Funktion. 0 Aufgabe 4-13. Schreiben Sie eine rekursive Version der Funktion reverse(s), die die Zeichenkette samgleichen Platz umkehrt. 0 4.11 Der C-Preprozessor C realisiert bestimmte Sprachkonzepte mit einem Preprozessor, den man sich als selbständigen ersten Schritt der Übersetzung vorstellen kann. Zwei Eigenschaf.en wer- den am häufigsten benutzt: #include, um den Inhalt einer Datei während der Uberset- zung einzufügen, und #define, um einen Namen durch eine beliebige Folge von Symbo- len zu ersetzen. Weitere Fähigkeiten, die in diesem Abschnitt beschrieben werden, sind bedingte Übersetzung und Makros mit Argumenten. 4,11.1 Definitionsdateien einfügen Durch Einfügen von Dateien kann man leicht mit Sammlungen von #define- Anweisungen und Deklarationen (und anderen Dingen) umgehen. Eine Quellzeile wie #inchJde "fi/ename" . Wir nennen diese dann eine Delinitionsdatei. A.d.Ü. 4.11.2 Textersatz Eine DefInition hat die Form #def i ne name ersatzJext Si.e sorgt für die einfachste Art von Textersatz - tritt anschließend das Symbol name auf, ,?"d es durch ersatzlext ersetzt. Der name bei #define hat die gleiche Form wie ein Va- nablenname; der Ersatztext ist beliebig. Normalerweise ist der Ersatztext der Rest der Zeile; eine lange DefInition kann über mehrere Zeilen fortgesetzt werden, indem man \ an das Ende jeder Zeile stellt, die fortgesetzt werden soll. Der Gültigkeitsbereich eines mit #define vereinbarten Namens erstreckt sich von der Defmition bis zum Ende der Quelldatei. Eine Defmition kann frühere DefInitionen verwenden. Textersatz findet nur für Namen und nicht innerhalb von Zeichenketten statt; ist also etwa YES ein definierter Name, dann wird innerhalb von printf("YES") oder in YESMAN nicht ersetzt. Jeder Name kann mit beliebigem Ersatztext defmiert werden. Zum Beispiel #define forever for (;;) 1* Endlosschleife */ defIniert ein neues Wort forever für eine endlose Schleife. Man kann auch Makros mit Parametern definieren, so daß der Ersatztext bei ver- schiedenen Aufrufen des Makros verschieden sein kann. Beispielsweise kann man einen Makro max folgendermaßen definieren: #define max(A. 8) «A) > (8) ? (A) : (8» Auch wenn er wie ein Funktionsaufruf aussieht, wird ein Aufruf von max direkt im Pro- ammtext expandiert. Überall wo ein Parameter im ersatz/ext steht (hier A oder B), WIrd er durch das entsprechende Argument ersetzt. Also wird die Zeile x = mex(p+q, r+s); ersetzt durch die Zeile x = «P+q) > (r+s) ? (P+q) : (r+s»; Werden die Parameter konsis"tent behandelt, dann funktioniert dieser Makro für beliebi- ge Datentypen; anders als bei Funktionen genügt eine einzige Defmition von max für ver- schiedene Datentypen. Studiert man die Expansion von max genauer, entdeckt man einige Fallstricke. Die Ausdrücke werden zweitpal bewertet; das ist ungünstig, wenn sie Nebenwirkungen wie Inkrement-Operatoren oder Eingabe und Ausgabe enthalten. Zum Beispiel wird bei max(i++, j++) 1* FALSCH */ der größere Wert zweimal inkrementiert. Der Ersatztext muß sorgfältig geklammert sein, damit die Reihenfolge von Bewertungen erhalten bleibt. Überlegen Sie was pas- siert, wenn der Makro #define squere(x) x * x /* FALSCH */ mit square(z+ 1) aufgerufen wird. Makros sind trotzdem wertvoll. Ein praktisches Beispiel stammt aus < stdio.h >, wo getchar und putchar oft als Makros definiert sind, um zu vermeiden, daß für jedes arbeitete Zeichen ein Funktionsaufruf erfolgen muß. Die Funktionen in < ctype.h > smd normalerweise auch als Makros implementiert. oder #include <fi/ename> wird durch den Inhalt der Dateifilename ersetzt.. Wennfilename in Doppelanführungs- zeichen steht, beginnt die Suche nach der Datei typischerweise dort, wo das Quellpro- gramm steht; wenn sie dort nicht gefunden wird, oder wenn filename in spitzen Klam- mern < > angegeben ist, geht die Suche nach implementierungsabhängigen Regeln wei- ter. Eine eingefügte Datei kann selbst auch #include's enthalten. Oft schreibt man mehrere #include-Zeilen am Anfang einer Quelldatei, um ge- meinsame #define-Anweisungen und extern-Deklarationen einzufügen oder um auf Funktionsprototypen von Bibliotheksfunktionen zuzugreifen, die in Defmitionsdateien wie < stdio.h > stehen. (Genau genommen müssen das keine Dateien sein; die Details, wie auf DefInitionsdateien zugegriffen wird, sind implementierungsabhängig.) Mit #include werden bevorzugt die Deklarationen eines großen Programms zu- sammengehalten. Damit ist garantiert, daß alle Quelldateien mit den gleichen DefInitio- nen und Variablendeklarationen arbeiten; so wird eine besonders unangenehme Fehler- quelle vermieden. Wenn eine eingefügte Datei verändert wird, müssen natürlich alle Da- teien, die davon abhängen, neu übersetzt werden. 
88 4 Punktionen l Programmstruktur 4.11 Der C-Preproz Jr 89 printf("x/y" " = Xg\n". x/y); expandiert und die Zeichenketten werden aneinandergehängt; also ist der Effekt pri ntf( "x/y = Xg\n". x/y); Im Argument wird " durch \" und \ durch \ \ ersetzt, so daß das Resultat eine gültige konstante Zeichenkette ist. Mit dem Preprozessor-Operator ## kann man Argumente während der Makro- Expansion aneinanderhängen. Steht ein Parameter im Ersatztext neben ##, wird der Parameter durch das Argument ersetzt, ## und der umgebende Zwischenraum werden entfernt, und das Ergebnis wird nochmals (auf Makroaufrufe) untersucht. Zum Beispiel verkettet der Makro paste seine beiden Argumente: #define paste(front. beck.) front ## back paste(name, 1) erzeugt das Symbol namel. Die Regeln für verschachtelte ## sind mysteriös; die Details kann man in Anhang A finden. Aufgabe 4-14. Definieren Sie einen Makro swap(t,x,y), der zwei Argumente vom Typ t vertauscht. (Blockstruktur sollte sich hilfreich erweisen.) 0 4.11.3 Bedingte Übersetzung Der Preprozessor selbst kann mit bedingten Anweisungen kontrolliert werden, die während seiner Ausführung bewertet werden. Damit kann man Text abhängig vom Wert von Bedingungen einfügen, die während der Übersetzung bewertet werden. Die #if-Anweisung berechnet einen konstanten ganzzahligen Ausdruck, (in dem sizeof, Umwandlungsoperationen oder Aufzählungskonstanten nicht vorkommen dür- fen). Ist der Wert des Ausdrucks nicht null, werden die folgenden Zeilen bis #endif, #elif oder #else eingefügt. (Die #eUr-Anweisung des Preprozessors funktioniert wie else If.) Der Ausdruck defined(name) in einer #if-Anweisung liefert 1, wenn name mit dem Preprozessor definiert wurde, und sonst O. Um zum Beispiel sicherzustellen, daß der Inhalt der Datei hdr.h nur einmal ein- gefügt wird, wird der Inhalt der Datei mit einer Bedingung umgeben: #if Idefined(KOR) #deHne KOR /* Inhalt von hdr.h */ #endif Beim ersten Einfügen von hdr.h wird der Name HDR definiert; wird die Datei nochmals eingefügt, ist HDR defmiert und bis #endif wird alles übersprungen. Ähnlich kann man vermeiden, daß man Dateien überhaupt mehrfach einfügt. Wird dieser Stil konsistent verwendet, dann kann jede Defmitionsdatei selbst die Defmitionsdateien einfügen, von denen sie abhängt, ohne daß sich der Benutzer einer Defmitionsdatei um solche Abhän- gigkeiten kümmern muß. Der folgende Code überprüft den Namen SYSTEM, um zu entscheiden, welche Version einer Defmitionsdatei einzufügen ist: #if SYSTEM == SYSV #define KOR "sysv.h" #elif SYSTEM == BSO #define KOR "bsd.h" #elif SYSTEM == MSOOS #define KOR "msdos.h" #else #define KOR "default.h" #endif #inchJde KOR Die Zeilen #ifder und #Irndef dienen speziell zur Überprüfung, ob ein Name defi- niert ist. Unser erstes Beispiel mit #ifhätte auch so geschrieben werden können: #ifndef KOR #define KOR /* Inhalt von hdr.h */ #endif Definitionen können mit #undef gelöscht werden, normalerweise um sicherzustel- len, daß eine Routine wirklich eine Funktion und kein Makro ist. #undef getchar int getchar(void) { ... } Parameter werden innerhalb von konstanten Zeichenketten nicht ersetzt. Wenn je- doch # im Ersatztext vor dem Parameternamen steht, wird beides zusammen als kon- stante Zeichenkette mit Doppelanführungszeichen expandiert, wobei der Parameter durch das Argument ersetzt wird. Dies kann man mit Verkettung von Zeichenketten kombinieren, um zum Beispiel einen Makro zur Fehlerverfolgung zu konstruieren: #define dprint(expr) printf(#expr" = Xg\n". expr) Ruft man dies zum Beispiel so auf dprint(x/y); wird der Makro zu 
91 5 Zeiger und Vektoren Ein Zeiger ist eine Variable, die die Adresse einer Variablen enthält. Zeiger wer- den in C häufig benutzt: zum Teil, weil sie manchmal die einzige Möglichkeit sind, um eine Berechnung auszudrücken, zum Teil, weil sie normalerweise zu kompakteren und efflzienteren Programmen führen als andere Methoden. Zeiger und Vektoren sind eng verwandt; dieses Kapitel untersucht auch diese Verwandtschaft und zeigt, wie man Vor- teile daraus zieht. Zeiger wurden mit der goto-Anweisung in einen Topf geworfen, als eine ausge- zeichnete Technik, um unverständliche Programme zu formulieren. Dies gilt sicher, wenn Zeiger sorglos verwendet werden, und man kann leicht Zeigerwerte erzeugen, die ..irgendwohin" zeigen. Mit einer gewissen Disziplin können Zeiger aber auch benutzt werden, um klar und einfach zu programmieren. Genau diesen Aspekt wollen wir im fol- genden herausarbeiten. Als hauptsächliche Neuerung legt ANSI-C die Regeln explizit fest, wie Zeiger mani- puliert werden können; letztlich wird vorgeschrieben, was gute Programmierer schon im- mer praktizieren und was gute Übersetzer schon immer erzwingen. Außerdem ersetzt der Datentyp void * (Zeiger auf void) den Typ char * als korrekten Typ für unspezifische (generische) Zeiger. 5.1 Zeiger und Adressen Beginnen wir mit einem vereinfachten Bild der Speicherorganisation. Eine typi- sche Maschine hat einen Vektor von Speicherzellen, die fortlaufend numeriert oder adressierbar sind und die einzeln oder in zusammenhängenden Gruppen bearbeitet wer- den können. Bei einer üblichen Architektur kann jedes Byte einen char- Wert darstellen, ein Paar von Ein-Byte-Zellen kann als short behandelt werden, und vier benachbarte Bytes sind long. Ein Zeiger ist eine Gruppe von Speicherzellen (oft zwei oder vier), die eine Adresse aufnehmen kann. Hat also c den Typ char und p ist ein Zeiger, der darauf verweist, dann könnten wir das folgendermaßen darstellen: P:'' I ... Der unäre Adreß-Operator & liefert die Adresse eines Objekts. Die Anweisung p = &c; weist also die Adresse von c an die Variable p zu, und man sagt ..p zeigt auf c", Dcr Adreß-Operator & kann nur auf Objekte im Speicher angewendet werden, also auf Va- riablen und Vektorelemente. Auf Ausdrücke, Konstanten oder register- Variablcn kann er nicht angewandt werden. Der unäre Operator * ist der I/llra/ts-Operator (indirection, derefere/lcing). Wird er auf cinen Zeiger angewendet, so greift er auf das Objekt zu, auf das der Zeiger verweist. Angenommen, x und y sind ganzzahlig und ip ist ein Zeiger auf int; der folgende, fiktive 
92 :eiger und Vektoren 5.2 Zeiger und Fl ionsargumente 93 Programmtext zeigt, wie man einen Zeiger vereinbart und wie die Operatoren & und * benutzt werden: int x : 1, y: 2, z[10]; int *ip; /* ip ist ein Zeiger auf int */ ip : &x; /* ip zeigt nun auf x */ y : *ip; /* y ist nun 1 */ *ip : 0; /* x ist nun 0 */ ip: &z[O]; /* ip zeigt nun auf z[O] */ Die Definitionen von x, y und z haben wir so schon bisher gesehen. Die Definition des Zeigers ip, int *ip; soll als Muster verstanden werden; sie besagt, daß der Ausdruck *ip ein Int-Wert ist. Die Syntax einer Variablenvereinbarung imitiert die Syntax von Ausdrücken, in denen die Va- riable auftreten könnte. Dieser Gedanke gilt auch für Funktionsvereinbarungen. Bei- spielsweise besagt double *dp. atof(char *); daß in einem Ausdruck *dp und ator(s) Werte vom Datentyp double haben, und daß das Argument von ator ein Zeiger auf char ist. Beachten Sie auch, daß daraus folgt, daß ein Zeiger jeweils nur auf eine bestimmte Art von Objekt zeigen darf: jeder Zeiger zeigt auf einen festgelegten Datentyp. (Es gibt eine einzige Ausnahme: ein ..Zeiger auf void" wird benutzt, um einen Zeiger beliebigen Typs aufzunehmen, aber er darf nicht selbst zum Zugriff verwendet werden. Wir werdcn in Abschnitt 5.11 darauf zurückkommen.) Zeigt Ip auf die int- Variable x, dann darf *ip überall stehen, wo x stehen dürfte: *ip : *ip + 10; erhöht *ip (also x) um 10. Die unären Operatoren * und & haben höheren Vorrang als arithmetische Opera- toren, also holt die Zuweisung y : *ip + 1 den Wcrt, auf den ip zeigt, addiert 1 und weist das Resultat an y zu; ebcnso inkrcmenticrt *ip +: 1 den Wcrt, auf den ip zeigt, wie das auch ++*ip S.2 Zeiger und Funktionsargumente Da C an Funktionen die Werte von Argumenten übergibt, kann die aufgcrufene Funktion Variablen beim Aufrufer nicht direkt ändern. Ein Sortierprogramm könnte zum Beispiel zwei Elemente mit einer Funktion namens swap tauschen und in die richti- ge Reihenfolge bringen wollcn. Dann reicht aber der Aufruf swap(a. b); nicht, wenn die swap-Funktion folgendermaßen definiert ist: void swap(int x. int y) /* FALSCH */ ( i nt t; t : x; x : y; y = t; ) Da nur Argumentwerte übergeben werden, kann swap die Argumente a und b beim Auf- rufer nicht beeinflussen. Die obige Funktion vertauscht nur Kopien von a und b. Um den gewünschten Effekt zu erzielen, muß der Aufrufer Zeiger übergeben, die auf die Werte zeigen, die geändert werden sollen: swap(&a. &b); Da der Operator & die Adresse einer Variablen liefert, ist &a ein Zeiger auf a. In swap werden die Parameter als Zeiger deklariert, und auf die Operanden wird indirekt mit die- sen Zeigen zugegriffen. void swap(int *PX. int *py) /* *px und *py austauschen */ ( int t; t = *px; *px = *py; *py = t; ) Als Bild: beim Aufrufer: und a:D b:D (*ip)++ tun. Im letzten Beispiel sind die Klammcrn notwendig; ohne sie würde dcr Ausdruck dcn Zeiger ip inkrementicren und nicht das Objekt, auf das ip zcigt, da unäre Operatoren wie * und ++ von rechts nach links zusammengefaßt werdcn. Schließlich sind Zeiger auch Variablen und können dirckt als solche benutzt wcr- den. Ist zum Beispiel iq ein wcitercr Zeiger auf int, dann kopiert iq: ip den Inhalt von Ip nach iq, womit dann iq auf das gleiche Objekt wie Ip zcigt. in swap: 
94 "eiger und Vektoren 5.3 Zeiger und' oren Die folgende Schleife füllt einen int- Vektor durch Aufrufe von getint: int n. array[SIZE]. getint(int *); for (n = 0; n< SIZE && getint(&array[n]) 1= EOF; n++) 95 Überall in getint wird · aJ I ' . schnitt 43 b hi b pn s n.orma e Ißt-Variable benutzt. Wir haben auch die in Ab- das wir ;uvises:: :eut:ne g d tch E nd unget.h benutzt, damit das eine Zeichen, , e er m le mgabe zuruckgestellt werden kann. eaf:e lo ::Se elti.nt geschiebe? ist, behandelt die Funktion ein + oder -, dem solches Zeichen .' d . g E u. tIge b ReprsentIerung on Null. Korrigieren Sie das so, daß ein In le mga e zuruckgestellt WIrd. 0 :::;arO:::ien  gtOoat, eine Fuktion analog zu getint, die Gleitpunkt- . as ur emen Datentyp liefert getOoat als Funktionswert? 0 5.3 Zeiger und Vektoren In. C besteht eine enge Beziehung zwischen Zeigern und Vektoren so en ::nk::g::cn.ekt.oren gemein.sam behandeln sollte. Jede Operation mit Vekt;i zienter aber zUine;. rm.uhert  h erden. Die Zeigerversion ist im allgemeinen efli- , ur nemgewel te etwas schwerer zu verstehen. Die' Vereinbarung int a[10]; defIniert einen Vektor a mit zeh EI t d h' . derfol g enden Obi ekt . t d N n emen en, as elßt, emen Block aus zehn aufeinan- J en,ml en amena[OJ,a[lJ,...,a[9]. Mit Zeigern als Argumenten kann eine Funktion auf Objekte in der aufrufenden Funktion zugreifen und sie ändern. Betrachten wir zum Beispiel eine Funktion getint, die bei jedem Aufruf einen ganzzahligen Wert liefert, den sie durch Zerlegen einer frei formatierten Eingabe gewinnt. getint muß sowohl den gefundenen Wert liefern als auch das Dateiende anzeigen, wenn keine Eingabedaten mehr zur Verfügung stehen. Diese Werte müssen auf verschiedenen Wegen zurückgeliefert werden, denn unabhängig davon, wie man EOF definiert, könnte dieser Wert auch eine Eingabezahl sein. Bei einer möglichen Lösung liefert getint eine Dateiende-Anzeige als Funktions- wert und verwendet ein Zeigerargument, um die umgewandelte Zahl bei der aufrufenden Funktion abzuspeichern. Dieses Schema wird auch von scanr verwendet; siehe Abschnitt 7.4. Jeder Aufruf speichert in array[n] die nächste Zahl aus der Eingabe; dann wird n inkre- mentiert. Man beachte, daß man unbedingt die Adresse von array[n] an getint überge- ben muß. Andernfalls hat getint keine Möglichkeit, dem Aufrufer die umgewandelte Zahl zurückzugeben. Unsere Version von getint liefert EOF am Dateiende, Null, wenn in der Eingabe keine Zahl ansteht, und einen positiven Wert, wenn in der Eingabe eine gültige Zahl vor- kommt. a: #include <ctype.h> int getch(void); void ungetch(int); /* getint: naechsten ganzzahligen Wert aus der Eingabe holen und in *pn ablegen */ int getint(int *pn) { a[9] n[2:eichnet das He Element im Vektor a. Vereinbart man pa als Zeiger auf einen , int c. sign; int *pa;  dann zeigt pa durch die Zuweisung pa = &a[O]; auf das Element 0 von a; das heißt, pa enthält die Adresse von a[O]. while (isspace(c = getch(») /* Zwischenraum ignorieren */ if (Iisdigit(c) && c 1= EOF && c 1= '+' && c 1= '-') ( ungetch(c); /* es ist keine Zahl */ return 0; a: ) si gn = (c == '-') ? -1 : 1; if (c == '+' 11 c == '-') c = getchO; for (*pn = 0; isdigit(c); c = getch(» *pn = 10 * *pn + (c - '0'); *pn *= sign; if (c ! = EOF> ungetch(c); return c; Die Zuweisung x = *pa; kopiert dann den Wert von a[OJ nach x. Zeigt pa auf ein bestimmtes Element eines Vektors, dann zeigt per S rachdetiniti-  a+l au das nachfolgende Element, und allgemein zeigt pa+i auf das-te Element 7c;:: un pa-i auf das i-te Element davor. Wenn also pa auf a[OJ zeigt, dann be- *(pa+1) ) 
96 5 iger und Vektoren den Inhalt von a[I], die Adresse des Elements a[l] ist pa+1 und *(pa+l) ist der Wert des Elements a[I]. a: a[O] Dies gilt unabhängig vom Datentyp oder der Größe der Elemente im Vektor a. Die Bedeutung von "addiere 1 zu einem Zeiger", und als Verallgemeinerung die gesamte Arithmetik mit Zeigern, ist so defIniert, daß pa+l auf das nächste Objekt zeigt und daß pa+1 auf das I-te Objekt nach pa zeigt. Es besteht also ein sehr enger Zusammenhang zwischen Vektorindizes und Zeiger- arithmetik. Nach SprachdefInition ist der Wert einer Variablen oder eines Ausdrucks vom Typ Vektor die Adresse des Elements 0 (des Anfangselements) des Vektors. Des- halb sind nach der Zuweisung pa = &a[O]; die Werte von pa und a identisch. Da der Name eines Vektors synonym zur Adresse des Anfangselements ist, kann man die Zuweisung pa=&a[O] auch so formulieren: pa = a; Wesentlich überraschender, wenigstens auf den ersten Blick, ist die Tatsache, daß statt a(i] auch *(a+l) geschrieben werden kann. Der C-Übersetzer wandelt a[i] sofort in *(a+l) um; die zwei Angaben sind äquivalent. Wendet man den Adreß-Operator & auf beide Teile dieser Äquivalenz an, so sieht man, daß &a [I] und a + 1 ebenfalls identisch sind: a+1 ist die Adresse des I-ten Elements nach a. Die Kehrseite der Medaille ist, daß man Zeiger wie pa in Ausdrücken zusammen mit einem Index verwenden kann: pa(i] ist äquivalent zu *(pa+I). Kurz - ein Ausdruck aus Vektornamen und Index ist äquivalent zu einem Ausdruck aus Zeiger und Abstand. Einen Unterschied zwischen Vektornamen und Zeiger muß man sich allerdings merken. Ein Zeiger ist eine Variable, also sind pa = a und pa++ erlaubt. Ein Vektorna- me ist jedoch keine Variable: Ausdrücke wie a = pa oder a++ sind nicht erlaubt. Wird ein Vektorname an eine Funktion übergeben, so wird in Wirklichkeit die Adresse des Anfangselements übergeben. Innerhalb der aufgerufenen Funktion ist die- ses Argument eine lokale Variable und folglich ist ein Vektorname als Parameter ein Zei- ger, das heißt, eine Variable, die eine Adresse enthält. Damit können wir eine andere Version der Funktion strlen schreiben, die die Länge einer Zeichenkette berechnet: /* strlen: Laenge der Zeichenkette s */ int strlen(char *s) { int n; for (n = 0; *s 1= '\0'; s++) n++i return n; ) 5.4 Adreß-Arithm 97 Da seine. Zeigrvariable t, darf man s natürlich inkrementieren. s++ hat keinen Ein- fluß auf .die Zel(:enkette m der Funktion, von der strlen aufgerufen wurde, sondern in- :reentiert nur die lokale Kopie des Zeigers innerhalb von strlen. Das heißt, daß Aufru- le WIe strlen("hello, world")j strlen(array); strlen(ptr); alle funktionieren. Als Parameter bei der DefInition einer Funktion sind char s[]; /* konstante Zeichenkette */ /* char array[100]; */ /* char *ptrj */ und char *s; äquivalnt; w?" zie.hen di zw:ite Schreibweise vor, da sie besser klarmacht, daß der Para- etr em Ze1ge 1St. Wird em Vektorname an eine Funktion übergeben, kann die Funk- tIon Je nach Belieben annehmen, daß ein Vektor oder ein Zeiger übergeben wurde und den Parameter entprechend verwenden. Eine Funktion kann sogar beide Schreibwisen verwenden, wenn dies zweckmäßig und klar scheint. . M kann auch einen Teil eine Vektors an eine Funktion übergeben, indem man emen .Zelger auf den Anfang des Teilvektors übergibt. Ist beispielsweise a ein Vektor dann liefern sowohl ' f(&a [2] ) als auch f(a+2) an die Funktion r die Adresse des Teilvektors, der mit dem Element a[2] beginnt. Inner- halb von r kann man als Parameterdeklaration sowohl feint arrU) ( ... ) als auch f( int *arr) ( ... ) sceiben. Aus dr Sicht von r spielt keine Rolle, daß der Parameter nur Teil eines großeren Vektors 1St. Is  sicher, daß die Elemente existieren, kann man auch rückwärts in einem Vektor dlZlern; p[-I), p[-2] usw. sind syntaktisch erlaubt und bezeichnen die Ele- ent, dl.e unmittelbar vor prO] liegen. Natürlich darf man nicht auf Objekte zugreifen die nicht Innerhalb der Vektorgrenzen liegen. ' 5.4 Adreß-Arithmetik Ist p ein Zeiger auf ein Element eines Vektors, dann inkrementiert p++ den Zei- ger p so, daß. er au das nchste Element zeigt, und p+= 1 inkrementiert ihn so, daß er i Elemete weiter zeigt. Diese und ähnliche Konstruktionen sind die einfachsten Arten von Zelger- oder Adreß-Arithmetik. . Adreß-Arithmetik ist in C konsistent und einheitlich defmiert; die Integration von Zelgrn, Vektoen. un Adre-.ithmetik ist eine der Stärken der Sprache. Wir illustrie- ren dies am BelSptel eIDer pnmttlven Speicherverwaltung. Sie besteht aus zwei Funktio- 
98 5 .i g er und Vektoren nen: alloc(n) liefert einen Zeiger p auf n aufeinanderfolgende Sicherzellen für Einzel zeichen die der Aufrufer von alloc zum Speichern von Zeichen verwenden kann, arree(p) gibt den so reservierten Speicher wieder frei, dmit er später neu vrgeben we- den kann. Diese Routinen sind "rudimentär", da arree m umgekehrter elhenfolge wie alloc aufgerufen werden muß. Das heißt, alloc und arre erwalten S.elhe als Stack, also mit einer tast-in,Jirst-out-Dis-nplin. Die Standard-Blbhthek. enthalt ahhche Fnk- tionen namens malloc und rree, die nicht so eingeschränkt smd; tm Abschnitt 8.7 zeigen wir, wie sie implementiert werden können. Bei der einfachsten Implementierung liefert alloc Abschnitte eines großen Zei- chenvektors, den wir allocburnennen wollen. Dieser ektor d nur on.alloc und.afree verwaltet. Da die Funktionen mit Zeigern und nicht mtt Vektonndlzes open eren, braucht keine andere Routine den Vektornamen zu kennen;  können den ektor also in der Quelldatei, die alloc und arree enthält, als static definlren, und er Ist deshalb außerhalb dieser Datei unsichtbar. Bei realistischen Implementierungen hat der Vektor möglicherweise nicht einmal einen Namen; er könnte sttt desse.n durch ufruf von malloc entstehen, oder indem man vom Betriebssystem emen Zeiger auf emen unbe- nannten Speicherbereich verlangt. Wir müssen auch noch wissen, wieviel von allocbur bereits verteilt wurde. Wr be- nutzen einen Zeiger allocp, der auf das nächste freie Element zeigt. Weden n Zetchen von alloc verlangt, so wird zunächst überprüft, ob noch genügend Plat In alocbuf vor- handen ist. Falls ja, liefert alloc den aktuellen Wert von allocp, ds hetßt, Ie Anfangs- adresse des freien Bereichs, und inkrementiert allocp um n, damit der Zeiger auf den nächsten freien Bereich zeigt. Ist kein Platz mehr frei, liefert alloc Null. arree(p) setzt einfach allocp auf p, wenn p auf eine Position in allocbur zeigt. vor Aufruf von alloc: allocbuf: allocp: '" cr=::ID frei _ benutzt -- '" nach Aufruf von alloc: allocbuf; cr=::ID allocp: '" I frei -- '" benutzt :.- - #define ALLOCSIZE 10000 /* verfuegbarer Platz */ static char allocbuf[ALLOCSIZEJ; /* speicherplat fuer.aloc**/ static char *allocp = allocbuf; /* naechste freIe PosItIon / char *alloc(int n) /* liefert Zeiger auf Platz fuer n Zeichen */ { if (allocbuf + ALLOCSIZE - allocp >= n) ( /* passt */ allocp += n; return allocp - n; /* alter Zeiger */ ) else /* nicht genug Platz */ return 0; ) 5.4 Adreß-Arithm 99 void afree(char *p) /* Speicher ab p freigeben */ { if (p >= allocbuf && p < allocbuf + ALLOCSIZE) allocp = p; ) Im allgemeinen kann ein Zeiger wie jede andere Variable initialisiert werden; nor- malerweise sind jedoch die einzig sinnvollen Werte entweder Null oder ein Ausdruck mit Adressen von früher defInierten Objekten mit geeignetem Datentyp. Die Vereinbarung static char *altocp = allocbuf; defIniert aIlocp als Zeiger auf char und initialisiert diesen Zeiger so, daß er auf den An- fang von allocbur zeigt; bei Programmbeginn ist dies die nächste freie Position. Das könnte man auch so schreiben: static char *allocp = &allocbuf[OJ; denn der Vektorname ist die Adresse des Elements in Position O. Die Bedingung if (allocbuf + ALLOCSIZE - allocp >= n) { /* passt */ überprüft, ob es noch genügend freien Platz gibt, um eine Anforderung von n Zeichen zu erfüllen. Falls ja, dann kann der neue Wert von allocp höchstens auf die erste Position nach dem Ende von allocbur verweisen. Kann die Anforderung erfüllt werden, dann lie- fert alloc einen Zeiger auf den Beginn eines Blocks für Zeichen. (Beachten Sie die Ver- einbarung der Funktion selbst). Kann die Anforderung nicht erfüllt werden, muß alloc als Resultat anzeigen, daß kein Platz mehr zur Verfügung steht. In C ist sichergestellt, daß Null niemals eine gültige Datenadresse ist; deshalb kann Null als Resultatwert dazu dienen, einen Fehler anzuzeigen, in diesem Fall also, daß kein Platz mehr frei ist. Zeiger und ganze Zahlen sind nicht austauschbar. Null ist die einzige Ausnahme: die Konstante Null kann an einen Zeiger zugewiesen werden, und ein Zeiger kann mit der Konstante Null verglichen werden. Die symbolische Konstante NULL wird oft statt Null als Gedächtnisstütze benutzt, um hervorzuheben, daß dies ein spe-neller Wert für einen Zeiger ist. NULL ist in < stdio.h > defIniert. Wir werden ab jetzt NULL verwenden. Bedingungen wie if (allocbuf + ALLOCSIZE - allocp >= n) { /* passt */ und if (p >= allocbuf && p < allocbuf + ALLOCSIZE) zeigen mehrere wichtige Aspekte von Zeigerarithmetik. Zum einen können Zeiger unter bestimmten Umständen verglichen werden. Wenn p und q auf Elemente im gleichen Vektor zeigen, dann funktionieren Vergleiche wie ==, !=, <, >= usw. Zum Beispiel trifft p < q zu, wenn p auf ein früheres Element im Vektor zeigt als q. Für jeden Zeiger ist die Be- dingung sinnvoll, ob er gleich oder nicht gleich Null ist. Der Ablauf ist jedoch undefmiert bei Arithmetik oder Vergleichen zwischen Zeigern, die nicht auf Elemente des gleichen Vektors verweisen. (Es gibt eine Ausnahme: die Adresse des ersten Elements nach dem Ende eines Vektors kann für Zeigerarithmetik verwendet werden.) 
100 5 . jger und Vektoren 5.5 chor-Zeiger ur unktionen 101 Zum andern haben wir schon festgestellt, daß ein Zeiger und ein ganzzahliger Wert addiert oder subtrahiert werden können. Die Konstruktion ehar *p = s; while (*p != '\0') p++; return p - s; S.S char-Zeiger und Funktionen Eine konstante Zeichellkette (string cOllstant), wie "Ich bin eine konstante Zeichenkette" ist ein Zeichenvektor. Intern wird der Vektor mit dem Nullzeichen '\0' beendet damit Programme d Ende dr Zeichenktte fInden können. Die Vektorlänge ist im Seicher daher um 1 großer als die Anzahl Zetchen zwischen den Doppelanführungszeichen. m äufIgsten kommen konstante Zeichenketten wohl als Funktionsargumente vor, WIe beI printf("hello. world\n"); Ercheint ein Zeichenkette wie diese in einem Programm, dann wird auf sie über einen ZeIger zugegrfen; printr erhält. als Argument einen Zeiger auf den Anfang des Zeichen- vektors. Auf eine konstante ZeIchenkette wird also mit einem Zeiger auf das erste Ele- ment zugegriffen. Konstante Zeichenketten brauchen nicht nur Funktionsargumente zu sein. Wird pmessage als ehar *pmessage; vereinbart, dann wird in pmessage = "now is the time";  pmessge ein Zeiger auf den Zeichenvektor zugewiesen. Dabei wird die Zeichenkette mcht oplert; an der <?peration sind nur Zeiger beteiligt. C hat keine Operatoren, die ei- ne ZeIchenkette als Einheit behandeln. Zwischen den folgenden beiden Definitionen besteht ein wichtiger Unterschied: ehar amessage[) = "now is the time"; /* ein Vektor */ ehar *pmessage = "now is the time"; /* ein Zeiger */ amessge ist ein Vektor, der gerade groß genug ist, um die Folge von Zeichen und das Nullzelchen '\0' aufzunehmen, mit denen er initialisiert wird. Einzelne Zeichen im Vek- tor kö.nnen geändert eden, aber amesage .wird immer auf den gleichen Speicherplatz verweisen. Andrerseits Ist pmessage ein ZeIger, der so initialisiert ist daß er auf eine konstante Zeicenkette zeigt; der Zeiger kann später so verändert werdn, daß er auf et- was anderes zeIgt. Versucht man aber, den Inhalt der konstanten Zeichenkette zu än- dern, ist das Resultat undefIniert. p + n bezeichnet die Adresse des noten Objekts nach dem Objekt, auf das p momentan zeigt. Dies gilt unabhängig von der Art des Objekts, auf das p zeigt; n wird in Abhängigkeit von der Größe der Objekte skaliert, auf die p zeigt. Die Objektgröße ergibt sich aus der Ver- einbarung von p. Hat zum Beispiel ein int-Wert vier Bytes, wird bei int-Zeigern mit dem Faktor vier skaliert. Subtraktion von Zeigern ist ebenfalls erlaubt: Zeigen p und q auf Elemente des gleichen Vektors und ist p<q, dann ist q-p+l die Anzahl der Elemente von p bis q ein- schließlich. Damit kann man noch eine weitere Version von strlen schreiben: /* strlen: Laenge der Zeichenkette s */ int strlen(ehar *s) ( ) In der Definition wird p mit dem Wert von s initialisiert, das heißt p zeigt auf das erste Zeichen der Zeichenkette. In der while-Schleife wird ein Zeichen nach dem anderen un- tersucht, bis '\0' am Ende der Zeichenkette entdeckt wird. Da p auf Zeichen zeigt, setzt p++ jedesmal p auf das nächste Zeichen, und p-s liefert die Anzahl Zeichen, über die weitergegangen wurde, eben die Länge der Zeichenkette. (Die Anzahl Zeichen in der Zeichenkette könnte zu groß sein, um sie in einer int- Variable zu speichern. In der DefI- nitionsdatei <stddef.h> wird ein Typ ptrdifr_t defIniert, der groß ist für die Differenz zweier Zeigerwerte samt Vorzeichen. Wären wir jedoch ganz vorsichtig, würden wir size talsResultattyp von strlen verwenden, wie das strlen aus der Standard-Bibliothek macht. size _ t ist der vorzeichenlose, ganzzahlige Typ, den der sizeof-Operator liefert.) Zeigerarithmetik ist konsistent: Hätten wir ßoat-Objekte manipuliert, die mehr Speicherplatz benötigen als char-Objekte, und wäre p ein Zeiger auf ßoat, dann würde p++ zum nächsten Doat-Wert gehen. Wir könnten also eine neue Fassung von alloc schreiben, die ßoat-Werte anstelle von char-Werten manipuliert, indem wir überall in alloc und afree den Typ char durch ßoat ersetzen. Alle Zeigeroperationen berücksichti- gen automatisch die Größe der Objekte, auf die gezeigt wird. Die erlaubten Operationen mit Zeigern sind: Zuweisung von Zeigern des gleichen Typs, Addition oder Subtraktion einer ganzen Zahl zu einem Zeiger, Subtraktion oder Vergleich zweier Zeiger auf Elemente des gleichen Vektors sowie Zuweisung oder Ver- gleich mit Null. Jede andere Zeigerarithmetik ist verboten. Zwei Zeiger dürfen nicht addiert werden, sie dürfen weder multipliziert noch dividiert, noch mit Shift oder anderen logischen Operatoren behandelt werden. Gleitpunktwerte dürfen nicht zu Zeigern ad- diert werden. Abgesehen von void ., darf ohne explizite Umwandlungsoperation kein Zeiger auf einen Datentyp an einen Zeiger auf einen anderen Datentyp zugewiesen wer- den. pmessage: G------1 now is the time\O I amessage: I now is the time\O I Wir illustrieren weitere Aspekte von Zeigern und Vektoren, indem wir verschiede- ne Versione von zwei nützlichen Funktionen aus der Standard-Bibliothek betrachten. strcpy(s,t), dIe erste Funktion, kopiert die Zeichenkette t in den Zeichenvektor s. Es wä- re schön, wenn mn each s = t sceiben könnte, aber da wird der Zeiger kopiert, nicht der Inhalt. Um die ZeIchen zu kopteren, brauchen wir eine Schleife. Hier ist die Vektor- fassung von strcpy: 
102 eiger und Vektoren /* strcpy: t nach s kopieren; Version mit Vektorindex */ void strcpy(char *s. char *t) ( int i; i = 0; while «s[i] tri]) 1= '\0') i++; ) Als Gegensatz ist hier eine Version von strcpy mit Zeigern: /* strcpy: t nach s kopieren; 1. Version mit Zeigern */ void strcpy(char *s. char *t) ( while «*s = *t) 1= '\0') ( s++; t++; ) ) Da nur Argumentwerte übergeben werden, kann strcpy die. Parameer s und t liebig benutzen. Hier sind sie zweckmäßig initialisierte Zeigervariablen, die Element. für Ele- ment die Vektoren entlanggeführt werden, bis '\0' als Abschluß von t nach s kopiert wur- de. In der Praxis würde man strcpy nicht so schreiben, wie wir das oben gezeigt haben. Erfahrene C- Programmierer würden folgendes vorziehen: /* strcpy: t nach s kopieren; 2. Version mit Zeigern */ void strcpy(char *s. char *t) ( while «*s++ = *t++) 1= '\0') ) Die Inkrementierung von sund t wurde hier in die Bedingung der Schleif verlagert. Der Wert von *t++ ist das Zeichen, auf das t gezeigt hat, bevor t kremenlert wurde; der nachgestellte Inkrement-Operator ++ ändert t erst, nachdem dieses tchen abge- holt wurde. Analog wird das Zeichen an der alten Zielposition s .agepelchrt, bevr s inkrementiert wird. Das Zeichen ist auch noch der Wert, der mit \0 verglichen d, um die Schleife zu kontrollieren. Im Endergebnis werden Zeichen von t nach s kopiert, unter Einschluß des abschließenden Nullzeichens '\0'. Als letzte Abkürzung halten wir fest, daß ein Vergleich mit '\0' redundant it,.da es nur darum geht, ob der Ausdruck Null ist. Die Funktion wird deshalb wahrschemhch so formuliert: /* strcpy: t nach s kopieren; 3. Version mit Zeigern */ void strcpy(char *s. char *t) ( while (*s++ = *t++) } Dies mag auf den ersten Blick unübersichtlich scheinen; es is jedoch ene. se b.quee Formulierung, und Sie sollten sich diese Ausdrucksweise aneignen, weil Sie sie haufig In C-Programmen sehen werden. .. Die Funktion strepy aus der Standard-Bibliothek « string.h » ltefert als FunktI- onswert einen Zeiger auf das Kopierziel. 5.5 chor-Zeiger 1 Funktionen 103 Als zweite Funktion betrachten wir stremp(s,t), die die Zeichenketten s und t ver- gleicht und ein Resultat kleiner, gleich oder größer Null liefert, je nachdem, ob s alpha- betisch kleiner, gleich oder größer t ist. Der Resultatwert entsteht als Differenz der Zei- chen an der ersten Stelle, an der sich s und tunterscheiden. /* strcmp: liefert <0 wenn s<t. 0 wenn s==t. >0 wenn s>t */ int strcmp(char *s. char *t) ( int i; for (i = 0; sei] == tri]; i++) if (s[i] == '\0') return 0; return sei] - tri]; } Die Zeigerfassung von strcmp: /* strcmp: liefert <0 wenn s<t. 0 wenn s==t. >0 wenn s>t */ int strcmp(char *s. char *t) ( for ( ; *s == *t; s++. t++) if (*s == '\0') return 0; return *s - *t; } Da ++ und -- vor oder nach ihren Operanden geschrieben werden können, sind andere Kombinationen von * und ++ und -- möglich, wenn auch weniger häufig. Zum Beispiel dekrementiert *--p p, bevor das Zeichen geholt wird, auf das p zeigt. Tatsächlich sind *p++ = val; /* val auf den Stack speichern */ val = *--p; /* oberstes Stack-Element entfernen und an val zuweisen */ die Standard-Ausdrücke, um etwas auf einen Stack zu legen oder vom Stack zu holen; siehe Abschnitt 4.3. Die Definitionsdatei < string.h > enthält Deklarationen für die in diesem Abschnitt erwähnten Funktionen, sowie für eine Sammlung von anderen Zeichenketten-Funktionen aus der Standard-Bibliothek. Aufgabe 5-3. Schreiben Sie eine Zeigerversion der Funktion streat, die wir in Kapitel 2 gezeigt haben: strcat(s,t) kopiert die Zeichenkette t an das Ende der Zeichenkette s. 0 Aufgabe 5-4. Schreiben Sie die Funktion strend(s,t), die 1 liefert, wenn die Zeichenket- te t am Ende der Zeichenkette s steht, und Null sonst. 0 Aufgabe 5 - 5. Schreiben Sie Versionen der Bibliotheksfunktionen strnepy, strneat und strnemp, die höchstens die ersten n Zeichen der als Argumente übergebenen Zeichen- ketten bearbeiten. Zum Beispiel kopiert strnepy(s,t,n) höchstens n Zeichen von t nach s. Vollständige Beschreibungen befinden sich im Anhang B. 0 Aufgabe 5-6. Formulieren Sie geeignete Programme aus früheren Kapiteln und Aufga- ben mit Hilfe von Zeigern anstelle von Vektorindizes neu. Gut geeignet dazu sind getline (Kapitell und 4), atoi, itoa und ihre Varianten (Kapitel 2, 3 und 4), reverse (Kapitel 3) sowie strindex und getop (Kapitel 4). 0 
104 iger und Vektoren 5.6 Vektoren von Zeigern; Zeiger auf Zeiger Da Zeiger selbst Variablen sind, können sie genau wie andere Variablen in Vekto- ren zusammengefaßt werden. Wir wollen dies anband eines Programms illustrieren, das eine Anzahl Textzeilen alphabetisch sortiert; es ist eine vereinfachte Fassung des UNIX- Programms so11. In Kapitel 3 haben wir eine Sortierfunktion nach Shell vorgestellt, die einen Vektor von ganzen Zahlen sortiert hat. In Kapitel 4 haben wir dies auf der Basis von Quickso11 verbessert. Die gleichen Algorithmen funktionieren wieder, nur müssen wir jetzt mit Textzeilen operieren, die verschieden lang sind und die, anders als ganze Zahlen, nicht in einer einzigen Operation verglichen oder verschoben werden können. Wir müssen die Daten so repräsentieren, daß wir effIZient und bequem mit variabel langen Textzeilen umgehen können. Hier kommt ein Vektor von Zeigern ins Spiel. Wenn wir die zu sortierenden Text- zeilen nacheinander in einem langen Zeichenvektor abspeichern, können wir auf jede Zeile mit Hilfe eines Zeigers auf ihr erstes Zeichen zugreifen. Die Zeiger selbst können in einem Vektor gespeichert werden. Zwei Zeilen können verglichen werden, indem ihre Zeiger an strcmp übergeben werden. Müssen zwei Textzeilen ausgetauscht werden, da sie sich nicht in der richtigen Reihenfolge befinden, dann werden einfach die Zeiger im Zeigervektor ausgetauscht, nicht die Textzeilen selbst. abc So vermeiden wir das zweifache Problem, daß die Speicherverwaltung kompliziert und der Rechenaufwand hoch wird, wenn wir die Textzeilen selbst verlagern würden. Der Sortiervorgang hat nun drei Phasen: alle Eingabeuilell leseI! sortieren in neuer Reihenfolge ausgeben Wie üblich ist es am einfachsten, wenn wir das Programm in Funktionen aufteilen, die dieser natürlichen Unterteilung entsprechen, wobei das Hauptprogramm die anderen Funktionen kontrolliert. Stellen wir die Sortierphase für den Augenblick zurück und konzentrieren wir uns auf die Datenstruktur und Eingabe und Ausgabe. Die Eingabefunktion muß die Zeichen jeder Zeile sammeln und speichern und einen Vektor von Zeigern auf die Zeilen konstruieren. Außerdem müssen die Eingabe- zeilen gezählt werden, denn diese Information braucht man zum Sortieren und Ausge- ben. Da die Eingabefunktion nur mit einer endlichen Anzahl Eingabezeilen fertig wird, kann sie eine ..unmögliche" Zeilenzahl wie -1 liefern, wenn zu viele Eingabezeilen vor- liegen. Die Ausgabefunktion muß nur die Zeilen in der Reihenfolge ausgeben, in der sie in dem Zeigervektor stehen. 5.6 Vektoren vOt jgern; Zeiger auf Zeiger 105 'include <stdio.h> #include <string.h> #define MAXLINES 5000 '* maximale Anzahl Zeilen *' ehar *lineptr[MAXLINES]; '* Zeiger auf die Textzeilen *' int readlines(ehar *lineptr[]. int nlines)' void writel ines(ehar *l ineptr[]. int nl ine); void qsort(ehar *l ineptr[]. int left. int right); '* Eingabezeilen sortieren *' ma i nO { int nlines; '* Anzahl eingelesener Zeilen *' if «nlfnes = readlfnes(lineptr, MAXLINES» >= 0) ( qsort(lineptr. O. nlines-1); writelines(lineptr. nlines); return 0; } else ( printf('error: input too big to sort\n"); return 1; } } Idefine MAXLEN 1000 '* maximale Laenge jeder Eingabezeile *' int getline(ehar * int); ehar *alloc(int); . '* readlines: Eingabezeilen einlesen *' int readl ines(ehar *l ineptr []. int maxl ines) { int len. nlines; ehar *p, line[MAXLEN]; nl ines = 0; while «len = getline(line. MAXLEN» > 0) if (nlines >= maxlines 11 (p = alloc(len» == NULL) return -1' else ( . l ine[len-1] = '\0'; '* Zei lentrerv'ler entfernen *' strepy(p. l ine); lineptr [nlines++] " p; } return nl ines; } '* writelines: Zeilen ausgeben *' void writelines(ehar *lineptr[] int nlines) { . int i; for (i = 0; i < nlines; i++) printf("Xs\n". l ineptr[i]); } Die Funktion getIine ist aus Abschnitt 1.9. Die wesentliche Neuerung ist die Definition für lineptr: ehar *lineptr[MAXLINES] 
106 .eiger und Vektoren Dadurch ist Iineptr ein Vektor mit MAXLINES Elementen; jedes Elemet ist ei? iger auf einen cbar Wert. Das heißt, Iineptr[i] ist ein Zeiger auf cbar d *hneptr[l] .Ist das Zeichen, auf das er zeigt, nämlich das erste Zeichen der i-ten gespeicherten Textzelle. Da Iineptr ein Vektorname ist, kann er selbst auch als Zeiger behandelt weren, genau wie in unseren früheren Beispielen. writelines kann deshalb auch so formuhert werden: /* writelines: Zeilen ausgeben */ void writelines(char *lineptr[], int nlines) ( while (nlines-- > 0) printf(IXs\n", *l ineptr++); } Am Anfang zeigt *lineptr auf die erste Zeile; mit jeder I.?krem:ntierng rückt Iineptr auf den nächsten Zeilenzeiger vor, während nlines auf 0 zuruckgezählt Wird. Nachdem Eingabe und Ausgabe abgehandelt sind, können wir uns nun .mit Sortie- ren beschäftigen. Der Quicksort aus Kapitel 4 muß leicht geändert werden: die Deklara- tionen müssen modiftziert werden, und der Vergleich muß durch Aufruf on strcmp r- folgen. Der Algorithmus bleibt gleich, was uns hoffen läßt, daß er noch tmmer funktIO- niert. /* qsort: sortiere v[left]...v[ight in aufsteigende Reihenfolge */ void qsort(char *v[], int left, lnt rlght) ( int i, last; void swap(char *v [], i nt i. i nt j); if (left >= right) /* nichts machen, wenn der Vektor */ return; /* weniger als 2 Elemente enthaelt */ swap(v, left, (left + right)/2); last = left; for (i = left+1; i <= right; i++) if (strcq>(v[i]. v[left]) < 0) swap(v, ++last, i); swap(v. left, last); qsort(v, lef" last-1); qsort(v, last+1, right); } Genauso braucht auch die Funktion swap nur triviale Änderungen: /* swap: vertausche v[i] und v[j] */ void swap(char *v[], int i, int j) ( char *t; t=v[i]; v[i] = v[j]; v[j] = t; ) Jedes einzelne Element von v (alias Iineptr) ist ein Zeiger auf cbar, also wird temp auch so deftniert, damit entsprechend zugewiesen werden kann. A fi be 5-7 Schreiben Sie readlines so um, daß die Textzeilen in einem Vektor abge- le erden, den main zur Verfügung stellt, so daß alloc nicht zur Speicherverwaltung verwendet wird. Wieviel schneller wird das Programm? 0 5.7 Mehrdimensio. . Vektoren 107 5.7 Mehrdimensionale Vektoren C verfügt über rechteckige, mehrdimensionale Vektoren, obgleich sie in der Praxis wesentlich seltener verwendet werden als Vektoren von Zeigern. In diesem Abschnitt werden wir einige ihrer Eigenschaften vorführen. Betrachten wir das Problem der Umrechnung eines Datums, aus Tag und Monat in den Tag im Jahr und umgekehrt. Beispielsweise ist der 1. März der 60. Tag in einern nor- malen Jahr und der 61. Tag in einern Schaltjahr. Wir deftnieren zwei Funktionen, die die Umrechnung vornehmen: day_ofyear wandelt Monat und Tag in den Tag im Jahr um, und montb _ day wandelt den Tag im Jahr in Monat und Tag um. Da die letztere Funkti- on zwei Werte berechnet, sind Monat und Tag jeweils Zeigerargumente: month_day(1988, 60, &m, &d) setzt m auf 2 und d auf 29 (nämlich den 29. Februar). Diese Funktionen brauchen beide die gleiche Information, nämlich eine Tabelle der Anzahl von Tagen in jedem Monat. Da die Anzahl der Tage je Monat in Schalt- und normalen Jahren verschieden ist, ist es einfacher, sie auf zwei Zeilen eines zweidimensio- nalen Vektors zu verteilen, als während der Berechnung zu verfolgen, was im Februar passiert. Der zweidimensionale Vektor und die Umrechnungsfunktionen lauten folgen- dermaßen: static char daytab[2] [13] = ( (0, 31, 28, 31, 30, 31. 30, 31. 31, 3D, 31, 30, 31), (0, 31, 29, 31, 30, 31, 30, 31, 31, 3D, 31, 30, 31) }; /* day_of_year: Tag im Jahr aus Monat und Tag bestimmen */ int day_of_year(int year, int month, int day) ( int i, leap; leap = yearX4 == 0 && yearX100 1= 0 11 yearX400 == 0; for (i = 1; i < month; i++) day += daytab[leap] [i]; return day; } /* month_day: Monat und Tag aus Tag im Jahr bestimmen */ void month_day(int year, int yearday, int *pmonth, int *pday) ( int i, leap; leap = yearX4 == 0 && yearX100 != 0 11 yearX400 == 0; for (i = 1; yearday> daytab[leap] [i]; i++) yearday -= daytab[leap] [i]; *pmonth = i; *pday = yearday; } Zur Erinnerung: der arithmetische Wert, der sich aus einer logischen Verknüpfung er- gibt, wie etwa der Wert von leap, ist entweder Null (für falsch) oder Eins (für wahr) und kann deshalb als Index in den Vektor daytab verwendet werden. 
108 5 ger und Vektoren Der Vektor daytab muß außerhalb von beiden Funkionen defmie weren, damit beide ihn benutzen können. Wir haben ihn mit char verembart, um zu illustneren, daß man in char legitim erweise auch kleine ganze Zahlen speichern kann, die keine Zeichen sind. daytab ist der erste zweidimensionale Vektor, dem wir begegnen. n C is ein zwei- dimensionaler Vektor in Wirklichkeit ein eindimensionaler Vektor, bei dem Jedes Ele- ment wieder ein Vektor ist. Deshalb schreibt man Indizes so daytab[i] [j] /* [Zeile] [Spal te] */ und nicht so daytab[i,j] /* FALSCH */ Von der unterschiedlichen Schreibweise abgesehen, kann ein zweidimensionaler Vektor fast so verwendet werden wie in anderen Programmiersprachen. Elemente werden zei- lenweise abgespeichert, deshalb ändert sich der Index. der am weitesten rechts s!eht (der Spaltenindex), am schnellsten, wenn die Elemente in der abgespeicherten Reihenfolge angesprochen werden. Ein Vektor wird mit einer Liste von lnitialisierungen in geschweiften Klammern in- itialisiert; jede Zeile eines zweidimensionalen Vektors wird mit einer entsprechenden Teilliste initialisiert. Wir haben den Vektor daytab mit einer Spalte mit 0 begonnen, so daß die Monatsnummem im natürlichen Bereich 1 bis 12 und nicht im Bereich 0 bis 11 liegen können. Da der Speicherbedarf hier unwesentlich ist, ist dies klarer, als die Indi- zes entsprechend abzuändern. Wenn ein zweidimensionaler Vektor an eine Funktion übergeben werden soll, dann muß die Parameterdeklaration in der Funktion die Anzahl Spalten enthalten; die Anzahl Zeilen ist nicht relevant da wie vorher ein Zeiger übergeben wird, und zwar ein Zeiger auf einen Vektor von Zeilen, die jeweils 13 int-Elemente enthalten. Hier liegt also ein Zeiger auf Objekte vor, die Vektoren von 13 int-Elementen sin. Wenn der Vektor daytab an die Funktion f übergeben werden soll. dann muß r so verembart werden: f( int daytab[2] [13]) ( .., ) Man könnte auch f( i nt daytabn [13]) ( ... ) schreiben, da die Anzahl der Zeilen unerheblich ist. Schließlich könnte man auch f(int (*daytab)[13]) ( ... ) angeben: so ist der Parameter ein Zeiger auf einen Vektor mit 13 int-Elementen. Die Klammern sind notwendig, denn die eckigen Klammern [] haben Vorrang vor *. Ohne Klammern ist die Vereinbarung int *daytab[13] ein Vektor mit 13 Zeigern auf Int-Werte. Allgemeiner formuliert: nur die erste D.ensi- onsangabe (der erste Index) eines Vektors darf weggelassen werden; alle anderen mussen angegeben werden. In Abschnitt 5.12 gibt es weitere Erklärungen zu komplizierten Vereinbarungen. Aufgabe 5 - 8. day _ otyear und month _ day enthalten keine Fehlerprüfungen. Entfernen Sie diesen Defekt. 0 5.8 lnitialisierung... Zei g ervektoren 109 5,8 Initialisierung von Zeigervektoren Betrachten Sie das Problem, eine Funktion month name(n) zu schreiben die einen Zeiger auf eine Zeichenkette liefert, die den Namen de"S noten Monats enthäl. Dies ist eine ideale Anwendung für einen internen static-Vektor. month name enthält einen pri- vaten Vektor mit Zeichenketten und liefert einen Zeiger auf die richtige davon. In die- sem Abschnitt sehen wir, wie dieser Vektor von Namen initialisiert wird. Die Syntax gleicht früheren lnitialisierungen: /* month_name: liefert Name des n-ten Monats */ char *month_name(int n) ( static char *name[] z ( "Iltegal month", "January", "February", "March", "Apri LU, uMayt-, UJl.J'1eIl, "July", "August", "September", "October", "November", "December" ); return (n < 1 11 n> 12) ? name[O] : name[n]; } name ist ein Vektor von Zeigern auf char und wird wie lineptr im Sortierbeispiel verein- bart. Die lnitialisierung ist eine Liste von Zeichenketten; jede Zeichenkette wird an die entsprechende Position im Vektor zugewiesen. Die Zeichen des i-ten Monatsnamens werden irgendwo aufbewahrt, und ein Zeiger auf die Zeichenkette wird in name[i] abge- legt. Da die Größe des Vektors name nicht angegeben ist, zählt der Übersetzer die In- itialisierungen und dimensioniert den Vektor entsprechend. 5.9 Zeiger kontra mehrdimensionale Vektoren Neulinge in Sachen C verwechseln manchmal einen zweidimensionalen Vektor und einen Vektor von Zeigern, wie name im obigen Beispiel. Mit den Defmitionen int a[10] [20]; int *b[10]; sind sowohl a[3][4] als auch b[3][4] legitime Verweise auf einen einzelnen int-Wert. Aber a ist ein echter zweidimensionaler Vektor: für 200 int- Werte ist Speicher bereitge- stellt worden, und die übliche Rechteckformel 20 xzeile + spalte wird angewendet, um das Element a [zeile ][spalte] aufzufmden. Für b reserviert die (lokale) Definition jedoch nur Speicher für 10 Zeiger und initialisiert sie nicht; die Initialisierung muß explizit erfol- gen, entweder statisch oder durch Ausführung von Anweisungen. Angenommen, jedes Element von b zeigt auf einen Vektor mit 20 Elementen, dann braucht man dazu Platz für 200 int-Werte und zusätzlich für 10 Zeiger. Der wichtige Vorteil des Zeigervektors ist, daß die Zeilen des Vektors verschieden lang sein können. Das heißt, nicht jedes Ele- ment von b muß auf einen Vektor mit 20 Elementen zeigen; manche Elemente könnten auf Vektoren mit 2 Elementen zeigen, andere auf Vektoren mit 50 und wieder andere überhaupt nicht auf Vektoren. Wir haben dies zwar auf int- Vektoren bezogen; am häufigsten werden Zeigervek- toren jedoch dazu benutzt, Zeichenketten unterschiedlicher Längen zu speichern, wie in 
110 5 iger und Vektoren dcr Funktion month name. Vergleichen Sie die Vereinbarung und die schematische Dar- stcllung für einen Vektor von Zeigern char *name (] = { "Illegal month", "Jan, "Feb", "Mar" }; name: 1 Feb\O Mar\O mit denen für cinen zweidimensionalen Vektor: char aname(] [15J = { "Illegal month", "Jan", "Feb", "Mar" }; aname: !Illegal month\O Jan\o Feb\O Mar\O o 15 30 45 Aufgabe S - 9, Ändern Sie die Funktionen day _oe _year und month _ day, so daß Zeiger stall Indexausdrücke verwendet werden. 0 5.10 Argumente aus der Kommandozeile In programmierumgebungen für C kann man normalerwcise Argumente aus der Kommandozeile beim Start an ein Programm übergeben. Wenn main aufgerufen wird, werden zwei Argumente übergeben. Das erste Argument (nach Konvention argc für ament ccount) ist die Anzahl der Argumente auf der Kommandozeile, mit der das Programm aufgerufen wurde; das zweite Argument (argv für argument vector) ist cin Zeiger auf einen Vektor von Zeigern auf Zeichenketten, die die Argumente enthalten, ein Argument pro Zeichenkelle. Üblicherweise verwenden wir mehrere Ebenen von Zeigcrn, um diese Zeichenkellen zu bearbeiten. Das einfachste Beispiel ist das Programm echo, das seine Argumente aus der Kom- mandozeile auf einer einzelnen Zeile, durch Leerzeichen getrennt, ausgibt. Das heißt, das Komrnando echo hello, world crzeugt die Ausgabe hello, world Nach Konvention ist argv[O] der Name, mit dem das Programm aufgerufen wurde, also ist argc wenigstens 1. Hat argc den Wert 1, gibt es nach dcrn Programmnamen keine Ar- gumente auf der Kommandozeile. Im obigen Beispiel hat argc dcn Wert 3 und a[O], argv[l] und argv[2] sind entsprcchend "echo", "heUo," und "world". Das erste ophonale Argument ist argv[l] und das lctzte argv[argc-1]; zusätzlich vcrlangt dcr Standard, daß argv[argc] ein Nullzeiger ist. 5.10 Argumente der Kommandozeile 111 argv: echo\O o Die erste Version von echo behandelt argv als Vektor von Zeigern auf Zeichen: #include <stdio.h> /* Echo der Kommandozeilenargumente; 1. Version */ mein(int argc, char *argv[J) { int i; for (i . 1; i < argc; i++) pr i ntf(..XsXs" argv[i], Ci < argc-1) ? .... ....); printf(..,'n"); , return 0; } Da argv ein Zeigr auf einen Vektor von Zeigern ist, kann man den Zeiger manipulieren statt IndexoperatIonen zu verwenden. Die nächste Version beruht auf der Inkrementie- rung von argv, der ein Zeiger auf einen Zeiger auf char ist; gleichzeitig wird argc herun- tergezählt. #include <stdio.h> /* Echo der Kommanozeilenargumente; 2. Version */ mein(int argc, ehar *argv[J) { while (--argc > 0) pri, ntf("XsXs" *++argv, (argc > 1) ? .... ....); printf(",n"); return 0; } argv t ein Zeiger aW: den Anfang des Vektors mit den Kommandoargumenten; inkre- mentiert man .den ZeIger um 1 (++argv), so zeigt er auf das ursprüngliche Element argv[1. und rocht mehr auf a[O]. Jede weitere Inkrementierung bewegt den Zeiger zum nachsten Argument; *argv 1St dann der Zeiger auf das Argument. Gleichzeitig wird arge dekrementiert; wird dieser Wert 0, sind keine Argumente mehr übrig zur Ausgabe. Man könnte die printe-Anweisung auch so schreiben: printf«argc> 1) ? "Xs .. : "Xs", *++argv); Dies illustriert, daß die Format-Zeichenkette von printe auch ein Ausdruck sein kann. Als zweites Beispiel wollen wir das Mustersuchprogramm aus Abschnitt 4.1 etwas erweitm. Wie Sie si einnem, haben wir das Suchmuster tief im Programm vergra- ben, eme. ganz offe1lS1chtlich ungenügende Lösung. Analog zum UNIX-Programm grep wollen wtr das Programm so ändern, daß das Suchmuster als erstes Argument auf der Kommandozeile angegeben wird. 
112 iger und Vektoren #include <stdio.h> #include <string.h> #define MAXLINE 1000 int getline(ehar *line, int max); /* find: Zeilen mit Suchmuster aus dem 1. Argument ausgeben */ main(int arge, eh ar *argv[]) { ehar l i ne [MAXLI NE] ; int found = 0; if (arge 1= 2) printf(IOUsage: find pattern\n lO ); else while (getl ine( l ine, MAXLlNE) > 0) if (strstr(line, argv[1]) 1= NULL) ( pri, ntf(IOXsIO Une); found++ ; } return found; } Die Funktion strstr(s,t) aus der Standard-Bibliothek liefert einen Zeige.r auf die erste Kopie der Zeichenkette t in der Zeichenkette s oder NULL, wenn tins nicht vorkommt. strstr ist in < string.h > deklariert. Das Modell kann jetzt erweitert werden, um weitere Zeigerkonstrukti?nen zu illu- strieren. Angenommen, wir wollen zwei option ale Argumente erlauben: emes verlangt "Gib alle Zeilen aus, außer denen, die das Suchmuster enthalten"; das andere verlangt "Gib vor jeder Zeile auch ihre Nummer aus". Eine übliche Konvention für C-Programme bei UNix-Systemen ist, daß ein Argu- ment das mit einem Minuszeichen beginnt, eine Option bezeichnet. Wählen wir etwa -x (für "ausgenommen", exJ:ept), um die Umkehrung der Ausw zu signalisieren, und _ n, um die Numerierung der Ausgabezeilen zu verlangen, dann WlJ"d das Kommando find -x -n pattem jede Zeile samt Zeilennummer ausgeben, die das Muster pattem nicht enthält. Optionale Argumente sollten in beliebiger Reihenfolge erlaubt sein, und der Rst des Programms sollte unabhängig von der Anzahl der angegebenen Argumente sem. Außerdem ist es für die Benutzer bequem, wenn Optionen kombiniert werden können, wie zum Beispiel find -nx panem Hier ist das Programm: #include <stdio.h> #include <string.h> #define MAXLINE 1000 int getline(ehar *line, int max); 5.10 Argumente, ier Kommandozeile 113 /* find: Zeilen mit Suehmuster aus dem 1. Argument ausgeben */ main(int arge, ehar *argv[]) { ehar ti ne [MAXLI NE] ; long l ineno = 0; int e, exeept = 0, number = 0, found = 0; while (--arge> 0 && (*++argv)[O] == '-') while (e = *++argv[O]) swi teh (e) ( ease 'x': exeept = 1; break; esse 'n': number " 1; break; defaul t: printf(lOfind: illegal option Xc\n lO , e); arge = 0; found = -1; break; } if (arge 1= 1) printf(IOUsage: find -x -n pattern\n lO ); else while (getline(line, MAXLINE) > 0) { l ineno++; if «strstr(line, *argv) != NULL) != exeept) ( if (number) printf(IXld:", l ineno); printf(IXs". Une); found++; } } return found; } Vor jedem optionalen Argument wird argc dekrementiert und argv inkrementiert. Verlief alles fehlerfre dann gibt am Ende der Schleife argc an, wieviele Argumente un- bearbeitet geblieben sind, und argv zeigt auf das erste davon. arge sollte deshalb 1 sein und *argv sollte auf das Suchmuster zeigen. Man beachte, daß *++argv auf eine Argu- ment-Zeichenkette zeigt, also ist (*++argv) [OJ das erste Zeichen des Arguments. (Ebenso korrekt wäre **++argv.) Da [J stärker bindet als * und ++, sind die Klammern notwendig, denn ohne sie wäre der Ausdruck gleichbedeutend mit *++(argv[O]). Das haben wir tatsächlich in der inneren Schleife verwendet, wo wir eine bestimmte Argu- ment-Zeichenkette entlanggehen müssen. In der inneren Schleife inkrementiert der Aus- druck *++argv[OJ den Zeiger argv[OJ! Es kommt selten vor, daß man kompliziertere Zeigerausdrücke als diese benutzt; solche werden klarer, wenn man sie in zwei oder drei EinzelschriUe zerlegt. Aufgabe 5-10. Schreiben Sie das Programm expr, das einen Ausdruck in umgekehrter polnischer Notation aus der Kommandozeile bewertet, wobei jeder Operator und jeder Operand ein separates Argument ist. Beispielsweise sollte expr 2 3 4 + * den Ausdruck 2 x (3 + 4) bewerten. 0 
114 5iger und Vektoren Aufgabe S -11. Ändern Sie die Programme entab und detab (die als Aufgaben im ersten Kapitel entwickelt wurden), so daß sie eine Liste von Tabulatorpositionen als Argumente akzeptieren. Gibt es keine Argumente, dann sollen die normalen Tabulatorpositionen benutzt werden. 0 Aufgabe S - 12. Erweitern Sie entab und detab, so daß die Abkürzung entab -m +n akzeptiert wird; sie soll bedeuten, daß Tabulatorpositionen alle n Spalten, ausgehend von Spalte m, anzunehmen sind. Sorgen Sie für eine (aus der Sicht des Benutzers) praktische Voreinstellung. 0 Aufgabe S -13. Schreiben Sie ein Programm tai!, das die letzten n Zeilen seiner Eingabe ausgibt. Nach Voreinstellung soll n zum Beispiel 10 sein, aber das kann mit einem optio- nalen Argument verändert werden, so daß tai I -n die letzten n Zeilen ausgibt. Das Programm soll sich vernünftig verhalten, unabhängig davon, wie unsinnig die Eingabe oder auch der Wert von n ist. Schreiben Sie das Pro- gramm so, daß es den verfügbaren Speicherplatz optimal ausnützt; Zeilen sollen wie beim Sortierprogramm von Abschnitt 5.6 gespeichert werden, nicht etwa in einem zweidi- mensionalen Vektor von fester Größe. 0 5.11 Zeiger auf Funktionen In C ist eine Funktion selbst keine Variable, aber man kann Zeiger auf Funktionen defInieren, die zugewiesen, in Vektoren eingetragen, an Funktionen übergeben, als Re- sultatwert von Funktionen geliefert werden können usw. Wir illustrieren dies, indem wir das Sortierprogramm modiftzieren, das wir früher in diesem Kapitel geschrieben haben, so daß ein option ales Argument -n dafür sorgt, daß die Eingabezeilen numerisch und nicht alphabetisch sortiert werden. Sortieren besteht oft aus drei Teilen - ein Vergleich, der die Anordnung von je- weils zwei Objekten bestimmt, ein Tausch, der die Anordnung umkehrt, und ein Sortier- algorithmus, der Vergleiche und Tauschoperationen durchführt, bis sich die Objekte in der richtigen Reihenfolge befrnden. Der Sortieralgorithmus ist von Vergleich und Tauschoperation unabhängig; wenn wir ihm verschiedene Vergleichs- und Tauschfunktio- nen übergeben, können wir nach verschiedenen Kriterien sortieren. Diesen Ansatz ver- wenden wir bei unserem neuen Sortierprogramm. Der Textvergleich von zwei Zeilen erfolgt wie bisher mit Hilfe von strcmp; zusätz- lich benötigen wir eine Funktion numcmp, die zwei Zeilen auf der Basis von numerischen Werten vergleicht und ein Resultat liefert, das strcmp entspricht. Diese Funktionen wer- den vor main deklariert und ein Zeiger auf die passende Funktion wird an qsort überge- ben. Wir behandeln fehlerhafte Argumente kaum, um uns auf die hauptsächlichen Punk- te zu konzentrieren. #include <stdio.> #include <string.h> #define MAXLINES 5000 char *lineptr[MAXLINES]; 1* maximale Anzahl Zeilen *1 1* Zeiger auf Textzeilen *1 5.11 Zeiger auf Pur 'nen 115 int reacH ines(char *l ineptr[], int nl ines); void writelines(char *lineptr[], int nlines); void qsort(void *lineptr[], int left, int right, int (*camp)(void *, void *». int numcmp(char *, char *); , 1* Eingabezeilen sortieren *1 main(int argc, char *argv[]) { int nlines; 1* Anzahl eingelesener Zeilen *1 int numeric a 0; 1* 1 fuer numerisches Sortieren *1 if (argc > 1 && strcmp(argv[1J, "-n") == 0) numeric a 1; if (nlines = readlines(lineptr, MAXLINES» >a 0) ( qsort«(void **) lineptr, 0, nlines-1, (int (*)(void*,void*»(numeric ? numcmp : strcmp»; wri tel ines( lineptr, nlines); return 0; } else ( printf("input too big to sort\n"); return 1; } }  u!ruf _n_qo sinß!:J) _ n.u_C:ID'p<?n_"'-'?Il£J!Q"'-11.. Da bekannt <tJ} s . s _!i!.__ tlOne a.:oI!.L ,der odreß-Op'erator & nicht nötig, wie er auc_ h_...()reU1e!DStQ!ll_lloni chtJ:>e ötig,-. -,- --- O_'M' ---- -- 'Y ir haben qsort so formuliert, daß jeder Datentyp bearbeitet werden kann, nicht nur Zelche?-"etten. urch den Funktionsprototyp wird angedeutet, daß qsort einen Vek- tor von Zeigern, zo:'el g Zahlen und eine Funktion mit zwei Zeigerargumenten er- wtet. Der enensche ZeIgertyp void · wird für die Zeigerargumente verwendet. Jeder Zeiger kann rn den Typ void · und zurück umgewandelt werden, ohne daß Information verloren ght;. deshalb können. wir qsort .aufrufen und dabei die Argumente explizit in den Typ VOld wandeln. Die aufwendige Umwandlungsoperation für den Funktions- name wandelt Ie Armente der Vergleichsfunktion um. Diese Umwandlungen haben zwar un allgmernen kernen Einfluß auf die tatsächliche Repräsentierung, aber sie über- zeugen den Ubersetzer, daß alles in Ordnung ist. I*.qsort: sortiere v[left]...v[right] in aufsteigende Reihenfolge *1 vOld qsort(void *v[], int left, int right, int (*camp)(void *, void *» ( int i, last; void swap(void *v[], int. int); if (left >= right) 1* nichts machen. wenn der Vektor *1 return; 1* weniger als 2 Elemente enthaelt *1 swap(v, left, (left + right)!2); last .. left; for (i = left+1; i <= rigt; i++) if ((*camp)(v[i], v[left]) < 0) swap(v, ++last, 0; swap(v, left, last); qsort(v, left, last-1, camp); qsorHv, last+1, right, camp); } 
116 Zeiger und Vektoren Die Parameterdeklarationen sollten sorgfältig betrachtet werden. Der vierte Parameter von qsort ist int (*camp)(void *, vold *) comp ist also ein Zeiger auf eine Funktion, die zwei void · Argumente hat und ein int- Resultat liefert. In der Zeile if «*camp)(v[i], v[left]) < 0) wird der Zeiger comp entsprechend seiner Deklaration benutzt: comp ist ein Zeiger auf eine Funktion, .comp ist die Funktion, und (*camp)(v[I], v[left]) ist der Aufruf. Die Klammern sind notwen  damit die einzelnen Teile korrekt zusam- mengefaßt werden. Ohne diese Klammern würde int *camp(void *, void *) /* FALSCH */ ausdrücken, daß comp eine Funktion ist, die einen Zeiger auf einen Int- Wert liefert, und dies ist natürlich etwas ganz anderes. Wir haben bereits die Funktion strcmp vorgeführt, die zwei Zeichenketten ver- gleicht. Hier ist die Funktion numcmp, die zwei Zeichenketten in bezug auf führende numerische Werte vergleicht, die mit atof berechnet werden: #include <stdlib.h> /* numcmp: s1 und s2 numerisch vergtelchen */ int numcmp(char *s1, char *s2) ( double v1, v2; v1 = atof(s1); v2 = atof(s2); if (v1 < v2) return -1; else If (v1 > v2) return 1; else return 0; } Die swap-Funktion, die zwei Zeiger vertauscht, ist identish zu dem, ws:r früer in diesem Kapitel vorgeführt haben, nur werden die DeklaratIOnen auf vOld abgean- dert. void swap(void *v[]. int i. int j) { void *t; t = v[i]; v[i] = v[j]; v[j] = t; } Zum Sortierprogramm kann man eine Vielzahl anderer Optionen hinzufügen; eini- ge sind anspruchsvolle Aufgaben. 5.12 Komplizie r Vereinbarun g en 117 Aufgabe 5 -14. Ändern Sie das Sortierprogramm, so daß eine Option - r akzeptiert wird, die die Reihenfolge der Sortierung umkehrt. Sorgen Sie dafür, daß - r auch zu- sammen mit - D funktioniert. 0 Aufgabe 5-15. Fügen Sie die Option -f (Cold) hinzu, bei der Groß- und Kleinschrei- bung beim Sortieren keinen Unterschied macht; zum Beispiel sind dann a und A gleich. o Aufgabe 5-16. Fügen Sie die Option -d (dictionary) hinzu, bei der nur Buchstaben, Ziffern und Leerzeichen verglichen werden. Sorgen Sie dafür, daß dies auch zusammen mit - r funktioniert. 0 Aufgabe 5-17. Fügen Sie eine Möglichkeit hinzu, einzelne Felder zu unterscheiden, so daß innerhalb der Zeilen in bezug auf verschiedene Felder sortiert werden kann, wobei jedes Feld noch mit voneinander unabhängigen Optionen sortiert werden soll. (Das Sachverzeichnis dieses Buches wurde mit -elf in bezug auf die Stichwörter und mit -n in bezug auf die Seitenzahlen sortiert.) 0 S.U Komplizierte Vereinbarungen C wird gelegentlich wegen der Syntax seiner Vereinbarungen kritisiert, insbesonde- re, wenn Zeiger auf Funktionen beteiligt sind. Die Syntax ist ein Versuch, Vereinbarung und Aufruf gleich aussehen zu lassen; das gelingt in einfachen Fällen, aber bei den schwierigeren kann es verwirren, denn man kann Vereinbarungen nicht von 1in.ks nach rechts lesen und Klammem nehmen überhand. Der Unterschied zwischen int *f(); /* f: Funktion, die einen Zeiger auf int liefert */ und int (*pf)(): /* pf: Zeiger auf Funktion, die int liefert */ verdeutlicht das Problem: · ist ein vorangestellter Operator und hat geringeren Vorrang als (), also braucht man Klammern, um die richtige Zuordnung zu erzwingen. Obwohl wirklich komplizierte Vereinbarungen in der Praxis selten auftreten, ist es wichtig ZU wissen, wie man sie versteht und nötigenfalls aufbaut. Ein guter Weg zum Aufbau von Vereinbarungen ist die Technik kleiner Schritte mit typedef, die in Abschnitt 6.7 erläutert wird. Als Alternative zeigen wir in diesem Abschnitt ein Paar von Program- men, die korrekte Vereinbarungen von C in (deutschen) Text und wieder zurück verwan- deln. Der Text wird von 1in.ks nach rechts gelesen. Das erste Programm, dcl, ist das kompliziertere. Es wandelt eine Vereinbarung von C in Worte um, wie in diesen Beispielen:" char **argv argv: Zeiger auf Zeiger auf char int (*daytab)[13] daytab: Zeiger auf Vektor[13] mit int i nt *daytab [13] daytab: Vektor[13] mit Zeiger auf int void *camp() camp: Funktion mit Resultat Zeiger auf void . In diesem Abschnitt haben wir auch den Text übersetzt, der in den Propammcn verwendet wird. dcl funk- tioniert auch im Deutschen verblüffend gut Ad.Ü. 
118 "Zeiger und Vektoren void (*eoq>)() eoq>: Zeiger auf Funktion mit Resultat void ehar (*(*x(»[])() x: Funktion mit Resultat Zeiger auf Vektor[] mit Zeiger auf Funktion mit Resultat eh ar ehar (*(*x[3])(»[5] x: Vektor[3] mit Zeiger auf Funktion mit Resultat Zeiger auf Vektor[5] mit ehar dcl beruht auf dem Grammatikbegriff dec/arator, der exakt im Anhang A  A.8.5 defmiert ist; hier ist eine vereinfachte Form: del: optionale *-e direet-del direet-del: name (dei) direet-del ( ) direet-del [optionale Größe] Oder in Worten: del ist direet-del, wobei *-e (Sterne) vorausgehen können. direet-dc/ ist ein Name oder dei in Klammem oder direet-del gefolgt von Klammern oder direet-dc/ ge- folgt von eckigen Klammem mit einer optionalen Größenangabe. Diese Grammatik kann zur Analyse von Vereinbarungen verwendet werden. Be- trachten Sie zum Beispiel folgende vereinbarung: (*pfa [] )() pCa wird als name erkannt und folglich als direct-del. Dann paßt pCa [] wieder zu direet- del. Damit wird *pCa [] als dei erkannt, deshalb gilt (*pCa []) als direet-dc/. Danach paßt dann (*pCa[])() zu direet-dc/ und folglich zu del. Wir können die Analyse als Syntax- baum folgendermaßen darstellen (direet-del wurde dabei mit dir-dcl abgekürzt): * pfa [] () 1/ name d+" dir.del I del I dir-dei I dir-dei I del Das Herz des Programms del bilden zwei Funktionen dcl und dirdcl, die eine Ver- einbarung gemäß dieser Grammatik analysieren. Da die Grammatik rekursiv defmiert ist, rufen sich die Funktionen gegenseitig rekursiv auf, während sie Teile einer Vereinba- 5.12 Komplizier t "ereinbarungen 119 rung ekennen; .ein solches Programm nennt man reeursive-deseellt parser (Methode des rekursiven AbstIegs). 1* dcl: "dcl" erkennen *1 void dcl(void) { int ns; for (ns = 0; gettoken() == '*'; ns++; di rde I (); while (ns-- > 0) streat(out. 11 Zeiger aufII); 1* *-e zaehlen *1 } 1* dirdcl: "dir-del" erkennen *1 void dirdcl(void) { int type; if (tokentype == '(') ( del(); if (tokentype 1= ')') printf("Fehler: ) fehlt\n"). } else if (tokentype == NAME) I*'variablenname *1 strepy(name. token); else . printf(IIFehler: Name oder (deI) erwartet\n"); Whll «type=gettoken(» == PARENS 11 type == BRACKETS) If (type == PARENS) strcat(out. 11 Funktion mit Resultat"); else { streat(out. streat(out. strcat(out. 1* ( deI ) *1 11 Vektor"); token) ; 11 mit"); } } .. Da diese. Progrmme illustrativ und nicht unbedingt kugelsicher sein sollen, gibt es fur dc/ beachtlice ElDschränkungen. Das Programm kann nur einfache Typen wie eh ar er Int verbelten. Parametertypen bei Funktionen oder Attribute wie eonst werden cht verarbeitet. Unerwartete Leerzeichen bringen dc/ durcheinander. Es gibt nur we- . Fehlerbehandlung, deshalb stiften auch falsche Vereinbarungen Verwirrung. Die notlgen Verbesserungen bleiben als Übungsaufgaben. Hier sind die globalen Variablen und das Hauptprogramm: #include <stdio.h> #include <string.h> #include <etype.h> #define MAXTOKEN 100 enum { NAME. PARENS. BRACKETS }; 1* Name. Klammern. eckige Klammern *1 void dcl(void); void dirdcl(void); int gettoken(void); 
120 .eiger und Vektoren 5.12 Komplizierte' :inbarungen 121 int tokentypei /* Typ des letzten SyriIols */ eher tokenTOKEN]i /* Zeichenkette mit letztem SyriIol */ ehar nameTOKEN] i /* Variabl */ ehar datatypeTOKEN]i /* Datentyp: ehar, int, usw. */ ehar out[1000]i /* Ausgabetext */ in() /* verwandle Vereinbarung in Worte */ { } return Oi . Die umgekehrte Richtung ist einfacher, insbesondere, wenn uns überflüssige Klam- mem nicht stören. Das Programm undc/ übersetzt eine verbale Beschreibung, wie "x ist eine Funktion mit Resultat Zeiger auf einen Vektor von Zeigern auf Funktionen mit Re- sultat char". Wir verwenden folgende Eingabe: x () * 0 * () ehar und erhalten folgende Ausgabe: ehar (*(*x(»[])() Dank der abgekürzten Eingabe-Syntax können wir die Funktion gettoken wiederverwen- den. undc/ benutzt auch die gleichen externen Variablen wie dc/. /* undcl: verwandle verbale Beschreibung in Vereinbarung */ main() ( while (gettoken() ,= EOF) { /* 1. SyriIol auf der Zeile */ strepy(datatype, tOken)i /* ist der Datentyp */ out[O] = '\0' i dcl()i /* Rest der Zeile analysieren */ if (tokentype ,= '\n') printf(NSyntaxfehler\nH)i printf("XII: XII XII\n", name , out, datatype)i int e, geteh(void)i void ungeteh(int)i ehar *p = tokeni while «e = geteh(» , 1 11 e == '\t') int typei ehar temp[MAXTOKEN]i while (gettoken() 1= EOF) ( strepy(out, token) i while «type = gettoken(» 1= '\n') if (type == PARENS 11 type == BRACKETS) streat(out, token)i else if (type == '*') { sprintf(temp, "(*XII)", out)i strepy(out, temp)i ) else if (type == NAME) { sprintf(temp, "XII XII", token, out)i strcpy(out, temp)i } else printf("ungueltige Eingabe bei XII\n", token)i printf("XII\n", out)i } Die Funktion gettoken überspringt Leerzeichen und Tabulatorzeichen und frodet dann das nächste Symbol in der Eingabe; ein "Symbol" ist ein Name, ein Paar von Klam- mern, ein Paar von eckigen Klammern, eventuell mit einer Zahl dazwischen, oder ein an- deres einzelnes Zeichen. int gettoken(void) /* liefere naechstes Symbol */ { if (e == '(') { if «e = geteh(» == ')') { strepy(token, "()")i return tokentype . PARENSi ) else ( ungeteh(e); return tokentype = '('i ) return Oi } } else if (e == '[') { for (*p++ = Ci (*p++ = geteh(» ,. ']'i ) } Aufgabe 5 -18. Sorgen Sie dafür, daß sich dc/ von Eingabefehlern erholt. 0 Aufgabe 5 -19. Ändern Sie undc/ so, daß keine überflüssigen Klammern zu Vereinba- rungen hinzugefügt werden. 0 Aufgabe 5-20. Erweitern Sie dc/ so, daß Vereinbarungen mit Parametertypen und At- tributen wie const usw. verarbeitet werden. 0 , *p = '\0' i return tokentype . BRACKETS; } else if (isalpha(e» { for (*p++ = e; isalnum(e = geteh(»i *p++ .. Ci *p = '\0' i ungeteh(e)i return tokentype . NAMEi ) else return tokentype = Ci } getch und ungetch wurden in Kapitel 4 besprochen. 
123 6 Strukturen Eine Struktur ist eine Ansammlung von einer oder mehreren Variablen, möglicher- weise mit verschiedenen Typen, die unter einem einzigen Namen zur bequemen Handha- bung zusammengefaßt sind. (In manchen Sprachen, insbesondere in Pascal, wird eine Struktur als "record" bezeichnet.) Strukturen sind nützlich, um komplizierte Daten zu organisieren, insbesondere in großen Programmen, denn sie ermöglichen, eine Gruppe von zusammengehörigen Variablen als Einheit statt separat zu behandeln. Ein traditionelles Beispiel einer Struktur ist ein Eintrag in einer Datei für Lohn- buchhaltung: Ein Mitarbeiter wird durch einen Satz von Attributen beschrieben, wie zum Beispiel Name, Adresse, Sozialversicherungsnummer, Gehalt usw. Einigc dieser At- tribute können selbst wieder Strukturen sein: Ein Name hat mehrere Komponenten, ge- nau wie eine Adresse oder sogar ein Gehalt. Ein andcres Beispiel, das für C typischer ist, hat mit Grafik zu tun: ein Punkt ist ein Koordinatenpaar, ein Rechteck ist ein Punk- tepaar usw. Als hauptsächliche Änderung wurde im ANSI-Standard die Zuweisung von Struktu- ren definiert - Strukturen können jetzt kopiert und zugewiesen sowie an Funktionen übergeben und von Funktionen als Resultat geliefert werden. Dies haben die meisten Übersetzer seit vielen Jahren unterstützt, aber die Eigenschaften sind nun genau defi- niert. Automatische Strukturen und Vektoren dürfen jetzt auch initialisiert werden. 6.1 Die Grundbegriffe Wir wollen ein paar Strukturen für Grafikanwendungen erzeugen. Das elementare Objekt ist ein Punkt, und wir nehmen an, daß er eine x-Koordinate und eine y_ Koordinate hat, die beide ganzzahlig sind. y . (4,3) x (0,0) Die zwei Komponenten können in einer Struktur zusammengefaßt werden: struct point { int x; int y; }; Das reservierte Wort struct steht am Anfang einer Strukturvereinbarung, die aus einer Liste von Deklarationen besteht, die in geschweifte Klammern eingeschlossen sind. Ein sogenanntes Etikett (stmctllre tag) kann dem Wort struct folgen (hier das Etikett point). Das Etikett steht dann für diese Art von Struktur und kann anschließend als Ab- kürzung für den Teil der Vereinbarung in geschweiften Klammern verwendet werden. Die Variablen, die in einer Struktur angegeben werden, nennen wir Kompol/elltel/ (members). Ein Etikett oder eine Komponente können den gleichen Namen besitzen wic eine gewöhnliche Variable, ohne daß dadurch ein Konflikt entsteht, denn sie könncn im- 
124 6 Strukturen 6.2 Strukturen un .nktionen 125 mer aus dem Kontext unterschieden werden. Außerdem kann der gleiche Komponen- tenname in verschiedenen Strukturen auftreten; aus stilistischen Gründen wird man aber normalerweise gleiche Namen nur für eng verwandte Objekte verwenden. Eine struct- Vereinbarung definiert einen Datentyp. Nach der rechten geschweif- ten Klammer am Ende der Komponentenliste kann eine Liste von Variablen stehen, ge- nau wie bei jedem elementaren Datentyp. Das heißt: struct ( '" ) x, y, z; struct reet ( struct point pt1; struct point pt2; ); Die reet-Struktur enthält zwei point-Strukturen. Wenn wir eine Variable screen vereinba- ren ist syntaktisch analog zu struct reet sereen; dann bezeichnet int x, Y, z; sereen.pt1.x die x-Koordinate der Komponente pU von screen. 6.2 Strukturen und Funktionen Strukturen darf man nur kopieren oder als Ganzes zuweisen' außerdem kann man die Adresse einer Struktur mit & bestimmen und auf die Kompoenten zugreifen. Ko- pieren und Zuweisen beinhaltet auch das Übergeben von Argumenten an Funktionen und das Zuruckliefem von Funktionsresultaten. Strukturen können aber nicht miteinan- der verglichen werden. Eine Struktur kann mit einer Liste von konstanten Werten für die Komponenten initialisiert werden; auch eine automatische Struktur kann durch Zuwei- sung initialisiert werden. Wir wollen Strukturen untersuchen, indem wir einige Funktionen zur Bearbeitung von Punkten und Rechtecken schreiben. Es gibt wenigstens drei Möglichkeiten: Kompo- nenten getrennt übergeben, eine gesamte Struktur übergeben oder einen Zeiger auf eine Struktur übergeben. Jeder Ansatz hat seine guten und schlechten Seiten. Die erste Funktion, makepoint, akzeptiert zwei ganze Zahlen und liefert eine point-Struktur: 1* makepoint: einen Punkt aus x- und v-Koordinate erzeugen *1 struct point makepoint(int x, int y) ( Jede dieser beiden Definitionen vereinbart Je, Y und z als Variablen des angegebenen Typs und reserviert Speicherplatz. Folgt einer Strukturdeklaration keine Variablenliste, wird auch kein Speicherplatz reserviert; eine solche Deklaration beschreibt lediglich die Form einer Struktur. Hat die Deklaration jedoch ein Etikett, dann kann das Etikett später zur Defmition von Objekten mit dieser Struktur verwendet werden. Wurde etwa point so wie oben deklariert, dann definiert struct point pt; eine Variable pt, die eine Struktur vom Typ struct point ist. Eine Struktur kann initiali- siert werden; dazu folgt der Definition eine Liste von lnitialisierungen, also konstanten Ausdrücken, für die Komponenten: struct point maxpt .. ( 320, 200 ); Eine automatische Struktur kann auch durch Zuweisung initialisiert werden oder durch Aufruf einer Funktion, die eine Struktur mit dem richtigen Typ als Resultat liefert. Ein Verweis auf eine Komponente einer bestimmten Struktur hat in einem Aus- druck folgende Form: Struktur- Variablenname . Komponente Dabei verbindet der Operator "." zur Auswahl von Strukturkomponenten den Namen der Strukturvariablen und den Namen der Komponente. Zum Beispiel gibt pri, ntf("Xet,Xet" pt.x, pt.y); die Koordinaten des Punkts pt aus und double dist, sqrt(double); dist .. sqrt«double)pt,x * pt.x + (double)pt.y * pt.y); berechnet den Abstand von pt zum Ursprung (0,0). Strukturen können Strukturen als Komponenten enthalten. Ein Rechteck kann als Paar von Punkten dargestellt werden, die die diagonal gegenüberliegenden Ecken be- zeichnen: struct point temp; temp.x .. x; temp.y .. y; return temp; Je } Man beachte, daß zwischen den Namen der Parameter und den gleichen Namen der Komponenten kein Konflikt entsteht; tatsächlich betont die Wiederverwendung der Na- men den Zusammenhang. Mit makepoint kann man nun eine Struktur dynamisch initialisieren oder Struktu- ren als Argumente für eine Funktion konstruieren: struct reet sereen; struct point middle; struct point makepoint(int, int); sereen.pt1 .. makepoint(O, 0); sereen.pt2 .. makepoint(XMAX. YMAX); middle .. makepoint«sereen.pt1.x + sereen.pt2.x)/2. (sereen.pt1.y + sereen.pt2.Y)/2); y D'" pt1 
126 6 Strukturen Als nächsten Schritt entwickeln wir einen Satz von Funktionen für Arithmetik mit Punkten. Zum Beispiel /* addpoint: zwei Punkte addieren */ struct point addpoint(struct point p1, struct point p2) { p1.x += p2.x; p1.y += p2.y; return p1; } Hier sind beide Parameter und der Resultatwert Strukturen. Anstatt eine expliite te- poräre Variable zu benutzen, haben wir direkt zu den Komponenten von pI addiert; dies soll ganz deutlich machen, daß Strukturparameter wie alle anderen Parameter als Werte übergeben werden. Als weiteres Beispiel untersucht die Funktion ptinrect, ob ein Punkt in einem Rechteck liegt, wobei wir die Konvention getroffen haben, daß die linke und untere Kan- te zum Rechteck dazugehören, nicht aber die obere und rechte Kante. /* ptinrect: Resultat ist 1 falls p in r, 0 falls nicht */ int pt;nrect(struct point p. struct rect r) { return p.x >= r.pt1.x && p.x < r.pt2.x && p.y >= r.pt1.y && p.y < r.pt2.y; } Dabei wird angenommen, daß das Rechteck in einer Normalform vorlieg, be der de pU-Koordinaten kleiner als die pU-Koordinaten sind. Die folgende Funktion hefert em Rechteck in dieser Normalform: #define min(a, b) «a) < (b) 1 (a) : (b» #define max(a, b) «a) > (b) 1 (a) : (b» /* canonrect: Normalform der Koordinaten eines Rechtecks liefern */ struct rect canonrect(struct rect r) ( struct rect tempi temp.pt1.x = min(r.pt1.x, r.pt2.x); temp.pt1.y = min(r.pt1.y, r.pt2.y); temp.pt2.x = max(r.pt1.x, r.pt2.x); temp.pt2.y = max(r.pt1.y, r.pt2.y); return tempi > Wenn eine große Struktur an eine Funktion übergeben werden soll, ist es im lIge- meinen effIZienter, einen Zeiger zu übergeben und nicht die ganze Struktur zu kopiere? Zeiger auf Strukturen verhalten sich genau wie Zeiger auf gewöhnliche Variablen. Die Vereinbarung struct point *pp; legt fest, daß pp ein Zeiger auf eine Struktur von Typ struct point ist. Wnn p auf eine point-Struktur zeigt, dann ist .pp die Struktur und (.pp).x oder (.pp). smd die Kompo- nenten. Um pp zu benutzen, könnten wir zum Beispiel folgendes schreiben: struct point origin, *pp; pp = &origin; printf("origin is (XcI,XcI)\n", (*pp).x, (*pp).y); 6.3 Vektoren von lkturen 127 Die Klammern bei (.pp).x sind notwendig, da der Vorrang des Operators "." zur Aus- wahl von Strukturkomponenten höher ist als der des Inhaltsoperators .. Der Ausdruck .pp.x bedeutet .(pp.x); das ist hier ungültig, denn x ist kein Zeiger. Zeiger auf Strukturen werden so häufig benutzt, daß es eine andere Schreibweise als Abkürzung gibt. Ist p ein Zeiger auf eine Struktur, dann verweist p->Komponente -einer- Struktur auf die angegebene Komponente. (Der Operator -> besteht aus einem Minuszeichen, dem eine rechte spitze Klammer> folgt.) Wir könnten deshalb auch schreiben printf("origin ;s (Xd,Xd)\n", pp->x, pp->y); Beide Auswahloperatoren . und -> werden von links nach rechts zusammengefaßt, also sind nach der Vereinbarung struct reet r, *rp = r; diese vier Ausdrucke äquivalent: r.pt1.x rp->pt 1 . x (r.pt1).x (rp->pt1) .x Die Auswahloperatoren . und -> haben zusammen mit () für Funktionsaufrufe und [] für Indexoperationen den höchsten Vorrang aller Operatoren und binden daher sehr eng. Nach der Vereinbarung struct { i nt len; char *str; > *p; vergrößert zum Beispiel ++p->len len und nicht p, da implizit als ++ (p-> len) geklammert wird. Mit Klammern kann man die Bindung ändern: (++p)->Ien inkrementiert p, bevor dann auf len zugegriffen wird, und (p++ )-> len inkrementiert p hinterher. (In diesem letzten Beispiel sind die Klam- mern unnötig.) Ebenso wird mit dem Ausdruck .p-> str auf das Objekt zugegriffen, auf das str zeigt; .p-> str++ inkrementiert str nach Zugriff auf das Objekt, auf das str zeigt (genau wie .s++); (.p-> str)++ inkrementiert das Objekt, auf das str zeigt; und .p++ -> str schließlich inkrementiert p nach Zugriff auf das Objekt, auf das str zeigt. 6.3 Vektoren von Strukturen Betrachten wir ein Programm, das die in C reservierten Worte in einem Text zählt. Wir benötigen einen Vektor von Zeichenketten, um die Namen aufzubewahren, und einen Vektor von ganzzahligen Werten für die Zähler. Eine Möglichkeit wäre, zwei par- allele Vektoren keyword und keycount zu verwenden, die folgendermaßen definiert wer- den könnten: char *keyword[NKEYSJ; int keycount[NKEYSJ; 
128 6 Strukturen Die Tatsache jedoch, daß die Vektoren parallel zu betrachten sind, deutet auf eine ande- re Organisation: einen Vektor mit Strukturen. Jeder Eintrag für ein reserviertes Wort besteht aus zwei Informationen: ehar *word; int count; und es gibt einen Vektor von solchen Informationspaaren. Die StrukturdefInition struct key { char *word; int count; } keytab [NKEYS) ; deklariert einen Strukturdatentyp key, defIniert einen Vektor keytab von entsprechenden Strukturen und reserviert Speicher für diesen Vektor. Jedes Element des Vektors ist eine Struktur. Eine alternative Formulierung wäre struet key { ehar *word; int count; }; struct key keytab[NKEYS); Da die Struktur keytab eine konstante Liste von Namen enthält, ist es am einfach- sten sie als externe Variable zu vereinbaren und sie ein für alle Mal bei der DefInition gleih zu initialisieren. Die Initialisierung der Struktur erfolgt analog wie früher - der DefInition folgt eine Liste von lnitialisierungen in geschweiften Klammern: struct key { ehar *word; int count; } keytab[) = { lIauto", 0, "break", 0, "ease", 0, "ehern, 0, "const", 0, IIcontinue", 0, "default", 0, '* ... *' "uns i gned" , 0, IIvoid ll , 0, "volati le", 0, "wh ile" , 0 }; Die Initialisierungen werden paarweise angegeben, den Komponenten der Struktur ent- sprechend. Präziser sollte man dabei die Initialisierungen für jede "Zeile" oder Einzel- struktur in geschweifte Klammern einschließen, also in folgender Form: { "auto", 0 }, { "break", 0 }, { "case", 0 }, Die inneren geschweiften Klammern sind jedoch nicht notwendig, wenn die Initialisierun- gen einfache Variablen oder Zeichenketten sind und wenn alle angegeben werden. Wie 6.3 Vektoren von  ;turen 129 lich berechnet der rsetzer die Anzahl der Einträge im Vektor keytab, wenn Initia- lislerungen angegeben smd und die Dimensionierung in [J ausgelassen wird. Das Programm zum Zählen von reservierten Worten beginnt mit der DefInition von keytab. Das Hauptprogramm liest seine Eingabe, indem wiederholt eine Funktion getword aufgerufe d, die j:desmal ein Wort holt. Jedes Wort wird dann in keytab ge- sucht, d zwar mit el.ner .Vanante der unktion zur binären Suche, die wir in Kapitel 3 eschnben haben. Die LISte der resemerten Worte muß in der Tabelle aufsteigend sor- tIert sem. #include <stdio.h> #include <etype.h> #include <string.h> #def i ne MAXWORD 100 int getword(ehar *, int); int binseareh(ehar *, struct key *, int); ,* reservierte orte in c zaehlen *' mein() ( int n; ehar word[MAXWORD); while (getword(word, MAXWORD) != EOF) if (isalpha(word[O)) if «n = binseareh(word keytab, NKEYS» >= 0) keytab[n).eount++; , for (n = 0; n < NKEYS; n++) if (keytab[n].eount > 0) printf("%4d %5\n", keytab[n].eount, keytab[n].word); return 0; } '* binseareh: word in tab[0]...tab[n-1] finden *' int binseareh(ehar *word, struct key tab[] int n) { , i nt eond; int lew, high, mid; low = 0; high = n - 1; while (low <= high) { mid = (loW+high) , 2; if «eond = stremp(word, tab[mid].word» < 0) high = mid - 1; else if (eond > 0) low = mid + 1; else return mid; } return -1; } Wir zeige die Funktion getword später; im Augenblick genügt es, wenn wir davon ausge- h:n, daß Jeder Aufruf von getword ein Wort fIndet, das in den Zeichenvektor kopiert Wird, der getword als erstes Argument übergeben wird. 
130 6 Strukturen 6.4 Zeiger auf Stn. en 131 Die Größe NKEYS ist die Anzahl der reservierten Worte in keytab. Wir könnten diese Zahl natürlich von Hand auszählen, es ist jedoch wesentlich leichter und sicherer, wenn dies die Maschine übernimmt, insbesondere, falls die Liste später geändert wird. Eine Möglichkeit hierzu wäre, an das Ende der Initialisierungsliste einen NULL-Zeiger zu stellen und dann keytab mit Hilfe einer Schleife bis zu diesem Zeiger abzusuchen. Dies ist jedoch zu aufwendig, denn die Größe des Vektors kann vollständig vom Übersetzer bestimmt werden. Die Größe des Vektors ist bestimmt durch die Größe ei- nes Eintrags multipliziert mit der Anzahl Einträge, deshalb ist die Anzahl Einträge gera- de /* getword: naeestes ort oder Zeieen aus der Eingabe olen */ int getword(ear *word, int lim) ( int e, gete(void); void ungete(int); ear *w = word; while (isspaee(e = gete(») Größevon keytab / Größevon struct key In C gibt es den unären Operator slzeof, der vom Übersetzer bewertet wird und der dazu benutzt werden kann, die Größe eines beliebigen Objekts festzustellen. Die Ausdrücke si zeof Objekt if (e 1= EOF) *w++ = c; if (Iisalpha(e» { *w .. I \0' ; return e; und } for ( ; --lim > 0; w++) if (Iisalnum(*w = gete(») ( ungete(*w); break; sizeof( Typnar.ne ) liefern jeweils eine ganze Zahl, nämlich die Größe des angegebenen Objekts oder Daten- typs gemessen in ..Byte". (Genaugenommen erzeugt slzeof einen ganzzahligen Wert oh- ne Vorzeichen, dessen Typ slze _tin der DefInitionsdatei < stddef.b > vereinbart ist.) Ein Objekt kann dabei eine Variable, ein Vektor oder eine Struktur sein. Der Datentypname kann der Name eines elementaren Typs wie Int oder double sein, oder auch der Name ei- nes abgeleiteten Typs, wie etwa einer Struktur oder eines Zeigers. In unserem Fall ist die Anzahl reservierter Worte gleich der Größe des Vektors, di- vidiert durch die Größe eines Vektorelements. Dies wird in einer #define-Anweisung dazu benutzt, um den Wert von NKEYS festzulegen: #define NKEYS (sizeof keytab / sizeof(struct key» Man kann auch die Vektorgröße durch die Größe des ersten Elements teilen: #define NKEYS (sizeof keytab / sizeof keytab[O]) Diese Formulierung hat den Vorteil, daß sie unabhängig vom Datentyp ist. slzeof kann nicht in einer #If-Zeile verwendet werden, da der Preprozessor die Typnamen nicht erkennt. Der Ausdruck in #denne wird dagegen nicht vom Preprozes- sor bewertet, also ist die Formulierung erlaubt. Betrachten wir jetzt die Funktion getword. Wir haben getword allgemeiner formu- liert als nötig für dieses Programm, aber die Funktion ist nicht kompliziert. getword holt das nächste ..Wort" aus der Eingabe. Dabei ist ein Wort entweder eine Kette von Buch- staben und Ziffern, die mit einem Buchstaben beginnt, oder ein einzelnes Zeichen, das kein Zwischenraumzeichen ist. Das funktionsresultat ist der erste Buchstabe des Worts, oder EOF am Dateiende oder das Zeichen selbst, wenn es kein Buchstabe ist. } *w = · \0 I ; return word[O]; } getword benutzt die Funktionen getcb und ungetch, die wir in Kapitel 4 entwickelt haben. Wid das Ede einer Folge von Buchstaben und Ziffern erreicht, dann hat getword ein Zetchen zuViel eingelesen. Durch einen Aufruf von ungetch wird dieses Zeichen in die Eingabe zurückgestellt, so daß es beim nächsten Aufruf wieder gelesen werden kann. getword benutzt auch Isspace, um Zwischenraumzeichen zu überspringen, isalpha, um Buchstaben und Isalnum, um Buchstaben oder Ziffern zu erkennen; alle stammen aus der Standard- Definitionsdatei < ctype.h > . Aufgabe 6-1. Unsere Version von getword behandelt Unterstrich.. ", konstante Zei- cheette ( Doppelühr.unzeichen), Kommentare und Zeilen für den Preprozes- sor rocht nchtlg. Schreiben Sie eme bessere Version. 0 6.4 Zeiger auf Strukturen Wir illustrieren einige der Überlegungen im Zusammenhang mit Zeigern auf Strturen und Vektoren von Strukturen, indem wir das Programm zum Zählen von re- servierten Worten nochmals schreiben, und zwar diesmal mit Zeigern anstelle von Vek- torindizes. Die globale Definition von keytab kann unverändert bleiben, aber main und blnsearch müssen modifIziert werden. 
132 6 Strukturen 6.5 Rekursive  .turen 133 #include <stdio.h> #include <etype.h> #include <string.h> #define MAXWORO 100 int getword(ehar *, int); struet key *binseareh(ehar *, struct key *, int); /* reservierte orte in C zaehten; Version mit Zeigern */ meinO { int eond; struet key *low s &tab[O]; struct key *high s &tab[n]; struct key *mid; while (low < high) { mid = low + (high-low) / 2; if «eond = stremp(word, mid->word» < 0) high = midi else if (eond > 0) low = mid + 1; else return midi berechnet werden, da die Addition zweier Zeiger nicht erlaubt ist. Subtraktion ist jedoch erlaubt, high -Iow ist die Anzahl der Elemente, und durch mid . low + (high-low) / 2 wird mid ein Zeiger auf das Element, das in der Mitte zwischen low und high liegt. Am wichtigsten aber ist, daß der Algorithmus geändert wird, um sicherzustellen, daß kein ungültiger Zeiger erzeugt wird, und daß nicht versucht wird, auf ein Element außerhalb des Vektors zuzugreifen. Das Problem besteht darin, daß &tab[ -1] und &tab[n] beide außerhalb der Grenzen des Vektors tab liegen. Das erste Verweis ist defi- nitiv nicht erlaubt, und mit dem zweiten Zeigerwert darf nicht auf ein Element zugegrif- fen werden. Die Sprachdefinition garantiert jedoch, daß Zeigerarithmetik korrekt arbei- tet, auch wenn sie sich auf das erste Element nach dem Ende eines Vektors (also &tab[nJ) stützt. In main schrieben wir for (p . keytab; p < keytab + NKEYS; p++) Ist p ein Zeiger auf eine Struktur, dann wird bei jeder Arithmetik mit p die Größe der Struktur berücksichtigt; p++ inkrementiert daher p mit einem entsprechenden Wert, da- mit p dann auf das nächste Element im Vektor von Strukturen zeigt, und der Vergleich hält die Schleife rechtzeitig an. Nehmen Sie jedoch nicht an, daß die Größe einer Struktur die Summe der Größe der Komponenten ist. Je nachdem, wie verschiedene Objekte ausgerichtet sein müssen, kann die Struktur namenlose "Löcher" enthalten. Wenn zum Beispiel ein char ein Byte belegt und ein int-Wert vier Bytes, dann kann die Struktur struct { ehar e; Int i; ehar word[MAXWORO]; struct key *p; while (getword(word, MAXO) 1= EOF) if (isalpha(word[O]» if «p=binseareh(word, keytab, NKEYS» 1= NULL) p->eOUlt++; for (p = keytab; p < keytab + NKEYS; p++) if (p->count > 0) printf("X4d XII\n", p->COUlt, p->word); return 0; } /* binseareh: word in tab[0]...tab[n-1] finden */ struet key *binsearch(char *word, struct key *tab, int n) { } return NULL; }; durchaus acht Bytes benötigen, und nicht nur fünf. Der Operator sizeor liefert den richti- gen Wert. Zum Schluß noch eine Bemerkung zum Programmformat. Liefert eine Funktion einen komplizierten Resultattyp, wie zum Beispiel einen Zeiger auf eine Struktur, struct key *binseareh(ehar *word, struct key *tab. int n) dann kann der Funktionsname schwer zu sehen oder mit einem Texteditor aufzufinden sein. Deshalb wird oft folgende andere Schreibweise benutzt: struct key * binseareh(ehar *word, struct key *tab, int n) Dies ist eine Frage des persönlichen Programmierstils; wählen Sie das Format, das Ihnen gefällt, und behalten Sie es bei. 6.5 Rekursive Strukturen Nehmen wir an, daß wir das allgemeinere Problem lösen wollen, die Häufigkeit aller Wörter in einem Text zu zählen. Da die Liste der Wörter von vornherein nicht be- kannt ist, können wir sie nicht einfach sortieren und binäre Suche verwenden. Andrer- seits können wir auch nicht jedes eingegebene Wort linear suchen, um zu sehen, ob das } Hier ist einiges erwähnenswert. Zunächst müssen die Vere.inbarunge? von binsearch zeigen, daß als Resultat ein Zeiger auf struct key geliefert wud und kem int- Wert; dies geschieht im Funktionsprototyp und in binsearch. Fidet bins.earch das ge- suchte Wort, dann wird ein Zeiger auf den zugehörigen Tabellenemtrag gehefert; andern- falls ist der Resultatwert NULL. Zum zweiten wird auf Elemente von keytab nun mit Hilfe von Zeigern zugegriffen. Dies bedingt wesentliche Änderungen in binsearch. Die lnitialisierungswerte für low und high sind nun Zeiger auf den Anfang und di- rekt hinter das Ende der Tabelle. Das mittlere Element kann nicht mehr als mid = (low+high) / 2 /* FALSCH */ 
134 6 Strukturen 6.5 Rekursive Struk n 135 Wort bereits früher aufgetreten ist; das Programm würde zu lange laufen. (Präziser ge- sagt, die Laufzeit des Programms würde wahrscheinlich quadratisch mit der Anzahl der Eingabewörter wachsen.) Wie können wir die Daten repräsentieren, um effizient mit ei- ner Liste beliebiger Wörter fertig zu werden? Eine mögliche Lösung ist, die bereits eingegebenen Wörter grundsätzlich sortiert aufzubewahren, indem man jedes Wort unmittelbar nach der Eingabe an die richtige Stelle speichert. Dazu sollte man allerdings nicht Wörter in einem linearen Vektor ver- schieben - auch dies benötigt zuviel Zeit. Wir werden staU dessen eine Datenstruktur verwenden, die man einen binären Baum nennt. Der Baum enthält einen "Knoten" für jedes unterschiedliche Wort; ein Knoten enthält folgende Information: einen Zeiger auf den Text des Worts einen Zähler für die Häufigkeit einen Zeiger auf den linken Nachkommen einen Zeiger auf den rechten Nachkommen Ein Knoten kann dabei nicht mehr als zwei Nachkommen besitzen; ein oder gar kein Nachkomme kann allerdings auftreten. Die Knoten werden so angeordnet, daß bei jedem Knoten der linke Unterbaum nur Wörter enthält, die alphabetisch vor dem Wort im Knoten stehen, und der rechte Unterbaum nur die "größeren" Wörter. Für den Satz "now is the time for all good men to come to the aid of their party" entsteht folgender Baum, wenn man die Wörter in Rei- henfolge der Eingabe einträgt: struct tnode ( ehar *word; int count; struct tnode *left; struct tnode *right; '* der Knoten eines Baumes: *' '* zeigt auf den Text *' '* Haeufigkeit *' '* linker Nachkomme *, '* rechter Nachkomme *' ); Diese "rekursive" Vereinbarung eines Knotens sieht vielleicht riskant aus, sie ist aber korrekt. Eine Struktur darf sich zwar selbst nicht enthalten, aber struct tnode *left; vereinbart len als Zeiger auf einen Knoten tnode, nicht als Knoten tnode selbst. Manchmal braucht man eine andere Art von rekursiven Strukturen: zwei Struktu- ren, die gegenseitig aufeinander verweisen. Dies kann folgendermaßen erreicht werden: struct t ( struct s *p; '* p zeigt auf ein s *' ); struct s ( struct t *q; '* q zeigt auf ein t *' ); now /"" is the /\ /"" for men of time / \ all good / \ aid come Um festzustellen, ob sich ein neues Wort bereits im Baum befmdet, beginnt man an der Wurzel und vergleicht das neue Wort mit dem Wort, das im Wurzelknoten gespeichert ist. Handelt es sich um das gleiche Wort, ist die Frage bereits positiv beantwortet. Ist das neue Wort kleiner als das Wort im Baum, wird die Suche mit dem linken Nachkom- men fortgesetzt; andernfalls wird beim rechten Nachkommen weiter untersucht. Gibt es in der gewünschten Richtung keine Nachkommen, befindet sich das neue Wort nicht im Baum, und tatsächlich ist die unbesetzte Position die richtige Stelle, um das neue Wort als Nachkomme einzutragen. Der Suchvorgang ist rekursiv, da die Suche von einern Knoten aus eine Suche von einem der Nachkommen aus verwendet. Rekursive Funktio- nen sind deshalb für Einfügen und Ausgeben wohl am natürlichsten. Zurück zur Beschreibung eines Knotens; er wird am einfachsten als Struktur mit vier Komponenten repräsentiert: \ / \ party their to Das Programm selbst ist überraschend klein, wenn wir eine Handvoll Hilfsfunktio- nen wie getword benutzen, die wir bereits früher geschrieben haben. Das Hauptpro- gramm liest die Wörter mit getword und fügt sie in den Baum mit addtree ein. #include <stdio.h> #include <ctype.h> #include <string.h> #def i ne MAXWORD 100 struct tnode *addtree(struct tnode *, char *); void treeprint(struct tnode *); int getword(char *, int); '* Haeufigkeit von orten zaehlen *' mein() ( struct tnode *root; char word[MAXWORD]; root = NUll; while (getword(word, MAXWORD) 1= EOF) if (isalpha(word[O]» root = addtree(root, word); treeprint(root); return 0; } addtree ist rekursiv. main präsentiert ein Wort an der obersten Ebene (der Wur- zel) des Baes. Auf jeder Ebene wird dieses Wort mit dem Wort verglichen, das im Knoten bereits gespeichert ist, und wird mit einem rekursiven Aufruf von addtree entwe- der zum linken oder zum rechten Unterbau m durchgereicht. Schließlich wird das Wort entweder im Baum gefunden (dann wird count inkrementiert) oder wir finden einen Nullzeiger, wo dann ein Knoten erzeugt und zum Baum hinzugefügt werden muß. Wird 
136 6 Strukturen 6.5 Rekursive Stru en 137 ein neuer Knoten erzeugt, dann liefert addtree einen Zeiger auf diesen Knoten, der dann beim vorhergehenden Knoten eingefügt wird. struct tnode *talloc(vold); ehar *strdup(ehar *); /* IICIdtree: einen Knoten mit w bei oder nach p einfuegen */ struct tnode *lICIdtree(struct tnode *p, ehar *w) { I nt eond; If (p == NULL) ( /* ein neues Wort */ p = tallocO; /* neuen Knoten erzeugen */ p->word = strdup(w); p->eOUlt = 1; p->left = p->rlght = NULL; } else If «cond = strcmp(w, p->word» == 0) p->eOUlt++; /* Wort Ist schon vorgekommen */ else if (eond < 0) /* kleiner: links darunter */ p->left = IICIdtree(p->left, w); else /* groesser: rechts darunter */ rlght = IICIdtree(p->rlght, w); return p; Bevor wir dieses Beispiel verlassen, wollen wir noch kurz abschweifen und ein Pro- blem diskutieren, das bei Speicherverwaltung auftritt. Es ist sicher wünschenswert, daß es in einem Programm nur eine Routine zur Speicherverwaltung gibt, auch wenn diese Routine Speicherbereiche für verschiedenartige Objekte liefern soll. Wenn aber die glei- che Funktion etwa Zeiger auf char und Zeiger auf struct tnode liefern soll, ergeben sich zwei Fragen. Erstens; Wie erfüllt die Funktion die Bedingung, daß bei den meisten Ma- schinen Objekte mit bestimmten Datentypen auch geeignet ausgerichtet sein müssen (beispielsweise müssen ganzzahlige Werte sehr oft geradzahlige Adressen besitzen)? Zweitens; Welche Vereinbarungen lösen das Problem, daß eine Funktion zur Speicher- verwaltung zwangsweise verschiedenartige Zeiger als Resultat liefert? Bedingungen zur Ausrichtung kann man im allgemeinen leicht erfüllen, wenn man eine gewisse Platzverschwendung in Kauf nimmt: Man sorgt dafür, daß die Funktion zur Speicherverwaltung grundsätzlich einen Zeigerwert liefert, der allen Anforderungen an Ausrichtung genügt. Die Funktion alloc aus Kapitel 5 garantiert keine bestimmte Aus- richtung; wir verwenden deshalb die Funktion malloc aus der Standard-Bibliothek, die das tut. In Kapitel 8 werden wir eine Implementierung von malloc vorführen. Die Frage der Typdeklaration für eine Funktion wie malloc ist bei jeder Sprache diffizil, die ihre Typüberprüfung ernst nimmt. In C vereinbart man korrekterweise, daß malloc einen Zeiger auf void liefert; dieser Zeiger wird dann jeweils explizit mit einer Umwandlungsoperation in den gewünschten Typ umgewandelt. malloc und verwandte Routinen sind in der Standard- Defmitionsdatei < stdlib.h > vereinbart. talloc kann des- halb so geschrieben werden; #Include <stdllb.h> /* talloc: tnode erzeugen */ struct tnode *talloc(void) { } Eine Funktion talloc beschafft Speicherplatz für den neuen Knoten. Sie liefert einen Zeiger auf einen freien Speicherbereich, der gignet ist, einen Knoten ufzune- men. Das neue Wort wird mit Hilfe von strdup an eme verborgene telle koplrt. . <':A:u: werden diese Funktionen in Kürze noch besprechen.) Der Häufigkeitszähler d 1D.Ib- lisiert, und die zwei Zeiger auf die Nachkommen werden auf NULL..gesetzt. Dlser Teil der addtree-Funktion wird nur an den Blättern des Baumes ausgeführt, wenn em neue Knoten hinzugefügt wird. Wir haben (unvorsichtigerwee) auf Fehlerbehandlung bel entsprechenden Resultatwerten von strdup und taUoc veI"Zlchtet. treeprint gibt den Baum sortiert aus; bei jedem Knoten wird zunächst der linke Unterbaum ausgegeben (alle Wörter, die dem momentanen Wort. vorausgehen), dann das Wort selbst und dann der rechte Unterbaum (alle Wörter, die noch nachfolgen). Macht Rekursin Sie unsicher, dann sollten Sie nachvollziehen wie treeprint den oben gezeigten Baum abarbeitet. /* treeprlnt: Baum p sortiert ausgeben (Inorder) */ vold treeprlnt(struct tnode *p) { } strdup kopiert einfach die als Argument übergebene Zeichenkette an eine sichere Stelle, die ein Aufruf von malloc liefert: ehar *strdup(ehar *s) /* Duplikat von s erzeugen */ { return (struct tnode *) melloc(slzeof(struct tnode»; If (p 1= NULL) ( treeprlnt(p->left); prlntf("X4d XII\n", p->eOUlt, p->word); treeprlnt(p->rlght); ehar *p; p = (ehar *) melloc(strlen(s)+1); /* +1 fuer '\0' */ if (p != NULL) strepy(p, s); return p; } } maIloc liefert NULL, wenn kein Speicherplatz verfügbar ist; strdup gibt diesen Wert wei- ter und überläßt seinem Aufrufer die Fehlerbehandlung. Speicherplatz, den man von malloc erhält, kann man durch Aufruf von free zur Wiederverwendung freigeben; siehe Kapitel 7 und 8. Aufgabe 6-2. Schreiben Sie ein Programm, das ein C-Programm liest und in alphabeti- scher Reihenfolge jede Gruppe von Variablennamen ausgibt, die sich erst nach den er- sten 6 Zeichen unterscheiden. Berücksichtigen Sie Wörter in Zeichenketten und Kom- } Ein praktischer Hinweis: Wird der Baum "unbalanciert", weil die Wörter nicht durcheinander ankommen, dann kann die Laufzeit des Programms zu schnell wachse? Im schlimmsten Fall sind die Wörter bereits sortiert, und dann is dieses Progr. die aufwendige Simulation einer linearen Suche. Es gibt Verallgemeerungen on baren Bäumen, die nicht derartig in eine lineare Liste ausarten, aber WIr wollen diese Baume hier nicht beschreiben. 
138 6 Strukturen 6.6 Suchen in T len 139 mentaren nicht. Machen Sie 6 zu einem Parameter, der von der Kommandozeile aus ge- setzt werden kann. 0 Aufgabe 6-3. Schreiben Sie ein Referenzlistenprogramm, das eine Liste aller Wörter in einem Text ausgibt und bei jedem Wort zusätzlich die Nummern der Zeilen, in denen dieses Wort auftritt. Entfernen Sie Füllwörter wie "der", "und", "die", ..oder", "das" und so weiter. 0 Aufgabe 6-4. Schreiben Sie ein Programm, das die verschiedenen Wörter aus der Ein- gabe in abnehmender Häufigkeit ihres Auftretens ausgibt. Geben Sie vor jedem Wort seine Anzahl aus. 0 struct nlist { /* Tabelleneintrag: */ struct nlist *next; /* naeehster Eintrag in der verketteten liste */ eh ar *name; /* definierter Name */ ehar *defn; /* Ersatztext */ }; Der Zeigervektor ist einfach #define HASHSIZE 101 6.6 Suchen in Tabellen In diesem Abschnitt werden wir die wesentlichen Funktionen zur Tabellensuche konstruieren, um weitere Aspekte von Strukturen zu illustrieren. Die Funktionen sind ty- pisch dafür, was man als Symboltabellen-Management in einem Makroprozessor oder Übersetzer fmdet. Betrachten wir etwa die #define-Anweisung. Sieht der Preprozessor eine Zeile wie #define IN 1 dann müssen der Name IN und der Ersatztext 1 in einer Tabelle gespeichert werden. Er- scheint dann später der Name IN in einer Anweisung wie state = IN; so muß er durch den Text 1 ersetzt werden. Es gibt zwei Funktionen, die die Namen und Ersatztexte verwalten. install(s,t) trägt den Namen S und den Ersatztext t in eine Tabelle ein; s und t sind einfach Zeichen- ketten. lookup(s) sucht die Zeichenkette s in der Tabelle und liefert einen Zeiger auf den entsprechenden Tabelleneintrag oder NULL, wenn s nicht vorhanden ist. Als Algorithmus wird eine Hash-Technik verwendet - der betroffene Name wird in eine kleine natürliche Zahl verwandelt, die dann als Index in einen Zeigervektor ver- wendet wird. Jedes Vektorelement zeigt auf den Anfang einer verketteten Liste von Da- tenblöcken, die alle Namen beschreiben, die denselben Hash-Wert besitzen. Ein Vektor- element ist NULL, wenn noch keine Namen diesen Hash-Wert ergaben. statie struct nlist *hashtab[HASHSIZE]; /* Zeigertabelle */ Die Hash -Funktion, die von lookup und install benutzt wird, addiert jedes Zeichen im Namen zu einer verwürfelten Kombination der vorhergehenden und liefert dann den Rest nach Division durch die Anzahl der Vektorelemente. Dies ist zwar nicht die best- mögliche Hash-Funktion, dafür aber kurz und effektiv. /* hash: Hash-ert fuer Zeichenkette s liefern */ unaigned hash(ehar *s) { unaigned hashval; for (hashval - 0; *s I- '\0'; s++) hashval - *s + 31 * hashval; return hashval X HASHSIZE; } Die vorzeichenlose Arithmetik stellt sicher, daß der Hash-Wert nicht negativ ist. bash liefert einen Index für den Vektor basbtab; ist die gesuchte Zeichenkette überhaupt vorhanden, dann befmdet sie sich in der Liste von Datenblöcken, die in die- sem Element beginnt. Die Suche erfolgt in der Funktion lookup. Findet lookup den ge- suchten Eintrag, wird ein entsprechender Zeiger als Resultatwert geliefert; gibt es den Eintrag nicht, so ist das Resultat NULL. /* lookup: s in hashtab suchen */ struct nlist *lookup(ehar *s) { struct nl Ist *np; for (np - hashtab[hash(s)]; np 1= NUll; np = np->next) If (stremp(s. np->name) -= 0) return np; /* gefunden */ return NUll; /* nicht gefunden */ o o name defn } Die ror-Schleife in lookup ist die Standard-Formulierung zum Durchlaufen einer verket- teten Liste: for (ptr - head; ptr 1= NUll; ptr = ptr->next) defn Ein Datenblock in der Liste ist eine Struktur, die Zeiger auf den Namen, den Er- satztext und den nächsten Block in der Liste beinhaltet. Ein NULL-Zeiger markiert dabei das Ende der Kette. o name install benutzt lookup, um zu entscheiden, ob der neue Name bereits vorhanden ist; wenn ja, ersetzt die neue Defmition die alte. Andernfalls wird ein neuer Eintrag kon- struiert. installliefert NULL, falls etwa kein Platz für einen neuen Eintrag vorhanden ist. 
140 6 Strukturen struct nlist *lookup(char *), char *strdup(char *), /* install: (name , defn) in hashtab eintragen */ struct nlist *install(char *name, char *defn) { struct nli st *np; unsigned hashval; if «np a lookup(name» aa NULL) { /* nicht da */ np = (struct nlist *) malloc(sizeof(*np», if (np a= NULL 11 (np->name = strdup(name» == NULL) return NULL; hashval = hash(name); np->next . hashtab[hashval], hashtab[hashvat] . np; } else /* bereits vorhanden */ free«void *) np->defn); /* alte defn auflassen */ if «np->defn . strdup(defn» .. NULL) return NULL, return np; } Aufgabe 6-5. Schreiben Sie eine Funktion undef, die einen Namen und eine Defmition aus der Tabelle entfernt, die lookup und install unterhalten. 0 Aufgabe 6-6. Implementieren Sie einen einfachen #define-Prozessor (das hißt, ohne Argumente), der für C-Programme geeignet ist, unter Verwendg .der Funktlonen aus diesem Abschnitt. Vielleicht fmden Sie auch getch und ungetch nutzlich. 0 6.7 typedef Neue Typnamen können in C mit Hilfe von typedef vereinbart werden. Beispiels- weise wird durch die Vereinbarung typedef int Length, der Name Length synonym zu int. Der Typ Length kann bei Vereinbarungen, Umwand- lungsopcrationen usw. genauso verwendet werden wie der Typ int: Length len, maxlen; Length *lengths[], Entsprechend vereinbart typedef char *String; String als Synonym für ehar *, also als einen Zeiger auf Zeichen, der dann in Vereinba- rungen und Umwandlungsoperationen benutzt werden kann: String P. lineptr[MAXLINES], alloc(int); int strcmp(String. String); p = (String) malloc(100), Man beachte, daß der in typedef vereinbarte Typname in der Positin eines Varia- blennamens auftritt und nicht unmittelbar nach dem Wort typedef. Staktlsch verwende man typedef wie eine Speicherklasse, also wie extern, staUe etc. Wir haben Namen bel typedef großgeschrieben, um sie hervorzuheben. Als komplizierteres Beispiel könnten wir einen Knoten für den früher in diesem Kapitel beschriebenen binären Baum mit typedef vereinbaren: 6.8 Unionen 141 typedef struct tnode *Treeptr, typedef struct tnode { /* der Knoten eines Baunes: */ char *word; /* zeigt auf den Text */ int count; /* Haeufigkeit */ Treeptr left, /* linker Nachkomme */ Treeptr right, /* rechter Nachkomme */ } Treenode; Hier werden zwei neue Typnamen eingeführt, nämlich 1reenode (eine Struktur) und 1reeptr (ein Zeiger auf die Struktur). talloc könnte dann folgendermaßen formuliert werden: Treeptr talloc(void) ( return (Treeptr) malloc(sizeof(Treenode», } Wir müssen betonen, daß eine typedef- Vereinbarung in keiner Hinsicht einen neu- en Datentyp konstruiert; es wird lediglich ein zusätzlicher Name für einen existenten Typ eingeführt. Ebenso gibt es keine neue Semantik: Variablen, die mit Hilfe von typedef- Typen vereinbart werden, haben genau die gleichen Eigenschaften wie Variablen, deren Vereinbarungen explizit formuliert wurden. typedef verhält sich effektiv wie #define, ab- gesehen davon, daß typedef vom Übersetzer selbst verarbeitet wird und folglich Texter- satz vornehmen kann, der die Möglichkeiten des C-Preprozessors übersteigt. Zum Bei- spiel erzeugt typedef int (*PFI)(char *, char *), den Datentyp PFI: "Zeiger auf eine Funktion (mit zwei ehar * Argumenten), die ein int- Resultat liefert". PFI kann dann beispielsweise im Sortierprogramm von Kapitel 5 fol- gendermaßen benutzt werden: PFI strcmp, nuncmp; Neben rein ästhetischen Gründen wird typedef hauptsächlich aus zwei Gründen be- nutzt. Einmal kann dadurch ein Programm gegen Portabilitätsprobleme parametrisiert werden. Führt man Datentypen, die maschinenabhängig sein könnten, mit Hilfe von typedef ein, dann muß man nur die typedef- Vereinbarungen ändern, wenn man das Pro- gramm auf eine andere Maschine überträgt. Häufig vereinbart man mit typedef Namen für verschiedene ganzzahlige Wertebereiche und wählt dann je nach Zielmaschine short, int und long dafür aus. Typdefmitionen wie size t und ptrdilT t aus der Standard-Biblio- thek sind Beispiele. -- Zum zweiten kann man mit typedef ein Programm besser dokumentieren - einen Typ 1reeptr kann man vielleicht leichter verstehen als einen Typ, der nur als Zeiger auf eine komplizierte Slruktur vereinbart ist. 6.8 Unionen Eine Union ist eine Variable, die (zu verschiedenen Zeitpunkten) Objekte mit ver- schiedenen Datentypen und Größen enthält, wobei der Übersetzer die nötige Größe und Ausrichtung des Speicherbereichs überwacht. Mit Unionen können verschiedene Arten von Datenobjekten in einern einzigen Speicherbereich manipuliert werden, ohne daß da- durch maschinenabhängige Information in das Programm eingeht. Die Datenstruktur entspricht dem ease-varianten reeord von Pascal. . 
142 6 Strukturen 6.9 Bit-Felder 143 Betrachten wir ein Beispiel, das man vielleicht in der Symboltabellen- Verwaltung eines Übersetzers fmdet: Nehmen wir an, daß Konstanten int-Werte, noat-Werte oder auch Zeiger auf Zeichen sein können. Der Wert einer bestimmten Konstanten muß in einer Variablen von geeignetem Typ abgespeichert werden. Am bequemsten für die Ver- waltung von Tabellen ist es jedoch, wenn der Wert unabhängig von seinem Typ jeweils gleichviel Speicher benötigt und auch im gleichen Speicherbereich abgelegt wird. Darin liegt der Sinn einer Union - eine einzelne Variable, die legitimerweise einen beliebigen von mehreren Typen aufnehmen kann. Die Syntax beruht auf Strukturen: union u_tag { int ival; float fval; ehar *sval; } u; oder die Alternative ivai folgendermaßen angesprochen symtab[i] .u. ival und für das erste Zeichen von svai gibt es zwei Möglichkeiten *symtab[i).u.sval symtab[i] .u.sval [0) Eine Union ist praktisch eine Struktur, in der alle Teile die relative Adresse 0 besit- zen en auf di.e Basisadresse der Strtur). Die Struktur ist groß genug, um die "brelteste AlternatIve aufzunehmen, und die Ausrichtung genügt allen Datentypen in de Union. Für eine Union gibt es die gleichen Operationen wie für eine Struktur: zu- weISen oder kopieren als Ganzes, berechnen der Adresse, Zugriff auf eine Alternative. Ee Union ann nur mit einem Wert initialisiert werden, der zum Typ der ersten AlternatIve paßt; die oben beschriebene Union u kann deshalb nur mit einem Integer- Wert initialisiert werden. Die in Kapitel 8 vorgeführte Speicherverwaltung zeigt, wie man mit einer Union ei- ne bestimmte Ausrichtung für eine Variable erzwingen kann. 6.9 Bit-Felder . Wenn Speierplatz sehr knapp ist, muß man vielleicht mehrere Objekte in einem emzeen Menwort zusammenfassen; ein übliches Beispiel ist eine Menge von Ein- zel-Bit-Werten bei Anwendungen wie der Symboltabelle eines Übersetzers. Auch bei Dat.enformaten, die von der Umgebung abhängen, wie etwa bei den Schnittstellen von penpheriegeräten, ist es oft notwendig, auf Teile eines Worts zugreifen zu können. Stellen Sie sich einen Ausschnitt aus einem Übersetzer vor, bei dem eine Symbol- tbelle verwaltet wird. Zu jedem Namen in einem Programm gehört bestimmte Informa- on, etwa?b es sich um ein reserviertes Wort handelt, ob der Name global bekannt oder m d.er S'pelerklasse static deklariert ist usw. Ganz kompakt codiert man diese Infor- matIOn m emer Menge von Einzel-Bit-Werten in einer Variablen vom Datentyp char oder int. Dies geschieht üblicherweise, indem man "Bit-Masken" definiert die den relevan- ten Bit-Positionen entsprechen, also ' #define KEYWORD 01 #define EXTERNAL 02 #define STATIC 04 Die Variable u ist groß genug, um den größten der drei Datentypen aufzunehmen, die Größe des benötigten Speicherplatzes ist implementierungsabhängig. Ein Wert aus jedem der drei Typen kann an u zugewiesen und dann in Ausdrücken benutzt werden, je- denfalls solange die Benutzung konsistent ist: der Datentyp, der entnommen wird, muß der Typ sein, der als letzter zuvor gespeichert wurde. Der Programmierer muß verfol- gen, welcher Typ jeweils in einer Union gespeichert ist; die Resultate sind implementie- rungsabhängig, wenn ein Objekt mit einem Datentyp abgespeichert und mit einem ande- ren Datentyp wieder entnommen wird. Syntaktisch werden, analog zu den Komponenten von Strukturen, die Altemativen einer Union folgendermaßen ausgewählt: Union - Variab/enname .A/temalive Union - Zeiger-> A/temalive Benutzt man die Variable utype, um festzuhalten, welcher Datentyp im Augenblick in u abgespeichert ist, dann könnte man etwa folgenden Programmtext fmden: if (utype == INT) printf("Xd\n", u.ival); else if (utype == FLOAT) pri, ntf("Xf\n" u.fval); else if (utype == STRING) printf("Xs\n", u.sval); else printf(IIbIId type Xd in utype\n", utype); Unionen können innerhalb von Strukturen und Vektoren auftreten und umgekehrt. Eine Alternative innerhalb einer Union in einer Struktur (oder umgekehrt) wird genauso ausgewählt, wie das bei verschachtelten Strukturen geschieht. Beispielsweise wird im fol- genden Strukturvektor struct { ehar *name; int flags; i nt utype; union { int ival; float fval; ehar .sval; } u; } symtab[NSYM); oder enum { KEYWORD = 01, EXTERNAL = 02, STATIC = 04 }; Die Zahlen müssen dabei Potenzen von 2 sein. Zugriff auf die Bits wird dann ein Pro- blem, das mit den Operatoren für Bit-Manipulation gelöst werden muß die in Kapitel 2 beschrieben wurden. ' Bestimmte Redewendungen sind sehr häufig. Die Zuweisung flags 1= EXTERNAL I STATIC; setzt die EXTERNAL- und STATIC-Bits in nags auf Eins, flags &= -(EXTERNAL I STATIC); löscht genau diese Bits, und die Bedingung 
144 6 Strukturen 145 If «flags & (EXTERNAL I STATIC» == 0) -. ist genau dann erfüllt, wenn beide Bits gelöscht sind. Obgleich diese Redewendungen leicht zu behalten sind, verfügt C alternativ dazu über die Möglichkeit, Bit-Werte innerhalb eines Worts direkt zu definieren und ZU er- wenden. Ein Bit-Feld ist eine Menge von nebeneinanderliegenden Bits innerhalb einer einzelnen implementierungsabhängigen Speichereinheit, die wir "Wort" nennen olen. Die Syntax für Definition und Zugriff von Bit-Feldern beruht auf Strukturen. Beispiels- weise könnten die oben gezeigten #define-Anweisungen für eine Symboltabelle durch folgende Definition von drei Bit-Feldern ersetzt werden: struct { IXIsIgned Int IXIS I gned I nt IXIS I gned I nt } flags; Hier wird eine Variable Oags definiert, die drei Einzel-Bit-Felder enthält. Die Zahl, die dem Doppelpunkt folgt, repräsentiert die Feldbreite in Bits. Die Bit-Felder werden unsigned int vereinbart, um sicherzustellen, daß es sich um vorzcichenlose Größen han- delt. Der Zugriff auf einzelne Bit-Felder geschieht genau wie bei aner.en Struturko.m- ponenten: Oags.ls _ keyword, Oags.ls _extern us.w. Bit-Feder .funktlmeren Wie lelne ganze Zahlen und können wie andere ganzzahhge Werte m arlthmetlschen Aus.drucken verwendet werden. Die vorhergehenden Beispiele könnte man etwas natürhcher so schreiben: flags.ls_extern = flags.ls_statlc = 1; Is_keyword Is_extern Is_statlc 1. . l' . 1. . 7 Eingabe 'Und Ausgabe Eingabe und Ausgabe sind nicht als Teil der Sprache C selbst definiert, und wir ha- ben dies deshalb bisher weitgehend ausgeklammert. Nichtsdestoweniger reagieren Pro- gramme mit ihrer Umgebung, und zwar wesentlich komplizierter, als wir dies bisher ge- zeigt haben. Im vorliegenden Kapitel beschreiben wir die Standard-Bibliothek. Sie be- steht aus einem Satz von Funktionen für Eingabe und Ausgabe, zum Umgang mit Zei- chenketten und zur Speicherverwaltung. Außerdem enthält sie mathematische Routinen und viele andere Hilfsfunktionen für C-Programme. Wir werden uns auf die Beschrei- bung von Eingabe und Ausgabe konzentrieren, Der ANSI-Standard definiert diese Funktionen sehr exakt, so daß sie in kompatibler Form auf jedem System, auf dem C zur Verfügung steht, existieren können. Programme, die ihr Zusammenspiel mit dem umgebenden System auf die Funktionalität beschränken, die von der Standard-Bibliothek angeboten wird, können unverändert von einem System auf ein anderes gebracht werden. Die Eigenschaften der Bibliotheksfunktionen sind in mehr als einem Dutzend De- finitionsdateien beschrieben; wir haben bereits einige davon gesehen, darunter <stdio.h>, <striDg.h> und <ctype.h>. Wir werden hier nicht die gesamte Bibliothek vorstellen, da wir mehr am Schreiben von C-Programmen interessiert sind, die sie benut- zen. Die Bibliothek wird im Detail im Anhang B beschrieben. 7.1 Standard-Eingabe und Standard-Ausgabe Wie wir bereits in Kapitell erwähnten, implementiert die Bibliothek ein einfaches Modell für Texteingabe und Textausgabe. Ein Textstrom ist eine Folge von Zeilen; jede Zeile endet mit einem Zeilentrenner. Wenn das System selbst nicht so arbeitet, sorgt die Bibliothek dafür, daß es wenigstens so scheint. Zum Beispiel kann die Bibliothek die Zeichenfolge Wagenrücklauf und Zeilenvorschub in einen Zeilentrenner bei der Eingabe abbilden, und dies bei der Ausgabe umkehren. Der einfachste Eingabemechanismus besteht darin, ein einzelnes Zeichen von der Standard-Eingabe, normalerweise von der Tastatur, mit Hilfe von getchar zu lesen: Int getchar(vold) getchar liefert bei jedem Aufruf das nächste Eingabezeichen oder den Wert EOF am Da- teiende. Die symbolische Konstante EOF ist in < stdio.h > definiert. EOF hat typischer- weise den Wert -1, aber Bedingungen sollten mit EOF formuliert werden, damit die Pro- grammtexte unabhängig vom spezifischen Wert bleiben. Bei vielen Systemen kann man eine Datei anstelle der Tastatur angeben, wenn man die Eingabe mit < umlenkt: Wenn ein Programm prog die Routine getchar benutzt, dann sorgt die Kommandozeile prog <Infile dafür, daß prog statt dessen Zeichen aus der Datei infile liest. Die Eingabe wird dabei so umgeschaltet, daß prog selbst die Änderung nicht bemerkt; insbesondere ist die Zei- chenkette ,,<iDOIe" nicht Teil der Kommandoargumente in argv. Die Umschaltung der Eingabe ist ebenfalls unsichtbar, wenn die Eingabe von einem anderen Programm über eine Pipe angeliefert wird: bei einigen Systemen führt die Kommandozcile setzt die Bits, flags.ls_extern . flags.ls_statlc . 0; löscht sie und mit If (flags.ls_extern == 0 && flags.ls_statlc == 0) werden die Bits überprüft. Fast alles bei Bit-Feldern ist implementierungsabhängig. Ob ein Bit-Feld eine Wortgrenze überschreiten kann, hängt von der Implementierung ab. Bit-Flder müssen nicht unbedingt benannt werden; für Zwischenräume können unben.annte Blt-Felde ver- wendet werden, die dann nur aus Doppelpunkt und Angabe zur Breite bestehen. Mit der besonderen Breite 0 kann man die Ausrichtung auf die nächste Wortgrenze verlangen. Bit-Felder werden bei manchen Maschinen von links nach rechts und bei anderen von rechts nach links angeordnet. Bit-Felder sind deshalb zwar nützlich, u intern .defi- nierte Datenstrukturen zu repräsentieren; bildet man jedoch extern definierte Objekte ab, so muß man sorgfältig überlegen, in welcher Reihenfolge die Bit-Felder ngelegt w:r- den. Programme, die von solchen Dingen abhängen, sind nicht portabel. Bit-Felder dur- fen auch nur als Int vereinbart werden; zur Portabilität sollte jedoch explizit slgned oder unsigned angegeben werden. Es gibt keine Vektoren von Bit-Feldern und Bit-Felder ha- ben keine Adressen, so daß der Adreß-Operator & auf sie nicht angewendet werden kann. 
146 ingabe und Ausgabe otherprog I prog zwei Programme otherprog und prog aus und lenkt die Standard-Ausgabe von otherprog zur Standard-Eingabe für prog. Die Funktion int putehar(int) wird zur Ausgabe benutzt: putchar(c) gibt das Zeichen c zur Standard-Ausgabe aus, nach Voreinstellung ist das der Bildschirm, putchar liefert als Resultatwert das ausgege- bene Zeichen oder EOF, wenn ein Fehler auftritt. Auch die Ausgabe kann normalerwei- se in eine Datei gelenkt werden, und zwar mit >dateiname: wenn das Programm prog die Funktion putchar benutzt, sorgt prog >outfHe dafür, daß die Standard-Ausgabe in der Datei outfile abgelegt wird. Gibt es Pipes, dann lenkt prog I anotherprog die Standard-Ausgabe von prog zur Standard-Eingabe von anotherprog. Eine Ausgabe, die mit printe erzeugt wird, taucht ebenfalls als Standard-Ausgabe auf. Aufrufe von putchar und printf können beliebig durcheinander erfolgen - die Aus- gabe erscheint in Reihenfolge der Aufrufe. In jeder Ouelldate in der Eingabe- oder Ausgabefunktionen aus der Bibliothek verwendet werden, muß die Zeile #include <stdio.h> vor dem ersten Aufruf stehen. Wenn der Name in spitzen Klammern < > eingeschlossen ist, wird die DefInitionsdatei an Standardstellen gesucht (bei UNIx-Systemen zum Bei- spiel typischerweise im Katalog jusr jinc/ude). Viele Programme lesen nur einen einzigen Eingabestrom und schreiben nur einen einzigen Ausgabestrom; für solche Programme kann Eingabe und Ausgabe mit Hilfe von getchar, putchar und printf völlig ausreichen, und diese Funktionen genügen sicherlich für den Anfang. Dies gilt ganz besonders, wenn Standard-Eingabe und Standard-Ausga- be umgelenkt werden, um die Ausgabe von einem Programm mit der Eingabe des näch- sten zu verbinden. Betrachten Sie zum Beispiel das Programm /ower, das seine Eingabe in Kleinbuchstaben umwandelt: #include <stdio.h> #include <etype.h> main() /* lower: Eingabe in Kleinbuchstaben umwandeln */ < int e; while «e = getehar(» != EOF) putehar(tolower(e»; return 0; ) Die Funktion tolower ist in < ctype,h > definiert; sie verwandelt einen Großbuch- staben in einen Kleinbuchstaben,. und liefert die anderen Zeichen unverändert. Wie be- . allerdings nicht die Umlaute Ä, Ö und Ü. A.d.Ü. 7.2 Formatierte, jabe - printf 147 reits frer erwähnt, sind "Funktionen" wie getchar und putchar in < stdio.h > und tolower I? < ctype.h > ot Makros, um den Aufwand eines FunktiofiSaufrufs für jedes ein- zelne ZeIchen zu vermelden. In Abschnitt 8.5 werden wir vorführen, wie man diese Ma- kros definiert. Unabhängig davon, wie die Funktionen von< ctype.h > für eine bestimm- te Mashine implementiert sind, müssen Programme, die sie benutzen, den Zeichensatz selbst rocht kennen. Aufgabe 7 -1. Schreiben Sie ein Programm, das Großbuchstaben in Kleinbuchstaben oder Kleinbuchsaben in Großbuchstaben umwandelt, und zwar abhängig vom Pro- gramm namen, mIt dem es aufgerufen wird, und der sich in argv[O] befmdet. 0 7.2 Formatierte Ausgabe - printf Die Ausgabefunktion printf übersetzt interne Werte in Zeichenfolgen. Wir haben pntf oe formale. Beshreibung in den vorhergehenden Kapiteln benutzt. Die Er- arug hier beschretbt dIe meisten typischen Anwendungen, aber sie ist nicht vollstän- dIg; dIe vollständige Beschreibung fmden Sie im Anhang B. int printf(ehar *format, argl. arg2. ...) Unter Konrolle. der Zei.cheette format wandelt die Funktion printf ihre Argumente um, formatIert sIe und gIbt sIe als Standard-Ausgabe aus. Als Resultat liefert printf die Anzahl der ausgegebenen Zeichen. . . D.ie Format-Zeichenkette enthält zwei Arten von Objekten: Gewöhnliche Zeichen, dIe m dIe Ausgabe kopiert werden, und Umwandlungsangaben, die jeweils die Umwand- lung und Ausgabe des nächstfolgenden Arguments von printf veranlassen. Jede Um- wandlunangabe beginnt mit dem Zeichen % und endet mit einem Umwandlungszei- chen. ZWIschen % und Umwandlungszeichen kann, der Reihe nach, folgendes angege- ben werden: · Ein Minuszeichen, damit das umgewandelte Argument nach links ausgerichtet wird. · Ene .Zal, die eine minimale Feldbreite festlegt. Das umgewandelte Argument WIrd m emem Feld ausgegeben, das mindestens so breit ist und bei Bedarf auch breiter. Hat das umgewandelte Argument weniger Zeichen als die Feldbreite verlangt, wird links (oder rechts, wenn Ausrichtung nach links verlangt wurde) auf die Feldbreite aufgefüllt. · Ein Punkt, der die Feldbreite von der Genauigkeit (precision) trennt. · Eine :zahl, de Genauigkeit, die die maximale Anzahl von Zeichen festlegt, die von emer ZeIchenkette ausgegeben werden sollen, oder die Anzahl Ziffern, die nch m Dezimalpunkt bei einem Gleitpunktwert ausgegeben werden, oder dIe mlOlmale Anzahl von Ziffern, die bei einem ganzzahligen Wert ausgegeben werden sollen. · Der Buchstabe h, wenn short ausgegeben werden soll, oder der Buchstabe I wenn das Argument long ist. Die Umwandlungszeichen erklärt Tabelle 7-1. Wenn das Zeichen nach % kein Umwand- lungszeichen ist, ist der Verlauf undefmiert. . 
148 7 19abe und Ausgabe Tabelle 7-1. Elementare printe Umwandlungen Argument; Ausgabe als int; dezimale Zahl. int; oktale Zahl ohne Vorzeichen (ohne führende Null). . int; hexadezimale Zahl ohne Vorzeichen (ohne führendes Ox oder OX), mit abcdef oder ABCDEF für 10, ..., 15. int; dezimale Zahl ohne Vorzeichen int. einzelnes Zeichen. chr *; aus der Zeichenkette werden Zeichen ausgegeben bis vor '\0', oder so viele Zeichen, wie die Genauigkeit verlangt. double; [- )m.dddddd, wobei die Genauigkeit die Anzahl der d festlegt (Voreinstellung ist 6). . . double. [- )m.dddddde:t.xx oder [- )m.ddddddE:t.xx, wobei die GenaUigkeit die.AdI der d festlegt (Voreinstellung ist 6). double. 'fc,e oder %E wird verwendet, wenn der Exponent kleiner als - 4 oder nicht kleiner als die Genauigkeit ist; sonst wird o/cI' benutzt. Null und Dezimalpunkt am Schluß werden nicht ausgegeben. . void *; als Zeiger (Darstellung hängt von Implementierung ab). es wird kein Argument umgewandelt; ein % wird ausgegeben. Als Feldbreite oder Genauigkeit kann jeweils * angegeben werde?; dann wird dr Wert durch Umwandlung des nächsten Arguments festgelegt. (das em nt-Wer sem muß). Zum Beispiel gibt der folgende Aufruf höchstens max Zeichen von emer ZeIchen- kette saus: pri, ntf("".*8" max, 8); Die meisten Umwandlungen wurden in früheren Kapiteln illusiert. Eine Ausah- me ist die Genauigkeit bei Zeichenketten. Die folgende Tabee zeigt den Effekt eer Reihe von Umwandlungsangaben, wenn "hello, world" (12 Zeichen) aw:gegeben d. Wir haben jedes Feld mit Doppelpunkten umgeben, damit Sie den Bereich sehen kon- nen: Zeichen d, i o x,x u e s f e,E g,G p " :Xs: :hello, world: :"'08: :hello, world: :".108: :hello, wor: :"-108: :hello, world: :".15s: :hello, world: :"-158: :hello, world :"'5.'08: hello, wor: '''-15.108: :hello, wor : Ee Warnung: printe benutzt sein erstes Argument, um zu entscheiden, :vie weitere Argumente folgen und um welche Datentypen es sich handelt. printe WIr se verwirrt und Sie erhalten falsche Antworten, wenn nicht genügend Argumente vorh- den sind oder wenn die Datentypen nicht stimmen. Sie sollten sich auch den Unterschied zwischen folgenden zwei Aufrufen klarmachen: . tf( ) . / * FEHLER wenn sein" enthaelt */ pr!n 8, , printf("Xs", s); /* SICHER */ 7.3 Variable Ar ntlisten 149 Die Funktion sprinte nimmt die gleichen Umwandlungen vor wie printe, aber die Ausgabe wird in einer Zeichenkette abgelegt: int sprintf(char *8tring, ehar *format, llIKI, llIK2, ..,) sprinte wandelt die Argumente 0181. 0182, usw. gemäß dem format wie vorher beschrie- ben um, legt aber das Resultat in der Zeichenkette string ab, anstatt es als Standard- Ausgabe auszugeben; string muß groß genug für das Resultat sein. Aufgabe 7 - 2. Schreiben Sie ein Programm, das beliebige Eingaben sinnvoll ausgibt. Mindestens sollten nicht-druckbare Zeichen, je nach lokalen Gebräuchen, oktal oder he- xadezimal ausgegeben werden und lange Textzeilen sollten getrennt werden. 0 7.3 Variable Argumentlisten Dieser Abschnitt enthält eine Implementation einer minimalen Version von printf, um zu zeigen, wie man eine Funktion schreibt, die eine variable Argumentliste portabel bearbeitet. Da wir hauptsächlich an der Bearbeitung der Argumente interessiert sind, wird minprinte nur die kontrollierende Zeichenkette und die Argumente bearbeiten, da- nach aber die echte Funktion printf aufrufen, um die Formatumwandlungen auszu- führen. Die korrekte Vereinbarung von printf ist int printf(char *fmt, ...) wobei die Deklaration H' bedeutet, daß Anzahl und Typen dieser Argumente varüeren können. Die Deklaration ... darf nur am Ende einer Argumentliste auftreten. Unsere Funktion minprinte wird mit void minprintf(char *fmt, .,.) vereinbart: anders als printf liefern wir die Anzahl der ausgegebenen Zeichen nicht als Resultat. Der interessante Teil besteht darin, wie minprintf die Argumentliste abarbeitet, wenn die Liste nicht einmal einen Namen hat. Die Standard-DefInitionsdatei < stdarg.h > enthält einen Satz Makros, die defInieren, wie man durch eine Argumentli- ste läuft. Die Implementierung dieser DefInitionsdatei ist von Maschine zu Maschine verschieden, aber die Schnittstelle, die sie anbietet, ist gleich. Mit dem 1yP va tist vereinbart man eine Variable, die der Reihe nach auf jedes Argument verweist; in- minprintf wird diese Variable ap genannt, als Abkürzung für argument pointer. Der Makro va start initialisiert ap so, daß die Variable auf das erste unbenannte Argument zeigt. Dieser Makro muß einmal aufgerufen werden, bevor ap benutzt wird. Es muß mindestens einen Parameter mit Namen geben; va start benutzt den letzten Parametern amen um anzufangen. - Jeder Aufruf von va arg liefert ein Argument und bewegt ap auf das nächste; va aJ:g benutzt einen 1yPnen, um zu entscheiden, welcher Datentyp geliefert und wie ap-fortgeschrieben wird. Zum Schluß erledigt va end eventuell notwendige Aufräumar- beiten. va_end muß vor dem Verlassen der Funktion aufgerufen werden. 1 
150 7 ngabe und Ausgabe 7.4 Formatierte 'abe - scanf 151 Diese Eigenschaften bilden die Basis unserer vereinfachten Version von printf: #include <stdarg.h> /* minprintf: vereinfachtes printf mit variabler Argumentliste */ void minprintf<char *fmt, ...) ( va_list ap; /* zeigt nacheinander auf char *p, *sval; int ival; double dval; va start(ap, fmt); /* ap zeigt auf 1. unbenanntes Argument */ for (p = fmt; *p; p++) { if (*p != 'X') ( putchar( *p); continue; jedes unbenamte Argument */ scanf hört auf, wenn die Format-Zeichenkette abgearbeitet ist, oder wenn ein Ein- gabefeld nicht zur Umwandlungsangabe paßt. Als Funktionsresultat wird die Anzahl er- folgreich erkannter und zugewiesener Eingabefelder geliefert. Damit kann man ent- scheiden, wieviele Eingaben gefunden wurden. Am Eingabeende wird EOF geliefert; man beachte, daß das von 0 verschieden ist - 0 bedeutet, daß bereits das erste Eingabe- zeichen nicht zur ersten Angabe im Format paßt. Der nächste Aufruf von scanf beginnt seine Suche unmittelbar nach dem zuletzt umgewandelten Zeichen. Es gibt auch eine Funktion sscanf, die aus einer Zeichenkette statt von der Stan- dard-Eingabe liest: int sscanf(char *string, char *format, argt. arg2, ...) sscanf bearbeitet die Zeichenkette string nach den Umwandlungsangaben in format und speichert die Resultatwerte mit Hilfe von a'8t, a'82 usw. Diese Argumente müssen Zei- ger sein. Die Format-Zeichenkette enthält normalerweise Umwandlungsangaben, die zur Interpretation der Eingabe verwendet werden. Die Format-Zeichenkette kann folgendes enthalten: · Leerzeichen oder Tabulatorzeichen, die ignoriert werden. · Gewöhnliche Zeichen (nicht aber %), die dem nächsten Zeichen nach Zwischenraum im Eingabestrom entsprechen müssen. · Umwandlungsangaben, bestehend aus %; einem optionalen Zeichen ., das die Zuwei- sung an ein Argument verhindert; einer optionalen Zahl, die die maximale Feldbreite festlegt; einem optionalen Buchstaben h, I oder L, der die Länge des Ziels beschreibt; und einem Umwandlungszeichen. Eine Umwandlungsangabe bestimmt die Umwandlung des nächsten Eingabefelds. Normalerweise wird das Resultat in der Variablen abgelegt, auf die das zugehörige Argu- ment zeigt. Wenn jedoch · die Zuweisung verhindern soll, dann wird das Eingabefeld übergangen; eine Zuweisung fmdet nicht statt. Ein Eingabefeld ist als Folge von Zeichen definiert, die keine Zwischenraumzeichen sind; es reicht entweder bis zum nächsten Zwi- schenraumzeichen oder bis eine explizit angegebene Feldbreite erreicht ist. Daraus folgt, daß scanf über Zeilengrenzen hinweg liest, um seine Eingabe zu fmden, denn Zeilentren- ner sind Zwischenraumzeichen. (Zwischenraumzeichen sind Leerzeichen, Tabulatorzei- chen \t, Zeilentrenner \n, Wagenrücldauf \r, Vertikal-Tabulator \ v und Seitenvorschub \C). } swi tch (*++p) ( case 'd': ival = va_arg(ap. int); pri ntf( "XeJ". i val); break; case 'f': dval = va_arg(ap, double); printf("Xf", dval); break; case I s · : for (sval = va_arg(ap, char *); *sval; sval++) putchar(*sval); break; default: putchar(*p) ; break; } } va_end(ap); /* hinterher aufraeumen */ } Aufgabe 7-3. Ändern Sie mlnprintf, so daß noch mehr Umwandlungen von prlntrbear- beitet werden. 0 7.4 Formatierte Eingabe - scanf Die Funktion scanf ist die zu printf analoge Eingabefunktion, die praktisch die gleichen Umwandlungen in der umgekehrten Richtung zur Verfügung stellt. int scanf(char *format, ...)' scanf liest Zeichen aus der Standard-Eingabe, interpretiert sie unter Kontrolle von format und legt die Resultate mit Hilfe der übrigen Argumete ab. I?as Frma.argu- ment wird nachstehend beschrieben; die anderen Argumente, die alle Zeiger sem musse, geben an, wo das zugehörige umgewandelte Eingabefeld gespeiche.t erden sol: te bei printf ist der vorliegende Abschnitt eine Zusammenfassung der nutzlichsten Moghch- keiten, aber keine vollständige Liste. Das Umwandlungszeichen gibt die Interpretation des Eingabefelds an. Das zu- gehörige Argument muß ein Zeiger sein, mit Rücksicht auf die Semantik von C, wonach nur Argumentwerte übergeben werden. Umwandlungszeichen zeigt die Tabelle 7-2. Den Umwandlungszeichen d, I, 0, u und x sollte h vorausgehen, wenn das Argu- ment ein Zeiger auf short statt Int ist, oder der Buchstabe I, wenn das Argument ein Zei- ger auf 10ng ist. Analog sollte vor den Umwandlungszeichen e, fund g der Buchstabe I stehen, wenn ein Zeiger auf double und nicht auf Ooat in der Argumentliste steht. 
152 :ingabe und Ausgabe 7.5 Dateizugriff 153 Tabelle 7-2. Elementare scanf Umwandlungen Eingabedaten; Argumenttyp dezimal, gan77.-hlig ; Int -. . ganzzahlig; Int -. Der Wert kann oktal (t 0 am Anfang) oder hexadeZ1ß1al (mit Ox oder OX am Anfang) angegeben sem. oktal ganzzahlig (mit oder ohne 0 am Anfang); Int-. dezimal ohne Vorzeichen; unslgned Int-. hexadezimal ganzzahlig (mit oder ohne Ox oder OX am Anfang); Int-. ein oder mehrere Zeichen; char -. Die nachfolgenden ingabe.zeicen werden im angegebenen Vektor abgelegt, bis die Feldrelt: erreicht Ist; Voreinstellung ist 1. In diesem Fall wird Zwischenraum rocht uberlesen; das nächste Zeichen nach Zwischenraum liest man mit %18. Zeichenkette (ohne Doppelanführungszeichen); cbar -, der auf einen Vektor zeigen muß, der die Zeichenkette und das abschließende '\0' aufnehmen kann, das angehängt wird. Gleitpunktzahl mit optionalem Vorzeichen, optionalem Dezimalpunkt und optionalem Exponenten; Ooat -. erkennt %; eine Zuweisung fmdet nicht statt. Als erstes Beispiel kann der primitive Taschenrechner aus Kapitel 4 jetzt mit scanf zur Eingabe-Umwandlung formuliert werden: #include <stdio.h> main() /* elementarer Taschenrechner */ { Zeichen d e, f,g o scanf ignoriert Leerzeichen und Tabulatorzeichen in der Format-Zeichenkette. Außerdem werden Zwischenraumzeichen (Leerzeichen, Tabulatorzeichen, Zeilentrenner usw.) in der Eingabe überlesen, wenn nach Eingabewerten gesucht wird. Um eine Ein- gabe zu verarbeiten, deren Format nicht exakt festgelegt ist, liest man oft am besten eine Zeile auf einmal und zerlegt sie dann mit sscanf. Wenn wir beispielsweise Zeilen verar- beiten möchten, die Datumsangaben in jeder der beiden oben gezeigten Arten enthalten, dann könnten wir das folgendermaßen formulieren: while (getline(line, sizeof(line» > 0) { if (sscanf(line, IlXej Xs Xejll, &day, monthname, &year) "" 3) printf(lIvalid: Xs\n", line); /* Art: 25 Dec 1988 */ else if (sscanf(line, "Xej/Xej/Xej", &month, &day, &year) "" 3) printf(lIvalid: Xs\n", line); /* Art: lTIfI/dd/yy */ else printf("invalid: Xs\n", line); /* ungueltig */ u x c s } Aufrufe von scanf und Aufrufe anderer Eingabefunktionen können durcheinander erfolgen. Der nächste Aufruf irgendeiner Eingabefunktion beginnt damit, daß das erste Zeichen gelesen wird, das scanf noch nicht gelesen hat. Eine Warnung zum Schluß: Die Argumente von scanf und sscanf mÜssen Zeiger sein. Bei weitem der häufigste Fehler ist, daß man scanf(IIXejIl, n); " anstelle von double SUII, v; sun " 0; while (scanf(""lf", &v) == 1) printf(l\t".2f\n". sun +" v); return 0; scanf(IIXejIl, &n); schreibt. Dieser Fehler wird im allgemeinen nicht bei der Übersetzung erkannt. Aufgabe 7 - 4. Schreiben Sie eine eigene Version von scanf analog zu minprintf aus dem letzten Abschnitt. 0 Aufgabe 7 - S. Ändern Sie den Postfix- Taschenrechner aus Kapitel 4 so ab, daß scanf be- ziehungsweise sscanf für Eingabe und Umwandlung verwendet werden. 0 7.5 Dateizugriff Bisher haben alle Beispiele von der Standard-Eingabe gelesen und in die Stan- dard-Ausgabe geschrieben, die automatisch für ein Programm vom lokalen Betriebssy- stem vordefmiert sind. Als nächsten Schritt schreiben wir ein Programm, das auf eine Datei zugreift, die noch nicht mit dem Programm verbunden ist. Ein Programm, das die Notwendigkeit sol- cher Operationen illustriert, ist cat., das eine Reihe von namentlich angegebenen Datei- en nacheinander als Standard-Ausgabe ausgibt. Mit cat gibt man Dateien auf dem Bild- schirm aus und produziert Eingabe für Programme, die selbst nicht per Name auf Datei- en zugreifen können. Beispielsweise gibt das Kommando cat x.c y.c den Inhalt der Dateienx.c undy.c (und sonst nichts) als Standard-Ausgabe aus. } Angenommen, wir wollen Eingabezeilen verarbeiten, die Datumsangaben wie 25 Dec 1988 enthalten. Die scanf-Anweisung dazu ist int day, year; char monthname[20]; scanf(" Xs Xejll, &day, monthname, &year); Der Adreß-Operator & wird vor montbname nicht angegeben, da ein Vektorname be- reits ein Zeiger ist. Die Format-Zeichenkette von scanf kann gewöhnliche Zeichen enthalten; sie müs- sen dann genauso in der Eingabe vorkommen. Datumsangaben wie mm/dd/yy könnten wir mit dem folgenden Aufruf von scanf verarbeiten: int day, month, year; scanf ("Xej/Xej/", &month, &day, &year); . Ci1I steht rur Ci1IenOle, also "verketten" A.d.Ü. 
154 '.ingabe und Ausgabe 7.5 Dateizugriff 155 Die Frage ist, wie man dafür sorgt, daß die namentlich angegebenen Dateien gele- sen werden - das heißt, wie verbindet man die externen Namen, die der Benutzer wählt, mit den Anweisungen, die die Daten lesen. Die Regeln sind einfach. Bevor eine Datei gelesen oder geschrieben werden kann, muß der Zugriff mit der Bibliotheksfunktion fopen eröffnet werden. fopen akzeptiert einen externen Namen, wiex.c oder y.c, führt Buch und verhandelt mit dem Betriebssy- stem (wobei die Details uns nicht zu interessieren brauchen) und liefert als Funktions- wert einen Zeiger, der anschließend beim Lesen oder Schreiben der Datei verwendet wird. . Als nächstes benötigen wir eine Möglichkeit, um eine Datei zu lesen oder zu schreiben, nachdem der Zugriff eröffnet wurde. Es gibt dazu verschiedene Funktionen am einfachsten sind getc und pute. Die Funktion getc liefert das nächste Zeichen aus ei ner Datei; dazu benötigt die Funktion den FILE-Zeiger, um die richtige Datei anzuspre- chen. Dieser sogenannte FILE-Zeiger zeigt auf eine Struktur, die Information über die Datei enthält, wie zum Beispiel die Adresse eines Puffers, die aktuelle Zeichenposition im Puffer sowie Angaben, ob die Datei gelesen oder geschrieben wird, ob Fehler aufge- treten sind und ob das Dateiende erreicht wurde. Die Benutzer brauchen die Details nicht zu wissen, da zu den Vereinbarungen aus < stdlo.h > auch eine Strukturdefmition für FILE gehört. Die allein nötigen Vereinbarungen für einen FILE-Zeiger sieht man an folgendem Beispiel: FILE *fp; FILE *fopen(ehar *name. ehar *mode); Diese Vereinbarung legt fest, daß fp ein Zeiger auf FILE ist und daß fopen einen entspre- chenden Zeiger liefert. Man beachte, daß FILE ein Datentypname wie int ist und nicht das Etikett einer Struktur; FILE ist mit Hilfe von typedef defIniert. (Details darüber, wie fopen im UNIx-System implementiert werden kann, befmden sich im Abschnitt 8.5.) Copen wird in einem Programm folgendermaßen aufgerufen: fp = fopenename. mode); Das erste Argument von Copen ist eine Zeichenkette, die den Namen der Datei enthält. Das zweite Argument ist die Zugriffsart , ebenfalls eine Zeichenkette, die angibt, wie man auf die Datei zugreifen will. Erlaubt sind unter anderem "... für Lesen, "w" für Schreiben und "a" für Anfügen. Manche Systeme unterscheiden zwischen Text- und Binärdateien; bei letzteren muß "b" an mode angehängt werden. Wenn eine nicht-existente Datei zum Schreiben oder Anfügen eröffnet wird, so wird sie erzeugt, falls das möglich ist. Wird eine existente Datei zum Schreiben eröffnet, geht der alte Inhalt verloren; beim Öffnen zum Anfügen bleibt der Inhalt erhalten. Ein Versuch, eine Datei zu lesen, die nicht existiert, ist ein Fehler, und es gibt auch andere Fehlermöglichkeiten, wie zum Beispiel den Versuch, eine Datei zu lesen, wenn man dazu nicht berechtigt ist. Bei Fehlern liefert fopen den Wert NULL. (Ein Fehler kann genauer identifIZiert werden; siehe dazu die Erklärungen der Funktionen zur Fehlerbehandlung im Abschnitt B.1.7 im Anhang B.) int gete(FILE *fp) gete liefert das nächste Zeichen aus dem Datenstrom den Cp bezeichnet. am Dateiende oder bei Fehler ist das Resultat EOF. ' , pute ist eine Ausgabefunktion: int pute(int e. FILE *fp) pute gibt das Zeichen c in die Datei fp aus und liefert das ausgegebene Zeichen als Re- sultatwert oder EOF, wenn ein Fehler auftritt. Wie getehar und putehar können auch gete und pute Makros statt Funktionen sein. We die usfg eines C-Programms beginnt, muß die Betriebssystem-Um- gebung dr:1 DateIVebmungen eröffnen und entsprechende FILE-Zeiger zur Verfügung stellen. Diese Datelverbmdungen sind Standard-Eingabe, Standard-Ausgabe und Dia- ose-Ausgabe. Die zugehörigen FILE-Zeiger heißen stdin, stdout und stderr; sie sind m < stdio.h > vereinbart. Normalerweise ist stdin mit der Tastatur verbunden und stdout sowie stderr führen zum Bildschirm, aber stdin und stdout können in Dteien oder Pipes umgelenkt sein, wie das in Abschnitt 7.1 beschrieben wurde. getehar und putchar können mit Hilfe von getc, putc, stdin und stdout folgender- maßen defIniert werden; #define getehar() gete(stdin) #define putehar(e) pute«e). stdout) Für formatierte Eingabe oder Ausgabe bei Dateien können die Funktionen CscanC und CprintC benutzt werden. Diese Funktionen werden exakt wie SC8nC und printC ver- wend.et, aesehen .davon, daß das erste Argument jeweils ein FILE-Zeiger ist, der die Datei zelchnet, die gelesen oder geschrieben werden soll; die Format-Zeichenkette ist das zweite Argument. int fseanf(FILE *fp. ehar *format. ...) int fprintf(FILE *fP. ehar *format. ...) . Nach esen Vobemerkungen können wir jetzt das Programm cat schreiben, das Datlen ane',l'anderreiht. Der Entwurf hat sich schon für viele Programme als nützlich fW1esen: Gibt es Argumente in der Kommandozeile, dann werden sie als Dateinamen mterpretlert und der Reihe nach bearbeitet. Gibt es keine Argumente dann wird die Standard-Eingabe bearbeitet. ' 
156 7 gabe und Ausgabe 7.6 Fehlerbehand  - stde" und exit 157 #include <stdio.h> /* cat: Dateien nacheinander ausgeben, Version 1 */ main(int argc, char *argv[]) ( FILE *fp; void filecopy(FILE *, FILE *); if (argc == 1) /* kein Argument; Standardeingabe kopieren */ filecopy(stdin, stdout); else while (--argc > 0) if «fp = fopen(*++argv, "r"» == NULL) ( printf("cat: can't open Xs\n", *argv); return 1; } else ( filecopy(fp, stdout); fclose(fp); . Um dieser Situation besser begegnen zu können, erhält jedes Programm einen zweiten Ausgabestrom für Diagnosen namens stderr, zusätzlich zu stdin und stdout, vom Betriebssystem zugewiesen. Ausgabe für stderr erscheint normalerweise auch dann am Bildschirm, wenn die Standard-Ausgabe umgelenkt wurde. Ändern wir cat, so daß die Fehlermeldungen als Diagnose-Ausgabe erscheinen: 'include <stdio.h> /* cat: Dateien nacheinander ausgeben, Version 2 */ mein(int argc, char *argv[]) ( int c; FILE *fp; void filecopy(FILE *, FILE *); char *prog = argv[O]; /* Progranmname fuer Fehlermeldungen */ if (argc -- 1) /* kein Argument; Standard Eingabe kopieren */ fllecopy(stdin, stdout); else while (--argc > 0) if «fp = fopen(*++argv, "r"» =- NULL) ( fprlntf(stderr, "Xs: can't open Xs\n", prog, *argv); exit(1); } else ( filecopy(fp, stdout); fclose(fp); } return 0; } /* filecopy: kopiere Datei ifp in Datei ofp */ void filecopy(FILE *ifp, FILE *ofp) { while «c = getc(ifp» 1= EOF) putc(c, ofp); ) if (ferror(stdout» ( fprintf(stderr, "Xs: error writing stdout\n", prog); exit(2); } Die FILE-Zeiger stdin und stdout sind Objekte vom Datentyp FILE *. Sie sind jedoch Konstanten, keine Variablen, deshalb kann man nicht an sie zuweisen. Die Funktion int fclose(FILE *fp) ist das Gegenteil von fopen; sie löst die Verbindung zwischen einem FILE-Zeiger und dem externen Namen, die fopen eingerichtet hat; der FILE-Zeiger kann für eine andere Datei verwendet werden. Da die meisten Betriebssysteme die Anzahl Dateien begren- zen, die ein Programm gleichzeitig eröffnet haben kann, ist es eine gute Idee, FILE- Zeiger freizugeben, wenn sie nicht länger benötigt werden, wie wir dies in cat getan ha- ben. Es gibt noch einen Grund für fdose bei einer Ausgabedatei - der Puffer wird aus- gegeben, in dem pute die Ausgabe sammelt. Geht ein Programm normal zu Ende, wird fdose am Schluß automatisch für jede offene Datei aufgerufen. (Sie können stdin und stdout freigeben, wenn sie nicht gebraucht werden. Mit der Bibliotheksfunktion freopen können sie auch mit einer anderen Datei verbunden werden.) } exit(O); 7.6 Fehlerbehandlung - stderr und exit Die Fehlerbehandlung in cat ist nicht ideal. Kann aus irgendeinem Grund auf eine der Dateien nicht zugegriffen werden, so wird die Fehlermeldung leider am Ende der bis- herigen Ausgabe angefügt. Dies könnte akzeptabel sein, wenn die Ausgabe zum Bild- schirm geht, jedoch nicht, wenn sie in eine Datei oder per Pipe zu einem anderen Pro- gramm geht. } Das Programm teilt Fehler auf zwei Arten mit: Erstens erscheinen Fehlermeldun- gen, die mit fprintf erzeugt werden, als stderr und werden deshalb am Bildschirm ausge- geben; sie verschwinden nicht in einer Pipe oder Ausgabedatei. Wir haben den Pro- grammnen aus argv[O] mit in die Meldung aufgenommen; wenn das Programm zu- sammen mit anderen ausgeführt wird, kann die Fehlerquelle identifiziert werden. Zweitens benutzt das Programm die Funktion exit aus der Standard-Bibliothek, die bei Aufruf die Programmausführung beendet. Das Argument von exit steht dem Prozeß zur Verfügung, der den vorliegenden Prozeß aufgerufen hat; auf diese Weise kann Erfolg oder Mißerfolg unseres Programms von einem anderen Programm abgeprüft werden, das unser Programm als Teilprozeß benutzt. Nach Konvention bedeutet 0 als Resultat daß alles funktioniert hat; von 0 verschiedene Werte signalisieren normalerweise unvor hergesehene Umstände. exit ruft fdose für jede offene Ausgabedatei auf, um etwa ge- pufferte Ausgaben noch zu schreiben. In der Funktion main ist return expr äquivalent zu exit(expr). Die Funktion exit hat den Vorteil, daß sie von anderen Funktionen aufgerufen werden kann, und daß Auf- rufe von exit mit Suchprogrammen wie denen in Kapitel 5 gefunden werden können. 
158 . Singabe und Ausgabe 7.8 Weitere Fur )nen Die Funktion ferror liefert einen von Null verschiedenen Wert, wenn beim Strom fp ein Fehler aufgetreten ist. int ferror(FllE *fp) Obwohl Ausgabefehler selten sind, kommen sie doch vor (zum Beispiel, wenn eine Platte voll wird), also sollte ein Produktionsprogramm das auch prüfen. Analog zu ferror liefert die Funktion feof(FlLE *) einen von Null verschiedenen Wert, wenn das Dateiende bei der angegebenen Datei gefunden wurde. int feof(FllE *fp) In unseren kleinen Beispielprogrammen haben wir haben uns im allg:meinen .cht um den exit-Status gekümmert, aber jedes ernsthafte Programm sollte sich bemühen, sinnvolle und nützliche Statuswerte zu liefern. 159 /* fputs: Zeichenkette s an iop ausgeben */ int fputs(ehar *s, FilE *iop) { int c; whi le (e = *s++) pute(e, iop); return ferror(iop) ? EOF : 0; > Der Standard legt fest, daß ferror bei Fehler einen von Null verschiedenen Wert liefert; fputs liefert EOF bei Fehler und einen nicht-negativen Wert sonst. Unser getline kann man leicht mit fgets implementieren: /* getline: eine Zeile lesen, laenge liefern */ int getline(char *line, int max) ( 7.7 Zeilen-Eingabe und -Ausgabe In der Standard-Bibliothek gibt es eine Eingabefunktion fgets, die der getline- Funktion gleicht, welche wir in früheren Kapiteln verwendet haben: ehar *fgets(ehar *line, int maxline, FilE *fp) fgets liest die nächste Eingabezeile (und auch den Zeilentren.ner) aus der Dat:i fp in dn Zeichenvektor Une' dabei werden höchstens maxline -1 Zeichen gelesen. Die resultie- rende Zeile wird n:it '\0' abgeschlossen. Normalerweise liefert fgets den Zeiger line als Resultat; am Dateiende oder bei Fehlern ist der Resultatwert aber NULL. (Unsere getline-Funktion liefert die Länge der eingelesenen Zeile, was nützlicher ist; 0 bedeutet Dateiende.) Als Ausgabefunktion schreibt fputs eine Zeichenkette (die keinen Zeilentrenner enthalten muß) in eine Datei: int fputs(ehar *line, FilE *fp) Sie liefert EOF, wenn ein Fehler auftritt, und sonst O. Die Bibliotheksfunktionen gets und puts funktionieren ähnlich wie fgets und fputs, aber sie verwenden stdin und stdout. Ein bißchen verwirrt, daß gets den abschließenden Zeilentrenner '\n' entfernt, während puts ihn hinzufügt. Um zu demonstrieren, daß Funktionen wie fgets und fputs nichts Besonderes sind, zeigen wir sie hier, kopiert aus der Standard-Bibliothek unseres Systems: /* fgets: hoeehstens n Zeichen von iop einlesen */ ehar *fgets(ehar *s, int n, FilE *iop) { if (fgets(line, max, stdin) == NUll) return 0; else return strlen(line); > Aufgabe 7 -6. Schreiben Sie ein Programm, das zwei Dateien vergleicht und die erste Zeile ausgibt, wo sie verschieden sind. 0 Aufgabe 7-7, Ändern Sie das Programm zur Mustersuche aus KapitelS so ab, daß die Eingabe aus einer Reihe von explizit angegebenen Dateien kommt; sind keine Dateien als Argumente angegeben, soll die Standard-Eingabe verwendet werden. Sollte der Da- teiname ausgegeben werden, wenn eine gesuchte Zeile gefunden wird? 0 Aufgabe 7-8. Schreiben Sie ein Programm, das eine Reihe von Dateien ausgibt, wobei für jede Datei eine neue Seite begonnen wird; zu jeder Datei soll es einen laufenden Sei- tentitel und eine Seitennumerierung geben. 0 7.8 Weitere Funktionen Die Standard-Bibliothek stellt eine Vielzahl von Funktionen zur Verfügung. In die- sem Abschnitt werden die nützlichsten kurz beschrieben. Mehr Details und viele andere Funktionen sind im Anhang B zu finden. 7.8.1 Operationen mit ZeichenkeUen Wir haben die Zeichenketten-Funktionen strlen, strcpy, strcat und strcmp bereits erwähnt, die man in < string.h > findet. Im folgenden sind sund t Zeiger auf char und c und n sind int. register int e; register eh ar *es; streat(s,t) strncat(s, t,n) strc(s,t) es = S' while c--n > 0 && (e = gete(iop» != EOF) if «*es++ = e) == '\n') break; *es = '\0'; return (e == EOF && es == s) ? NUll s; strne(s,t,n) strepy(s, t) strncpy(s,t,n) strlen(s) strehr(s,e) strrchr(s,e) > hängt t an das Ende von s an hängt n Zeichen von t an s an liefert negativ oder Null oder positiv je nachdem ob s < t, s == t, oder s > t ist wie strcmp, aber nur für die ersten n Zeichen kopiert t nach s kopiert höchstens n Zeichen von t nach s liefert Länge von sohne '\0' am Schluß liefert Zeiger auf erstes c in s, oder NULL falls nicht da liefert Zeiger auf letztes c in s, oder NULL falls nicht da 
160 " ngabe und Ausgabe 7.8 Weitere Fun nen 161 7.8.2 Tests fiir Zeichenklassen und Umwandlung Eine Reihe von Funktionen aus <ctype.h> dienen zur KlassifIkation und Um- wandlung von Zeichen.. Im folgenden ist c ein int-Wert, der als unsigned char repäsen- tiert werden kann, oder EOF. Die Funktionen liefern int. isalpha(c) nicht Null, wenn c ein Buchstabe ist, sonst 0 isupper(c) nicht Null, wenn c ein Großbuchstabe ist, sonst 0 ;slower(c) nicht Null, wenn c ein Kleinbuchstabe ist, sonst 0 isdigi t(c) nicht Null, wenn c eine Ziffer ist, sonst 0 isalnun(c) nicht Null, wenn Isalpha(c) oder Isdigit(c) gilt, sonst 0 isspace(c) nicht Null, wenn c ein Leerzeichen, Tabulatorzeichen, Zeilentrenner, Wagenrücklauf, Seitenvorschub oder Vertikal-Tabulator ist, sonst 0 liefert c, umgewandelt in Großbuchstaben return c, umgewandelt in Kleinbuchstaben toupper(c) tolower(c) void *calloc(size_t n, size_t size) liefert einn Z<:iger auf enüge Speiche für einen Vektor von n Objekten der angege- benen röße SIZe. Ist rocht genugend Speicher vorhanden, so ist der Resultatwert NULL. Der Speicher wird mit 0 initialisiert. .. Der Zeigr, den malloc oder calloc liefern, hat die korrekte Ausrichtung für das gewünschte Objekt, muß aber noch mit einer Umwandlungsoperation in den richtigen Datentyp umgewandelt werden, zum Beispiel int *ip; ip = (int *) calloc(n, izeof(int»; ..free.(p) stellt.den Speicher wieder zur Verfügung, auf den p zeigt; dabei stammt p urs?runglich vo? emem Aufruf von malloc oder calloc. Speicher kann zwar in beliebiger Reihenfolge freigegeben werden; es ist jedoch ein entsetzlicher Fehler wenn etwas frei- gegeben wird, was nicht von calloc oder malloc stammt. ' . Es ist auch falsch, wenn etwas benutzt wird, das schon freigegeben wurde. Ein ty- plSch:s, abe ehlerhaftes Programmfragment ist folgende Schleife, die Elemente aus ei- ner LISte freigibt: for (p - head; p ,- NULL; P - p->next) /* FALSCH */ free(p); Korrekterweise kopiert man das, was noch benötigt wird, vor der Freigabe: for (p = head; p 1= NULL; P = q) ( q = p->next; free(p) ; 7.8.3 ungetc In der Standard-Bibliothek gibt es auch eine sehr eingeschränkte Fassung der Funktion ungeteh, die wir im Kapitel 4 geschrieben haben; hier heißt die Funktion ungete. int ungetc(int c, FILE *fp) stellt das Zeichen c in die Datei fp zurück und liefert entweder c oder EOF bei Fehler. Nur ein Zeichen kann garantiert für jede Datei zurückgestellt werden. ungete kann in Verbindung mit allen Eingabefunktionen wie scanr, gete oder getehar verwendet werden. 7.8.4 Kommandoausrtihrung Die Funktion system(char .s) führt das Kommando aus, das in der Zeichenkette s enthalten ist, und setzt dann die Ausführung des momentanen Programms fort. Der In- halt der Zeichenkette s hängt stark vom lokalen Betriebssystem ab. Als triviales Beispiel führt bei UNIx-Systemen der Aufruf system(lIdate ll ); } in Meus zu.' Speicherverwaltung wie malloc, bei dem Speicher in beliebi- ger Reihenfolge WIeder fretgegeben werden kann, wird in Abschnitt 8.7 vorgeführt. 7.8.6 Mathematische Funktionen . ehr  anzig mathematische Funktionen sind in < math.h > deklariert; hier smd :1IDge, e haufiger benutzt werden. Jede benötigt ein oder zwei double-Argumente und liefert em double-Resultat. sin(,t) Sinus von x, x im Bogenmaß COS(,t) Kosinus von x, x im Bogenmaß atan2<y,x) Arcustangens vony/x,y/x im Bogenmaß exp(,t) Exponentialfunktion e" log(,t) natürlicher Logarithmus (Basis e) von x (x>O) log10(,t) gewöhnlicher Logarithmus (Basis 10) von x (x>O) pow(,t,y) x y sqrt(,t) Quadratwurzel von x (x) fabs(,t) absoluter Wert von x 7.8.7 Zufallszahlengenerator . De Funktion rand() berechnet eine Folge von ganzzahligen Pseudo-Zufallszahlen UD Bereich Null bis RAND _ MAX; der Grenzwert RAND _MAXist in < stdlib.h > definiert. das date-Programm aus; dieses Programm schreibt Datum und Uhrzeit in seine Stan- dard-Ausgabe. system liefert einen system abhängigen g :m7:7.ahIig en Statuswert vom aus- geführten Kommando. Bei einem UNIX-System ist der Statuswert der Wert, der von exit geliefert wird. 7.8.5 Speicherverwaltung Die Funktionen malloc und calloc beschaffen Speicherblöcke dynamisch. void *malloc(size_t n) liefert einen Zeiger auf einen (uninitia1isierten) Speicherbereich von n Bytes, oder NULL, wenn nicht genügend Speicher vorhanden ist. . Nationale Zeichen wie Umlaute oder Akzente zählen nicht zu den Buchstaben. A.d.Ü. 
162 7 19abe und Ausgabe 163 Gleitpunkt-Zufallszahlen, die größer oder gleich Null und kleiner als Eins sind, kann man folgendermaßen erzeugen: #detine trendO «double) randO I (RAND_MAX+1.0» (Wenn Ihre Bibliothek bereits eine Funktion für Gleitpunkt-Zufallszahlen enthält, hat sie vermutlich bessere statistische Eigenschaften als diese.) Die Funktion srand(unslgned) hinterlegt den Ausgangswert für rand. Die portable Implementierung von rand und srand, die der Standard vorschlägt, steht im Abschnitt 2.7. Aufgabe 7-9. Funktionen wie isupper können so implementiert werden, daß sie entwe- der Platz oder Zeit sparen. Untersuchen Sie beide Möglichkeiten. 0 8 Die Schnittstelle zum UNIX-Betriebssystem Das UNIX-Betriebssystem stellt seine Dienste als Satz von Systemaufrufen zur Ver- fügung, die praktisch Funktionen im Betriebssystem sind, die ein Benutzerprogramm auf- rufen kann. Dieses Kapitel beschreibt, wie man einige der wichtigsten Systemaufrufe in C-Programmen benutzt. Wenn Sie UNIX verwenden, sollte dies für Sie eine unmittelbare Hilfe sein, denn manchmal muß man System aufrufe benutzen, um die größtmögliche Ef- fIZienz zu erzielen oder um auf Dienste zuzugreifen, die nicht in der Standard-Bibliothek enthalten sind. Aber auch wenn Sie C mit einem anderen Betriebssystem verwenden, sollten Sie weitere Einblicke in die C-Programmierung durch das Studium dieser Beispie- le gewinnen können; obwohl die Details verschieden sind, fmdet man ähnliche Pro- grammtexte bei jedem Betriebssystem. Da die ANSI-C-Bibliothek sich in vielen Fällen an den Möglichkeiten von UNIX orientiert, können diese Beispiele auch zu Ihrem Verständ- nis der Bibliothek beitragen. Das Kapitel ist in drei wesentliche Teile unterteilt: Eingabe und Ausgabe, Dateisy- stem und Speicherverwaltung. In den ersten beiden Teilen nehmen wir an, daß Sie ein wenig mit den externen Charakteristika von UNIx-Systemen vertraut sind. Kapitel 7 beschäftigte sich mit einer Schnittstelle für Ein- und Ausgabe, die über Betriebssysteme hinweg einheitlich zur Verfügung steht. Bei jedem einzelnen System müssen die Funktionen der Standard-Bibliothek mit Hilfe der Möglichkeiten implemen- tiert werden, über die das Betriebssystem verfügt. In den nächsten Abschnitten beschrei- ben wir die UNIX-Systemaufrufe für Eingabe und Ausgabe und zeigen, wie man damit Teile der Standard-Bibliothek implementieren kann. 8.1 File-Deskriptoren Im UNIx-Betriebssystem erfolgt die gesamte Ein- und Ausgabe durch Lesen oder Schreiben von Dateien, denn alle Peripheriegeräte, sogar Tastatur und Bildschirm, sind Dateien im Dateisystem. Dies bedeutet, daß die gesamte Kommunikation zwischen ei- nem Programm und peripheren Geräten über eine einzige, einheitliche Schnittstelle er- folgt. Im allgemeinsten Fall müssen Sie, bevor Sie eine Datei lesen oder schreiben, Ihre Absicht dem System mitteilen; dies wird als Eröffnen der Datei bezeichnet. Wenn Sie in eine Datei schreiben wollen, muß diese Datei möglicherweise erst erzeugt werden, oder ihr bisheriger Inhalt muß beseitigt werden. Das System prüft, ob Sie dies dürfen: Exi- stiert die Datei? Sind Sie berechtigt, auf diese Datei zuzugreifen? Ist alles in Ordnung, so erhält das Programm eine kleine, nicht-negative Zahl, einen sogenannten File- Deskriptor. Für jede Eingabe von oder Ausgabe zu dieser Datei wird dann der File-De- skriptor anstelle des Namens benutzt, um die Datei zu identifIZieren. (Ein File-Deskrip- tor entspricht dem FILE-Zeiger der Standard-Bibliothek oder dem file handle von MS- DOS.) Alle Informationen über eine geöffnete Datei werden vom System unterhalten; das Benutzerprogramm verweist auf die Datei nur mit dem File-Deskriptor. Da Ein- und Ausgabe mit Tastatur und Bildschirm so häufig vorkommen, wurden besondere Vorkehrungen getroffen, um dies bequem zu machen. Wenn der Kommando- prozessor (die ,,shell") ein Programm ausführen läßt, sind bereits drei Dateiverbindun- gen eröffnet, mit den File-Deskriptoren 0, 1 und 2, die als Standard-Eingabe, Standard- 
164 8 Die Schnittstelle zun fiX-Betriebssystem Ausgabe und Diagnose-Ausgabe bezeichnet werden. Wenn ein Programm vom File-De- skriptor 0 liest und auf 1 oder 2 schreibt, kann es ein- und ausgeben, ohne selbst Dateien eröffnen zu müssen. Der Benutzer eines Programms kann Standard-Eingabe und Standard-Ausgabe zu Dateien mit Hilfe von < und > umlenken: prog <infile >outfile In diesem Fall ändert die Shell die Voreinstellung der File-Deskriptoren 0 und 1 und ver- knüpft sie mit den angegebenen Dateien. Der File-Deskriptor 2 bleibt normalerweise mit dem Bildschirm verbunden, damit Fehlermeldungen dorthin gehen. Ähnliches gilt, wenn Standard-Eingabe oder Standard-Ausgabe mit einer pipe verbunden werden. In al- len Fällen werden die Dateiverbindungen von der Shell und nicht vom Programm selbst geändert. Das Programm weiß nicht, woher seine Eingabe kommt oder wohin seine Ausgabe geht, wenn die Ftle-Deskriptoren 0 für Eingabe und 1 und 2 für Ausgabe ver- wendet werden. 8.2 Elementare Ein. und Ausgabe - read und write Zur Ein- und Ausgabe werden die System aufrufe read und write benutzt, auf die C-Programme mit zwei Funktionen namens read und wrlte zugreifen. Bei beiden ist das erste Argument ein File-Deskriptor. Das zweite Argument ist ein Zeichenvektor in Ih- rem Programm, in den oder aus dem Daten transferiert werden sollen. Das dritte Argu- ment ist die Anzahl Bytes, die transferiert werden soll. int n_read = read(int fd, char *buf, int n)i int n_written = write(int fd, char *buf, int n)i Jeder Aufruf liefert die Anzahl Bytes, die transferiert wurden. Beim Lesen kann der Re- sultatwert kleiner sein als die gewünschte Anzahl. Liefert read das Resultat 0, bedeutet dies, daß das Dateiende erreicht wurde. Ist das Resultat -1, so ist irgendein Fehler pas- siert. Beim Schreiben ist der Resultatwert die Anzahl Bytes, die geschrieben wurden; ein Fehler liegt vor, wenn das nicht die verlangte Anzahl ist. Jede Anzahl Bytes kann mit einem einzigen Aufruf gelesen oder geschrieben wer- den. Die häufigsten Werte sind 1, also ein Zeichen auf einmal (..nicht gepuffert"), oder eine Anzahl wie 1024 oder 4096, die der physikalischen Blocklänge von Peripheriegeräten entspricht. Größere Werte sind eff1zienter, da weniger System aufrufe gemacht werden. Wenn wir dies alles zusammenfassen, können wir ein einfaches Programm schrei- ben, das seine Eingabe zur Ausgabe kopiert, also das Äquivalent des Dateikopierpro- gramms, das wir in Kapitel 1 geschrieben haben. Dieses Programm kopiert von überall nach überall, da Eingabe und Ausgabe zu beliebigen Dateien oder Geräten umgelenkt werden können. #include "syscalls.h" main() /* Eingabe in die Ausgabe kopieren */ { char buf[BUFSIZJi int ni while «n = read(O, buf, BUFSIZ» > 0) write(1, buf, n)i return Oi } 8.3 open, creat, cl, unlink 165 Wir haben Funktionsprototypen für die System aufrufe in einer Datei namens syscalls.b gesammelt, damit wir sie in die Programme in diesem Kapitel einfügen kön- nen. Dieser Name ist jedoch nicht Teil des Standards. Der Parameter BUFSIZ ist auch in syscalls.b definiert; sein Wert eignet sich als Puffergröße für das lokale System.. Ist die Dateigröße kein Vielfaches von BUFSIZ, so liefert ein Aufruf von read schließlich eine kleinere Anzahl Bytes, die von write ausgege- ben werden sollen; danach liefert der nächste Aufruf von read den Wert O. Es ist instruktiv, wenn man sieht, wie man mit read und write komplexere Funktio- nen wie getcbar, putcbar usw. konstruiert. Hier ist zum Beispiel eine Version von getcbar, bei der die Eingabe nicht gepuffert ist. Von der Standard-Eingabe wird ein Zei- chen auf einmal gelesen. #include "syscalls.h" /* getchar: ungepufferte Eingabe einzelner Zeichen */ int getchar(void) { char Ci return (read(O, &c, 1) == 1) ? (unsigned char) c : EOFi } c muß als cbar vereinbart werden, da read einen Zeiger auf cbar als zweites Argument erwartet. Wenn man c in der return-Anweisung in den Typ unsigned cbar umwandelt, vermeidet man Probleme durch Propagierung des Vorzeichens. Die zweite Version von getcbar liest ein größeres Stück der Eingabe und verteilt als Resultatwert die einzelnen Zeichen der Reihe nach. #include "s y s c alls.h" /* getchar: gepufferte Eingabe einzelner Zeichen, einfache Version */ int getchar(void) { static char buf[BUFSIZJi static char *bufp = bufi static int n = Oi if (n == 0) { /* Puffer ist leer */ n = read(O. buf, sizeof buf)i bufp = buf i } return (--n >= 0) ? (unsigned char) *bufp++ : EOF; } Übersetzte man diese Versionen von getchar mit der Definitionsdatei < stdio.b >, so müßte man den Namen getchar mit #under entfernen, denn er könnte als Makro imple- mentiert sein. 8.3 open, creat, close, unlink Abgesehen von der Standard-Eingabe, Standard-Ausgabe und Diagnose-Ausgabe müssen Sie Dateien explizit eröffnen, um sie zu lesen oder zu schreiben. Dazu gibt es zwei Systemaufrufe, nämlich open und creat.. . Ken Thompson wurde gefragt, was er beim nächslenmal bei UNIX ändern würde: "Ich würde creat mit e buchstabieren." A.d.Ü. . 1 
166 8 Die Schnittstelle; UNIX-Betriebssystem 8.3 open, creat se, unlink 167 open gleicht ziemlich der fopen-Funktion, die in Kapitel 7 besprochen wurde, nur daß der Resultatwert kein FILE-Zeiger sondern ein File-Deskriptor ist, also ein int-Wert. open liefert -1, falls ein Fehler auftritt. 'include <fentl.h> int fd; int open(ehar *name, int flags, int perms); fd = open(name, flags, perms); Wie bei fopen ist name eine Zeichenkeue, die den Dateinamen enthält. Das zweite Ar- gument, Oags, ist ein int-Wert, der beschreibt, wie zugegriffen werden soll; die wichtig- sten Werte sind O_RDONLY Öffnen nur zum Lesen O_I/RONLY Öffnen nur zum Schreiben O_RDI/R Öffnen zum Lesen und Schreiben Bei UNIX System V sind diese Konstanten in < fcntl.b > und bei Berkeley- Versionen (BSD) in < sysjfile.b > defmiert. Eine existente Datei wird folgendermaßen zum Lesen eröffnet: fd = open(name, O_RDONLY, 0); Bei der Art von open, die wir beschreiben, ist perms immer Null. Es ist ein Fehler, wenn man auf eine Datei mit open zugreift, die nicht existiert. Der Systemaufruf creat dient dazu, neue Dateien zu erzeugen oder existierende Dateien zu überschreiben: int ereat(ehar *name, int perms); fd = ereat(name, perms); Dieser Aufruf liefert einen File-Deskriptor, wenn die angegebene Datei erzeugt werden konnte, und -1 sonst. Wenn die Datei bereits existiert, reduziert creat ihre Länge auf 0 und zerstört damit den bisherigen Inhalt; es ist kein Fehler, wenn man auf eine Datei mit creat zugreift, die schon existiert. Wenn die Datei noch nicht existiert, so erzeugt sie creat mit dem Zugriffsschutz, der als Argument perms angegeben ist. Im UNIX-Dateisystem gibt es neun Bits für den Zugriffsschutz einer Date die Zugriff zum Lesen, Schreiben und Ausführen für den Be- sitzer der Date für seine Gruppe und für alle anderen Teilnehmer kontrollieren. Mit ei- ner Oktalzahl mit drei Ziffern kann man deshalb den Zugriffsschutz bequem angeben. Beispielsweise erlaubt 0755 Lesen, Schreiben und Ausführen für den Besitzer und Lesen und Ausführen für die "Gruppe" und alle ,,Anderen". Zur Illustration ist hier eine vereinfachte Fassung des uNIx-Programms cp, das ei- ne Datei in eine andere kopiert. Unsere Version kopiert nur eine Datei, sie akzeptiert als zweites Argument keinen Katalog, und sie erfmdet den Zugriffsschutz, statt ihn zu ko- pieren. /* ep: f1 nach f2 kopieren */ main(int arge, ehar *argv[) ( int f1, f2, n; ehar buf[BUFSIZ); if (arge 1= 3) error("Usage: ep fram to"); if «f1 = open(argv[1) , O_RDONLY, 0» == -1) error(lIep: ean't open Xs", argv[1); if «f2 = ereat(argv[2), PERMS» == -1) error(lIep: ean't ereate Xs, mode X030", argv[2). PERMS); while «n = read(f1, buf, BUFSIZ» > 0) if (write(f2, buf, n) 1= n) error(lIep: write error on file Xs", argv[2); return 0; } Dieses Programm erzeugt die Ausgabedatei mit einem unveränderlichen Zugriffsschutz von 0666. Mit dem Systemaufruf stat, der in Abschnitt 8.6 beschrieben wird, kann man den Zugriffsschutz einer existenten Datei feststellen und folglich der Kopie den gleichen Zugriffsschutz geben. Beachten Sie, daß die Funktion error ähnlich wie printf mit variablen Argumentli- sten aufgerufen wird. Die Implementierung von error zeigt, wie man ein anderes Mit- glied der printe-Familie benutzt. Die Funktion vprinU aus der Standard-Bibliothek funk- tioniert wie printf, aber die variable Argumentliste ist durch ein einzelnes Argument er- setzt, das durch einen Aufruf des Makros va_start initialisiert wurde. Ähnlich passen vfprintf und vsprinte zu fprinte und sprinte. 'include <stdio.h> 'include <stdarg.h> /* error: gib eine Fehlermeldung aus und verende */ void error(ehar *fmt, ...) ( va_list args; va_start(args, fmt); fprintf(stder, "error: 11); vfprintf(stderr, fmt, args); fprintf(stderr, "\n"); va_end(args); exit(1); #include <stdio.h> #include <fentl.h> #include "sysealls.h" ldefine PERMS 0666 /* RW fuer Besitzer, Gruppe und Andere */ void error(ehar *, ...); } Die Anzahl Dateien, die ein Programm gleichzeitig eröffnen kann, ist (häufig auf etwa 20) begrenzt. Wenn ein Programm viele Dateien bearbeiten will, muß man Vorkeh- rungen treffen, um File-Deskriptoren wiederzuverwenden. Die Funktion close(int fd) löst die Verbindung zwischen einem File-Deskriptor und einer eröffneten Datei und gibt den File-Deskriptor zur Wiederverwendung mit einer anderen Datei frei; sie entspricht fdose aus der Standard-Bibliothek, nur daß es keine Puffer gibt, die geleert werden müs- sen. Wird ein Programm mit exit oder durch return aus der main-Funktion beendet, so werden alle eröffneten Dateien implizit geschlossen. Die Funktion unlink(char -name) entfernt den Dateinamen name aus dem Datei- system. Sie entspricht der Funktion remove aus der Standard-Bibliothek. 
168 8 Die Schnittstelle zum lIX-Betriebssystem 8.5 Beispiel: Eine )lementierung von/open undgetc 169 Aufgabe 8-1. Ändern Sie das Programm cal aus Kapitel 7, und benutzen Sie read, write, open und dose anstelle der äquivalenten Funktionen aus der Standard-Bibliothek. Experimentieren Sie, um die relative Geschwindigkeit der zwei Programmversionen fest- zustellen. 0 8.4 Random-ZugrifT - /seek Eingabe und Ausgabe erfolgen normalerweise sequentiell: Jeder Aufruf von read oder write bezieht sich auf die Dateiposition unmittelbar nach dem vorhergehenden Auf- ruf. Falls notwendig, kann eine Datei jedoch in jeder beliebigen Reihenfolge gelesen oder geschrieben werden. Mit dem Systemaufruf Iseek kann man sich in einer Datei be- wegen, ohne Daten zu lesen oder zu schreiben: long lseelc(int fd, long offset, int origin); Dieser Aufruf setzt die Bearbeitungsposition in der durch den File-Deskriptor fd be- schriebenen Datei auf olTset, relativ zu dem durch origin beschriebenen Punkt. Der nächste Aufruf von read oder write beginnt dann bei dieser Position. origin kann 0, 1 oder 2 sein und legt fest, daß olTset vom Anfang der Datei, von der aktuellen Dateipositi- on oder vom Dateiende aus gemessen wird. Um zum Beispiel an eine Datei anzufügen, (das entspricht der Umlenkung mit » in der UNIX-Shell oder "a" bei fopen), positioniert man vor dem Schreiben zum Dateiende: lseelc(fd, OL, 2); Zum Dateianfang (..zurückspulen") kommt man mit dem Aufruf: lseelc(fd, OL, 0); Beachten Sie das Argument OL; dafür könnte man auch (Iong) 0 schreiben oder nur 0, wenn für Iseek ein korrekter Funktionsprototyp vorhanden ist. Mit Iseek kann man Dateien mehr oder weniger wie große Vektoren behandeln, al- lerdings mit langsamerem Zugriff. Die folgende Funktion liest beispielsweise von einer beliebigen Stelle an beliebig viele Bytes aus einer Datei. Sie liefert die Anzahl gelesener Zeichen, oder -1 bei Fehler. #include "syscalls.h" /* get: n Bytes ab Position pos einlesen */ int get( int fd, long pos, char *buf, int n) { Erinnern wir uns darau, daß die Standard-Bibliothek Dateien mit FILE-Zeigern und nicht mit File-Deskriptoren beschreibt. Ein FILE-Zeiger ist ein Zeiger auf eine Struktur, die verschiedene Informationen über die Datei enthält: einen Zeiger auf einen Puffer, damit die Datei in großen Stücken gelesen werden kann; die Anzahl Zeichen, die noch im Puffer sind; einen Zeiger auf die Position des nächsten Zeichens im Puffer; den File-Deskriptor; und einige Bits, die Zugriff (lesen/schreiben), Fehlerstatus usw. be- schreiben. Die Datenstruktur, die eine Datei beschreibt, steht in < stdio.h >. Diese Defmiti- onsdatei muß (mit #indude) in jede Quelldatei eingefügt werden, die Funktionen aus der Standard-Bibliothek für Ein- und Ausgabe verwendet. < stdio.h > wird auch von den Funktionen in dieser Bibliothek selbst verwendet. Im folgenden Auszug aus einer typi- schen Defioitionsdatei < stdio.b > beginnen Namen, die nur von Bibliotheksfunktionen verwendet werden sollten, mit einem Unterstrich, damit sie sich nach Möglichkeit von Namen im Benutzerprogramm unterscheiden. #define NULL 0 Idefine EOF (-1) #define BUFSIZ 1024 #define FOPEN_MAX 20 /* maximale Anzahl gleichzeitig eroeffneter Dateien */ typedef struct _iobuf { int cnt; /* Anzahl verbleibender Zeichen */ char *ptr; /* position des naechsten Zeichens */ char *base; /* Adresse des Puffers */ int flag; /* Art des Dateizugriffs */ int fd; /* File-Deslcriptor */ } FILE; extern FILE _ioO[FOPEN_MAX]; #define stdin (&_ioO[O]) #define stdout (&_ioO[1]) #deflne stderr (&_ioO[2]) enun _flags { _REAO = 01, _WRITE . 02, _UNBUF = 04, _EOF = 010, _ERR . 020 /* Datei zum Lesen eroeffnet */ /* Datei zum Schreiben eroeffnet */ /* nicht gepuffert */ /* Dateiende wurde bereits erreicht */ /* ein Fehler ist schon passiert bei dieser Datei */ if (lseelc(fd, pos, 0) >= 0) /* auf pos einstellen */ return read(fd, buf, n); else return -1; }; int _fillbuf(FILE *); int _flushbuf(int, FILE *); #define feof(p) «(p)->flag & _EOF) != 0) Idefine ferror(p) «(p)->flag & _ERR) != 0) Idefine fi leno(p) «p)->fd) #define getc(p) (--(p)->cnt >= 0 \ ? (unsigned char) *(p)->ptr++ : _fillbuf(p» #define putc(x,p) (--(p)->cnt >= 0 \ ? (unsigned char) *(p)->ptr++ = (x) : _flushbuf«x),p» #define getchar() getc(stdln) #define putchar(x) putc«x), stdout) } Der Funktionswert von Iseek ist long und gibt die neue Position in der Datei an, oder er ist -IL bei einem Fehler. Die Funktion fseek der Standard-Bibliothek gleicht Iseek, nur hat das erste Argument den Typ FILE ., und das Funktionsresultat ist nicht-null bei Feh- ler. 8.5 Beispiel: Eine Implementierung von fopen und gele Wie einige dieser Teile zusammenpassen, illustrieren wir mit einer Implementie- rung von fopen und getc aus der Standard-Bibliothek. 
170 8 Die Schnittstelle zun fIX-Betriebssystem 8.5 Beispiel: Eine plementierung von fopen und gele 171 Normalerweise dekrementiert der gete-Makro die Anzahl im Puffer verbleiben<ler Zeichen, rückt den Zeiger vor und liefert das Zeichen als Resultat. (Zur Erinnerung: ei- ne längere #define-Anweisung wird mit einem Gegenschrägstrich auf einer neuen Zeile fortgesetzt.) Wenn die Anzahl der im Puffer verbleibenden Zeichen jedoch negativ d, ruft gete die Funktion fiUbur auf, um den Puffer neu zu füllen, die Datenstruktur Wieder zu initialisieren und eGt Zeichen zu liefern. Die Zeichen werden als unsigned geliefert, damit sicher ist, daß alle Zeichen positiv sind. Wir wollen zwar die Details nicht beschreiben, aber wir haben auch die Definition von pute beigefügt. pute funktioniert praktisch wie gete und ruft eine Funktion _ Oushbuf auf, wenn der Puffer gefüllt ist. Der Auszug aus < stdict.b > enthält auch Makros zum Zugriff auf die Fehler- und Dateiende-Bits sowie auf den File-Deskriptor. Wir können jetzt die Funktion fopen schreiben. Der größte Teil von fopen befaßt sich damit, Zugriff zur Datei zu eröffnen, richtig zu positionieren und .die Bits in der FILE-Struktur dem Zugriff entsprechend zu setzen. fopen stellt noch kelllen Puffer be- reit; dies erfolgt durch _ fiUbuf beim ersten Lesezugriff auf die Datei. #include <fentl.h> #include "s y sealls.h" #define PERMS 0666 /* RW fuer Besitzer, Gruppe und Rest */ /* fopen: Datei eroeffnen, liefert FILE-Zeiger */ FILE *fopen(ehar *name, ehar *mode) ( unser fopen weder "b", für binären Zugriff, weil das bei uNIx-Systemen bedeutungslos ist, noch ,,+", was sowohl Lesen als auch Schreiben erlaubt. Der erste Aufruf von gete für eine Datei entdeckt Null als Anzahl der verbleiben- den Zeichen im Puffer; folglich wird _ fiUbuf aufgerufen. Entdeckt _ fiUbuf, daß die Datei nicht zum Lesen eröffnet wurde, so wird sofort das Resultat EOF geliefert. Andernfalls versucht _ fiUbur, einen Puffer anzulegen (falls gepuffert gelesen werden soll). Wenn ein Puffer eingerichtet ist, ruft fiUbuf dann read auf, um ihn zu füllen, setzt die Anzahl der verbleibenden Zeichen und -die Zeiger entsprechend und liefert das Zei- chen am Anfang des Puffers als Resultat. Spätere Aufrufe von _fiUbuf finden dann eincn Puffer vor. #include "sysealls.h" /* _fillbuf: Eingabepuffer anlegen und fuellen */ int _fillbuf(FILE *fp) ( int fd; FILE *fp; if (*mode 1= 'r' && *mode 1= 'w' && *mode 1= 'a') return NULL; for (fp = _iob; fp < _i ob + OPEN_MAX; fp++) if «fp->flag & (_READ I _WRITE» == 0) break; /* frei FILE-Struktur gefunden */ if (fp >= _iob + OPEN_MAX) /* niehts mehr frei */ return NULL; if (*mode == 'w') fd = ereat(name, PERMS); else if (*mode == 'a') ( if «fd = open(name, O_WRONLY, 0» == -1) fd = ereat(name, PERMS); lseek(fd, OL, 2); > else fd = open(name, 0 RDONLY, 0); if (fd == -1) -/* Zugriff nieht moeglieh */ return NULL; fp->fd = fd; fp->ent = 0; fp->base = NULL; fp->flag = (*mode == 'r') ? _READ _WRITE; return fp; int bufsize; if «fp->flag&(_READI_EOFI_ERR» 1= _READ) return EOF; bufsize = (fp->flag & _UNBUF) ? 1 : BUFSIZ; if (fp->base == NULL) /* noeh kein Puffer */ if «fp->base = (ehar *) malloe(bufsize» == NULL) return EOF; /* kein Puffer zu erhalten */ fp->ptr = fp->base; fp->ent = read(fp->fd, fp->ptr, bufsize); if (--fp->ent < 0) ( if (fp->ent == -1) fp->flag 1= _EOF; else fp->flag 1= _ERR; fp->ent = 0; return EOF; > return (unsigned ehar) *fp->ptr++; > Als einziges Problem bleibt übrig, wie alles initialisiert wird. Der Vektor iob muß definiert und für stdin, stdout und stderr initialisiert werden: FILE _iobtOPEN_MAXJ = ( 1* stdin, stdout, stderr: */ ( 0, (ehar *) 0, (ehar *) 0, _READ, 0 >, ( 0, (ehar *) 0, (ehar *) 0, _WRITE, 1 >, ( 0, (ehar *) 0, (ehar *) O. _WRITE I _UNBUF, 2 > > Diese Version von fopen behandelt nicht alle Zugriffsarten des Standards; allerdings wür- de man nicht viel Programmtext brauchen, um sie hinzuzufügen. Insbesondere erkennt >; Die Initialisierung der Oag-Komponente der Struktur zeigt, daß stdin gelesen, stdout ge- schrieben und stderr ungepuffert geschrieben werden soll. Aufgabe 8-2. Schreiben Sie fopen und fiUbuf so um, daß Bit-Felder anstelle von expli- ziter Bit-Manipulation verwendet werden. Vergleichen Sie Programmgröße und Aus- führungsgeschwindigkeit. 0 Aufgabe 8 - 3. Entwerfen und schreiben Sie die Funktionen _ Oushbuf, mush und fdose. o 
172 8 Die Schnittstelle zun. nX-Betriebssystem 8.6 Beispiel: Kata ausgeben 173 von readdir und closedir benutzt wird. sammengefaßt. #define NAME_MAX 14 Aufgabe 8-4. Die Funktion int fseek(FILE *fp. lang offset. int origin) in der Standard-Bibliothek ist identisch zu Iseek, abgesehen davon, daß fp ein FlLE- Zeiger und kein File-Deskriptor ist, und daß als Resultat ein Int-Status und keine Positi- onsangabe geliefert wird. Schreiben Sie fseek. Sorgen Sie unbedingt dafür, daß Ihre fseek- Funktion im Zusammenhang mit den Pufferoperationen korrekt funktioniert, die die anderen Bibliotheksfunktionen durchführen. 0 Diese Information ist in einer Datei dirent.h zu- typedef struct { lang inoi char name[NAME_MAX+1Ji } Direnti /* laengste Dateinamenskomponente i */ /* systemabhaengig */ /* portabler Katalogeintrag: */ /* inode-Nummer */ /* Name + '\0' am Ende */ typedef struct { int fdi Dirent di } DIR i DIR *opendir(char *dirname)i Dirent *readdir(DIR *dfd)i void closadir(DIR *dfd)i Der System aufruf stat akzeptiert einen Dateinamen als Argument und liefert alle Information aus der Inode für diese Datei; bei Fehlern ist das Funktionsresultat -1 sonst Q , 8.6 Beispiel: Kataloge ausgeben Manchmal braucht man eine andere Dateioperation - wenn man Information über eine Datei selbst und nicht über ihren Inhalt benötigt. Ein Beispiel dafür ist ein Programm zum Ausgeben von Katalogen, wie das UNIX-Kommando ls (list directory). Dieses Programm gibt die Dateinamen in einem Katalog aus und zusätzlich - wahlweise - auch andere Informationen, wie Dateigröße, Zugriffsberechtigungen usw. ls gleicht dem Kommando DIR bei MS-DOS. Da ein UNIX-Katalog einfach eine Datei ist, braucht ls ihn nur ZU lesen, um die Da- teinamen zu erhalten. Aber man muß einen Systemaufruf benutzen, um auf andere In- formation über eine Datei zuzugreifen, wie zum Beispiel auf die Dateigröße. Bei ande- ren Betriebssystemen benötigt man unter Umständen sogar einen Systemaufruf, um an die Dateinamen zu kommen; dies gilt zum Beispiel bei MS-DOS. Wir wollen erreichen, daß der Zugriff zu dieser Information möglichst system unabhängig erfolgen kann, auch wenn die Implementierung sehr system abhängig sein mag. Wir illustrieren einige Aspekte, indem wir ein Programm fsize schreiben. fsize ist eine spezielle Version von ls und gibt die Größen aller Dateien aus, die als Argumente in der Kommandozeile angegeben werden. Ist eine dieser Dateien selbst ein Katalog, so durchläuftfsize diesen Katalog rekursiv. Ohne Argument aufgerufen, bearbeitetfsize den aktuellen Katalog. Beginnen wir mit einer kurzen Wiederholung der Struktur des uNIx-Dateisystems. Ein KaJalog ist eine Date die eine Liste von Dateinamen enthält, zusammen mit Anga- ben, wo sich diese Dateien befinden. Eine solche Angabe ist ein Index in eine weitere Tabelle, die sogenannte Inode-Tabelle. In der Inode befmdet sich alle Information über eine Date mit Ausnahme des Dateinamens. Ein Katalogeintrag enthält im allgemeinen nur zwei Teile, den Dateinamen und die Inode-Nummer. Bedauerlicherweise sind Format und exakter Inhalt eines Katalogs nicht bei allen Versionen des uNIx-Betriebssystems gleich. Wir teilen deshalb die Arbeit in zwei Teile, um die nicht-portablen Teile möglichst einzukapseln. Die äußere Ebene defIniert eine Struktur namens Dlrent und drei Funktionen open dir, readdir und closedir, um einen sy- stemunabhängigen Zugriff auf Namen und Inode-Nummer aus einem Katalogeintrag zu erreichen. Mit dieser Schnittstelle schreiben wir fslze. Dann zeigen wir, wie man diese Funktionen bei Systemen implementiert, die die gleiche Katalogstruktur wie UNIX Versi- on 7 oder System V haben; Abarten bleiben als Übungsaufgaben. Die Dlrent-Struktur enthält die Inode-Nummer und den Namen. Die maximale Länge einer Komponente eines Dateinamens ist NAME_MAX, ein system abhängiger Wert. opendir liefert einen Zeiger auf eine Struktur namens DIR, analog zu FILE, die /* DIR minimal: ungepuffert. etc. */ /* File-Deskriptor fuer Katalog */ /* der Katalogeintrag */ char *namei struct stat stbufi int stat(char *. struct stat *)i stat(name. &stbuf)i Dieser Aufruf füllt die Struktur stbuf mit der lnode-Information für die Datei name. Die Struktur, die das Resultat von stat beschreibt, befmdet sich in < sysfstat.h > und sieht ty- pischerweise so aus: struct stat { dev_t ino_t short short short short dev_t off_t time_t time_t time_t }i /* von stat gelieferte lnode-Information */ st_devi st_inoi st_modei st_nl inki st_uidi st_gidi stJdevi st_Sjzei st_atimei st_mtimei st_ctimei /* Geraet der lnode */ /* lnode-Nummer */ /* Dateityp. Zugriffsschutz */ /* Anzahl Pfade zur Datei */ /* Nummer des Dateibesitzers */ /* Nummer der Gruppe des Dateibesitzers */ /* bei Geraeten */ /* Dateigroesse (in Bytes) */ /* Zeitpunkt des letzten Zugriffs */ /* Zeitpunkt der letzten Aenderung */ /* Zeitpunkt der letzten lnode-Aenderung */ Die meisten Werte werden in den Kommentaren erklärt. Datentypen wie dev t und Ino _ t sind in < sysftypes.h > defIniert; diese DefInitionsdatei muß auch eingefügt rden. Die Komponente st_ mode enthält eine Reihe von Bits, die die Datei beschreiben. Die DefInitionen dieser Bits befmden sich auch in < sysfstat.h >; wir brauchen nur den Teil. der sich mit dem Dateityp befaßt: #define S_IFMT 0160000 /* Dateityp: */ #define S_IFDIR 0040000 /* Katatog */ #define S IFCHR 0020000 /* Geraet mit Zeichenprotokolt */ #define S:IFBLK 0060000 /* Geraet mit Blockprotokoll */ #define S_IFREG 0100000 /* normele Datei */ /* ... */ 
"IX-Betriebssystem 8.6 Beispiel: Kata l ' ausgeben 175 174 8 Die Schnittstelle ZUl1 Jetzt könncn wir das Programm fsize schreiben. Wenn aus der Komponente st_mode nach dem Systemaufruf stat hervorgeht, daß die Datei kein Katalog ist, dann haben wir die Dateigröße und können sie direkt ausgeben. Ist die Datei jedoch ein Kata- log, dann müssen wir sie, eine Datei auf einmal, bearbeiten; dabei können wiederum Ka- taloge auftreten, so daß der Vorgang rekursiv ist. Das Hauptprogramm bearbeitet die Argumente aus der Kommandozeile; jedes Argument wird an die Funktion fsize übergeben. #include <stdio.h> #include <string.h> #include "syscalls.h" #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include "dirent.h" #define MAX_PATH 1024 1* dirwalk: fcn auf alle Dateien in dir anwenden *1 void dirwalk(char *dir, void (*fcn)(char *» { char name [IW(_PATHJ ; Dirent *dp; [)'IR *dfd; if «dfd  opendir(dir» == NULL) ( fprintf(stderr, "dirwalk: can't open XIi\n", dir); return; 1* Bits fuer Lesen und Schreiben *1 1* Systemtypen *1 1* stat-Resultatstruktur *1 } while «dp . readdir(dfd» ,= NULL) { 1* diesen Katalog und Vorgaenger ueberspringen *1 if (str(cIp->name, ".") ." 0 11 strcq)(dp->name, "..") = 0) continue; if (strlen(dir)+strlen(dp->name)+2 > sizeof(name» fprintf(stderr, "di rwalk: name XIiIXIi too long\n", dir, cIp->name); void fsize(char *); 1* Dateigroessen ausgeben *1 main(int argc, char **argv) { if (argc == 1) 1* Voreinstellung: momentaner Katalog *1 fsize("."); else while (--argc > 0) fsize(*++argv); return 0; else ( sprintf(name, "XIiIXIi", dir, dp->name); (*fcnHname) ; } } closadir(dfd); struct stat stbuf; if (stat(name, &stbuf) == -1) ( fprintf<stderr, "fsize: can't access Xs\n", name); return; } Jeder Aufruf von readdir liefert einen Zeiger auf Information über die nächste Datei oder NULL, wenn keine Dateien mehr übrig sind. Jeder Katalog enthält immer auch Ein- träge für sich selbst, nämlich ".", und für seinen Vorgänger ".."; diese Einträge müssen übersprungen werden, weil das Programm sonst ewig laufen würde. Bis zu dieser Ebene ist der Programmtext unabhängig davon, wie Kataloge ausse- hen. Als nächsten Schritt stellen wir minimale Versionen von opendir, readdir und closedir für ein spezielles System vor. Die folgenden Funktionen sind für Version 7 und System V UNIx-Systeme; sie benutzen die Information über Kataloge aus der DefInitions- datei < sys/dir.b>, die etwa so aussieht: #ifndef DIRSIZ #define OIRSIZ 14 #endi f struct direct 1* Katalogeintrag *1 { } Die Funktion fsize gibt die Dateigröße aus. Ist die Datei jedoch ein Katalog, so ruft fsize zuerst dirwalk auf, um alle Dateinamen im Katalog zu bearbeiten. Beachten Sie, wie die Bit-Definitionen S)FMf und S)FDIR aus <sys/stat.h> verwendet werden, um zu entscheiden, ob die Datei ein Katalog ist. Die Klammern sind wichtig, da der Vor- rang von & kleiner ist als der von ==. int stat(char *, struct stat *); void dirwalk(char *, void (*fcn)(char *»; 1* fsize: gib Groesse der Datei "name" aus *1 void fsize(char *name) { } if «stbuf.st mode & S IFMT) == S IFDIR) dirwalk(n, fsiz;); - printf("XBld Xs\n", stbuf.st_size, name); ino t d ino; 1* lnode-Nummer *1 char d=nametOIRSIZJ; 1* ein langer Name hat kein '\0' *1 }; } Die Funktion dirwalk ist eine allgemeine Routine, die eine Funktion auf jede Datei in einem Katalog anwendet. Sie eröffnet den Katalog, durchläuft alle Dateinamen darin und ruft die Funktion für jeden auf, schließt dann den Katalog und hört auf. Da fsize dirwalk für jeden Katalog aufruft, rufen sich die beiden Funktionen gegenseitig rekursiv auf. Manche Versionen des Systems erlauben viel längere Namen und haben eine komplizier- tere Katalogstruktur . Der Datentyp ino _t ist mit typedef vereinbart und beschreibt den Index in die Inode-Liste. Zufälligerweise ist er unsigned short bei dem System, das wir normalerwei- se benutzen, aber das ist nicht die Art von Information, die man in einem Programm ver- gräbt; bei einem anderen System kann dies anders sein, also ist typedef besser. Einen kompletten Satz an "Systemtypen" fmdet man in < sys/types.h >. 
176 8 Die Schnittstelle zu TNIX-Betriebssystem opendir eröffnet den Katalog, prüft, daß es sich um einen Katalo handlt (diesal mit dem Systemaufruf fstat, der wie stat funktioniert, nur daß er auf emen File-Deskrip- tor angewandt wird), legt eine Katalogstruktur an und speichert die Information: int fstat(int fd. struct stat *); 1* opendir: einen Katalog fuer readdir-Aufrufe eroeffnen *1 DIR *opendir(ehar *dirname) { int fd; struct stat stbuf; DIR *dp; if (Cfd = openCdirname. O_RDONLY. 0» == -1 11 fstatCfd. &stbuf) == -1 11 Cstbuf.st_mode & S_IFMT) 1= S_IFDIR 11 Cdp = CDIR *) malloc(sizeof(DIR») == NULL) return NULL; dp->fd .. fd; return dp; } closedir schließt die Katalogdatei und gibt den Speicherplatz wieder frei: 1* elosedir: Katalog sehliessen. der von opendir eroeffnet wurde *1 void closedirCDIR *dp) { if (dp) ( eloseCdp->fd); freeCdp) ; } } Schließlich liest readdir jeden Katalogeintrag mit read. Ist ein Katalogeintrag im Augenblick unbenutzt (da ein Dateiname gelöscht wurde), so ist die Inode-Nummer Null und diese Position im Katalog wird übersprungen. Andernfalls werden Inode-Nummer und Dateiname in einer statie-Struktur abgelegt, und ein Zeiger auf die Struktur wird an den Aufrufer geliefert. Jeder neue Aufruf überschreibt die vorhergehende Information. #include <sys/dir.h> 1* lokale Katalogstruktur *1 1* readdir: Katatogeintraege der Reihe naeh lesen *1 Di rent *readdi r(DIR *dp) ( struct direct dirbuf; 1* lokale Katalogstruktur *1 statie Dirent d; 1* Resultat: portable Struktur *1 while CreadCdp->fd. Cehar *) &dirbuf. sizeofCdirbuf» == sizeofCdirbuf» ( if Cdirbuf.d_ino == 0) 1* Eintrag unbenutzt *1 eontinue; d.ino = dirbuf.d_ino; strnepyCd.name. dirbuf.d_name. DIRSIZ); d.name[DIRSIZ] = '\0'; 1* unbedingt absehliessen *1 return &d; } return NULL; } f 8.7 Beispiel: Fun' len zur Speicherverwaltung 177 Auch wenn das fsize-Programm ziemlich speziell ist, illustriert es doch ein paar wichtige Ideen. Zunächst sehen wir, daß viele Programme keine ..System programme" sind; sie benutzen nur Information, die durch das Betriebssystem unterhalten wird. Für solche Programme ist entscheidend, daß die Repräsentierung der Information nur in Standard-DeClnitionsdateien vorkommt und daß Programme diese Dateien einfügen und die Vereinbarungen nicht in sich selbst vergraben. Außerdem sehen wir, daß man mit ei- ner gewissen Sorgfalt eine Schnittstelle zu system abhängigen Objekten schaffen kann, die selbst relativ system unabhängig ist. Die Funktionen der Standard-Bibliothek sind gute Beispiele dafür. Aufgabe 8-5. Ändern Sie dasfsize-Programm so, daß die andere Information aus dem Inode-Eintrag auch ausgegeben wird. 0 8.7 Beispiel: Funktionen zur SpeicherverwaItung In Kapitel 5 haben wir eine sehr einfache Speicherverwaltung vorgestellt, die mit einem Stack arbeitete. Die Version, die wir jetzt schreiben, unterliegt keinen Einschrän- kungen. Aufrufe von malloc und free können in beliebiger Reihenfolge erfolgen; malloc verlangt vom Betriebssystem mehr Speicher, wenn dies notwendig wird. Diese Funktio- nen illustrieren einige der Überlegungen, die man anstellen muß, wenn man ein maschi- nenabhängiges Programm einigermaßen maschinenunabhängig schreiben will, und sie zeigen eine Anwendung mit Strukturen, Unionen und typedef aus dem wirklichen Leben. malloc vergibt Speicher nicht aus einem bei der Übersetzung festgelegtcn Vektor, sondern aus Bereichen, die bei Bedarf vom Betriebssystem angefordert werden. Da an- dere Teile eines Programms auch Speicher anfordern können, ohne diese Speicherver- waltung aufzurufen, braucht der Speicher, den malloc verwaltet, nicht unbedingt zusam- menzuhängen. Freier Speicher wird daher als Liste von freien Blöcken verwaltet. Jeder Block enthält eine Größenangabe, einen Zeiger auf den nächsten Block und den freien Speicher selbst. Die Blöcke werden in Reihenfolge aufsteigender Speicheradressen an- geordnet und der letzte Block (die höchste Adresse) zeigt auf den ersten. freie liste  nicht ::::::: frei I frei, kontrolliert von malloc I nicht frei I in Gebrauch, kontrolliert von malloc I : : : : : : : : : : : I nicht kontrolliert von malloc Wird Speicherplatz angefordert, so wird die freie Liste durchlaufen, bis ein Block geeigneter Größe gefunden wird. Diesen Algorithmus nennt man first fit (paßt zuerst), im Gegensatz zu best fit (paßt am besten), wo nach dem kleinsten Block gcsucht wird, dcr die Anforderung erfüllt. Hat der Block genau die notwendige Größe, wird er aus der freien Liste entfernt und an den Benutzer geliefert. Ist der Block zu groß, wird er geteilt, der angeforderte Speicher wird an den Benutzer geliefert und der Rest bleibt in der frei- 
178 8 Die Schnittstelle UNIX-Betriebssystem en Liste. Findet man keinen Block geeigneter Größe, wird ein neues, großes Stück vom Betriebssystem angefordert und in die freie Liste aufgenommen. Auch bei Freigabe wird die freie Liste durchsucht, um den neuen freien Block an der richtigen Stelle einzufügen. Hat der neue freie Block als Nachbarn einen freien Block, so werden die Nachbarn in einen einzigen Block zusammengefaßt, damit der Spei- cher nicht zu sehr fragmentiert wird. Die Nachbarschaft kann leicht festgestellt werden, da die freie Liste in Reihenfolge aufsteigender Speicheradressen unterhalten wird. In Kapitel 5 haben wir angedeutet, daß manoc Speicher liefern muß, der für die Objekte korrekt ausgerichtet ist, die darin gespeichert werden. Obgleich sich Maschinen unterscheiden, gibt es für jede Maschine einen Datentyp, der am meisten eingeschränkt ist: wenn dieser Typ bei einer bestimmten Adresse gespeichert werden kann, können dort auch alle anderen Datentypen gespeichert werden. Bei manchen Maschinen ist double der am meisten eingeschränkte Datentyp; bei anderen genügen iot oder long. Ein freier Block enthält einen Zeiger auf den nächsten Block in der Kette, eine An- gabe zur Größe des Blocks und schließlich den freien Bereich selbst. Wir nennen die In- formation am Anfang des Blocks den Reader. Um die Ausrichtung zu vereinfachen, sind alle Blöcke Vielfache de1 Reader-Größe und der Reader selbst ist korrekt ausgerichtet. Dies erreichen wir mit Hilfe einer Union, die die gewünschte Struktur enthält sowie eine Alternative mit dem in bezug auf Ausrichtung am meisten eingeschränkten Datentyp; wir haben hier willkürlich loog gewählt: typedef lang Align; /* zur Ausrichtung auf lang */ union header { /* Blockbeschreilu1g: */ stl'uct { union header *ptr; /* naechster Block, falls auf freier Liste */ unsigned size; /* Groesse dieses Blocks */ } s; Align X; /* Ausrichtung erzwingen */ }; typedef union heacler Header; Die Alternative Align wird nie benutzt; sie erzwingt nur, daß jeder Reader korrekt ausge- richtet ist. manoc rundet die gewünschte Anzahl Bytes auf die nächstmögliche Anzahl Reader-Einheiten auf; der Block, der verwendet wird, enthält eine Einheit mehr für den Reader selbst, und dieser Wert wird in der size-Komponente im Header abgelegt. manoc liefert einen Zeiger, der auf die freie Fläche zeigt, nicht auf den Reader. Der Auf- rufer kann alles mit dem angeforderten Speicher machen; wird aber etwas außerhalb des zugewiesenen Speichers abgelegt, so wird die Liste wahrscheinlich zerstört.  "'" f """" f,,; BJod<  I L- Adresse fIlr den Benutzer Ein Block, wie ihn maUoc liefert  8.7 Beispiel: Fw men zur Speicherverwaltung 179 Die siu-Komponente ist notwendig, da die von manoc verwalteten Blöcke nicht zusam- menhängen müssen - man kann die Größen nicht mit Zeigerarithmetik berechnen. Die Variable base wird als Anfang benutzt. Hat freep den Wert NULL (dies ist beim ersten Aufruf von manoc der Fall), dann wird eine leere freie Liste konstruiert; die- se enthält einen Block der Größe Null und der Header zeigt auf sich selbst. In jedem Fall wird dann die freie Liste durchsucht. Die Suche nach einem freien Block geeigneter Größe beginnt an dem Punkt (freep), wo der letzte Block gefunden wurde; diese Strate- gie verteilt die Fragmentierung gleichmäßiger. Wird ein Block gefunden, der zu groß ist, so wird das Ende des Blocks an den Benutzer vergeben; dann muß im Reader des ur- sprünglichen Blocks nur die siu-Komponente geändert werden. Auf jeden Fall erhält der Benutzer einen Zeiger auf die freie Fläche innerhalb des Blocks, die eine Einheit hin- ter dem Reader beginnt. static Header base; 1* leere Liste zur Initialisierung */ static Header *freep = NULL; /* Anfang der freien Liste */ /* malloc: allgemein verwendbare Speicherverwaltung */ void *malloc(unsigned nbytes) ( Header *p, *prevp; Header *morecore(unsigned); unsigned nunits; nunits = (nbytes+sizeof(Header)-1)/sizeof(Header) + 1; if «prevp = freep) == NULL) { /* noch keine freie Liste */ base.s.ptr = freep = prevp = &base; base.s.size = 0; } for (p = prevp->s.ptr; ; prevp = p, p = p->s.ptr) { if (p->s.size >= nunits) { /* gross genug */ if (p->s.size == nunits) /* passt genau */ prevp->s.ptr = p->s.ptr; else { 1* vom Ende her vergeben */ p->s.size -= nunits; p += p->s.size; p->s.size = nunits; } freep = prevp; return' (void *)(p+1); } if (p == freep) /* einmal rund um die freie Liste */ If «p = morecore(nunits» == NULL) return NULL; /* kein Platz mehr */ } } Die Funktion morecore beschafft Speicher vom Betriebssystem. Wie dies im De- tail geschieht, ist von System zu System verschieden. Da die Anforderung von Speicher beim Betriebssystem relativ aufwendig ist, wollen wir das nicht bei jedem Aufruf von manoc tun, deshalb fordert morecore mindestens NALLOC Einheiten an; dieser größere Block wird dann je nach Bedarf in kleinere Teile zerlegt. morecore notiert die Größe in der siu-Komponente und fugt den zusätzlichen Speicher mit einem Aufruf von free in die Verwaltung ein. 
180 8 Die Schnittstelle ZI JNIX-Betriebssystem Der UNIx-Systemaufruf sbrk(n) liefert einen Zeiger auf weitere n Bytes Speicher. sbrk liefert -1, wenn kein Speicherplatz mehr verfügbar ist; NULL wäre dafür ein besse- rer Resultatwert gewesen. -1 muß in den Typ cbar * umgewandelt werden, damit mit dem Funktionswert verglichen werden kann. Auch hier machen Umwandlungsoperatio- nen die Funktion einigermaßen immun gegenüber den Details der Repräsentierung von Zeigern bei verschiedenen Systemen. Eine Annahme bleibt jedoch noch immer - daß nämlich Zeiger auf verschiedene Blöcke, die von sbrk geliefert werden, sinnvoll vergli- chen werden können. Dies garantiert der Standard nicht; er läßt nur Vergleiche von Zei- gern innerhalb eines Vektors zu. Deshalb ist diese Version von malloc nur portabel zwi- schen Maschinen, bei denen allgemeine Vergleiche von Zeigern sinnvoll sind. #define NALLOC 1024 /* minimal anzufordernde Anzahl Header */ /* morecore: vom System mehr Speicher verlangen */ static Header *morecore(unsigned nu) ( char *cp, *sbrk(int); Header *up; i f (nu < NALLOC) nu = NALLOC; cp = sbrk(nu * sizeof(Header»; if (cp == (char *) -1) /* kein Ptatz mehr */ return NULL; up = (Header *) cp; up->s.size = nu; free«void *)(up+1»; return freep; } free selbst ist die letzte Funktion. Ausgehend von freep durchläuft sie die freie Li- ste, um den Punkt zu fmden, wo der freie Block eingefügt werden soll. Dies geschieht entweder zwischen zwei existenten Blöcken oder an einem Ende der Liste. In jedem Fall werden benachbarte freie Blöcke zusammengefaßt. Man muß nur dafür sorgen, daß die Zeiger auf die richtigen Objekte zeigen und daß die size-Komponenten korrekt bleiben. /* free: Block bei ap in freie Liste aufnehmen */ void free(void *ap) ( Header *bp, *p; bp = (Header *)ap - 1; /* auf Blockbeschreibung zeigen */ for (p = freep; '(bp > p && bp < p->s.ptr); p = p->s.ptr) if (p >= p->s.ptr && (bp > p 11 bp < p->s.ptr» break; /* freier Block sm einen oder anderen Ende */ if (bp + bp->s.size == p->s.ptr) ( /* mit rechtem Nachbarn kombinieren */ bp->s.size += p->s.ptr->s.size; bp->s.ptr = p->s.ptr->s.ptr; ) else bp->s.ptr = p->s.ptr; if (p + p->s.size .= bp) ( /* mit linkem Nachbarn kombinieren */ p->s.size += bp->s.size; p->s.ptr = bp->s.ptr; ) else p->s.ptr = bp; freep = p; } r 8.7 Beispiel: Fur >Den zur Speicherverwaltung 181 Obgleich Speicherverwaltung implizit maschinenabhängig ist, illustrieren diese Funktionen, wie man die Maschinenabhängigkeit kontrollieren und in einen sehr kleinen Programmteil einschließen kann. Mit typedef und union wird das Ausrichtungsproblem gelöst (ausgehend von der Tatsache, daß sbrk einen geeigneten Zeiger liefert). Um- wandlungsoperationen sorgen dafür, daß Zeigerumwandlungen explizit geschehen, und sie werden sogar mit einer schlecht definierten System schnittstelle fertig. Obgleich sich die Details hier auf Speicherverwaltung beziehen, so ist die allgemeine Technik auch auf andere Situationen anwendbar. Aufgabe 8-6. Die Funktion calloc(n,size) aus der Standard-Bibliothek liefert einen Zei- ger auf n Objekte mit der Größe slze, wobei der Speicherbereich auf 0 initialisiert ist. Schreiben Sie calloc, wobei malloc entweder aufgerufen oder modifiziert wird. 0 Aufgabe 8-7. malloc akzeptiert eine Speicheranforderung, ohne die Größe auf Plausibi- lität zu überprüfen; free nimmt an, daß der angebotene Block eine korrekte size- Komponente enthält. Verbessern Sie diese Funktionen, so daß sie Fehler besser erken- nen.O Aufgabe 8-8. Schreiben Sie eine Funktion bfree(p,n), die einen beliebigen Block p mit n Bytes in die freie Liste aufnimmt, die malloc und free unterhalten. Mit Hilfe von bfree kann ein Benutzer jederzeit einen Vektor aus der Speicherklasse staUe oder extern zur freien Liste hinzufügen. 0 
r 183 A C-Sprachbeschreibung A.l Einflihrung Dieser Anhang beschreibt die Programmiersprache C, wie sie durch den Entwurf definiert wird, der am 31. Oktober 1988 dem ANSl eingereicht wurde, um als , .American N ational Standar_d for Informa_ tiQ'!! Systems-Programming Language C, X3.159-1989" genehmigt zu werden. Dieser Anhang ist eine Interpretation des vorgeschlagenen Stan- dards, nicht der Standard selbst, obwohl sorgfältig darauf geachtet wurde, einen verläßli- chen Führer für die Sprache zu gestalten. Im großen und ganzen folgt dieser Anhang der groben Linie des Standards, der seinerseits wieder der Ersten Ausgabe dieses Buches folgt, aber die Gliederung unter- scheidet sich im Detail. Abgesehen davon, daß einige Grammatikregeln umbenannt sind und daß die Definitionen der lexikalischen Symbole und der Preprozessor nicht formali- siert sind, ist die hier angegebene Grammatik für die eigentliche Sprache äquivalent zu der im Standard. In diesem ganzen Anhang werden Kommentare so wie hier eingerückt und kleiner ge- schrieben. Meistens heben diese Kommentare hervor, wie ANSI-StandardC von der Sprache abweicht, die in der Ersten Ausgabe definiert wurde, oder auch von Erweiterun- gen, die später in verschiedenen Übersetzern eingeführt wurden. A.2 Lexikalische Konventionen Ein Programm besteht aus einer oder mehreren Übersetzungseinheiten (translation units), die in Dateien gespeichert sind. Es wird in verschiedenen Phasen übersetzt, die in  A,12 beschrieben werden. Die ersten Phasen nehmen einfache lexikalische Umwand- lungen vor, führen die Anordnungen der Zeilen aus, die mit # beginnen, und definieren und expandieren Makros. Wenn die in  A,12 beschriebene Arbeit des Preprozessors ab- geschlossen ist, liegt das Programm als Folge von Eingabesymbolen (tokens) vor. A.2.1 Eingabesymbole Es gibt sechs Klassen von Eingabesymbolen: Namen, reservierte Worte, Konstan- ten, konstante Zeichenketten (string literals), Operatoren und übrige Trenner. Leerzei- chen, Tabulatorzeichen, Vertikal-Tabulatoren, Zeilentrenner, Seitenvorschub sowie Kom- mentare, die nachstehend beschrieben werden, insgesamt als Zwischenraum bezeichnet, werden ignoriert, abgesehen davon, daß sie Eingabesymbole voneinander trennen. Zwi- schenraum muß direkt benachbarte Namen, reservierte Worte und Konstanten trennen. Nachdem die Eingabe bis zu einem bestimmten Zeichen schon in Eingabesymbole zerlegt ist, wird als nächstes Eingabesymbol die längstmögliche Zeichenfolge aufgefaßt, die ein Eingabesymbol darstellen kann. A.2.2 Kommentare Ein Kommentar wird durch die Zeichen '* eingeleitet und durch die Zeichen *' beendet. Kommentare dürfen nicht verschachtelt werden, und sie dürfen nicht innerhalb von Zeichenkonstanten oder konstanten Zeichenketten auftreten. 1 
184 A ;prachbeschreibung A.2.3 Namen Ein Name besteht aus einer Folge von Buchstaben und Ziffern. Das erste Zeichen muß ein Buchstabe sein; dabei zählt der Unterstrich zu den Buchstaben. Groß- und Kl uchs '!-.-'-I' en . Namen können beliebig lang sein.' Bei intern verwendeten Namen werden wenigstens die, erte,! .31 Zeichen unterschieden; impiemeü. tierungen dürfen auch mehr Zeichen als signifikant' ansehen. Zu den internen Namen gehören Makronamen im Preprozessor und alle anderen Namen, die keine externe Bin- dung besitzen (A.ll.2). Namen mit externer Bindung sind mehr eingeschränkt:. Imple- mentierungen ,könnten hier gerade noch sechs, ?-eic:h!1: ,untsc;!Ij;i de \l.I!LiAürnn Groß- und Kleinuc\),en alse!w.e..r!..!?t.r ht  A.2.4 Reservierte Worte Die folgenden Worte sind reserviert und können nur mit ihrer vordefinierten Be- deutung verwendet werden: auto default float lang sizeof union break da for register static uns i gned case double goto return struct void char else if short switch volatile const enun int signed typedef whi le cant i nue extern Manche Implementierungen reservieren auch die Worte fortran und asm. Die reservierten Worte eoost, sigoed und volatile werden durch den ANsl-Standard neu eingeführt; enum und void kamen in der (englischen) Ersten Ausgabe noch nicht vor, sind aber allgemein gebräuchlich; eotry war früher reserviert, wurde aber nie benutzt und ist nicht mehr reserviert. A.2.5 Konstanten Es gibt verschiedene Arten von Konstanten. Jede Konstante hat einen Datentyp;  A,4.2 diskutiert die elementaren Typen. constant: integer-constant character-constant f/oating-constant enumeration-constant A.2.5.1 Ganzzahlige Konstanten Eine ganzzahlige Konstante (integer constant) besteht aus einer Ziffernfolge. Sie wird normalerweise de7imal interpretiert. !,:alls ,d..ieJ'olge miuir:.?-iffe! OJ)eginl1t, wird sie oktal interpretiert, also zur Basis 8. Oktale Konstanten dürfen die Ziffern 8 und 9 nichtentfiaIien. -J:lIs ie Folge.. mit x .o.er OX (Zfer 0, Buchstabe x)lJeginnt, wird sie hexadezimal interpe.t,irt, also zur Basis 16. Zu den hexadezimalen Ziffern gehören a (oder A) bis f (oder F) mit den Werten 10 bis 15. An die ganzzahlige Konstante kann der Buchstabe u oder U angehängt werden, .der__ angibt, daß die Konstante vorzeic henlos ist (unsigned). Außerdem kann der Buchstabe I oder L ang ehängt werden, der an gibt, daß sie long ist. '., -. '-'-,_._- r A.2 Lexikalische I lentionen 185 Der Typ einer ganzzahligen Konstanten hängt von ihrer Form, ihrem Wert und den angehängten Suffixen ab. (Typen werden in  A,4 diskutiert.) Ohne Suffixe und dezimal angegeben, hat die Konstante den ersten der folgenden Typen, in denen sie dargestellt werden kann: lot, loog Int oder unsigned long Int. Ohne Suffixe und oktal oder hexade- zimal angegeben, hat sie den ersten möglichen der folgenden Typen: Int, unsigned int, loog Int oder unsigned long Int. Mit dem Suffix u oder U ist sie unsigned int oder uosigned long Int. Mit dem SuffIX I oder L ist sie long int oder unsigned long int. Die Auslegung der 'JYpen für ganzzahlige Konstanten geht wesentlich über die Erste Ausgabe hinaus, in der lediglich große ganzzahlige Konstanten als loog angesehen wur- den. Die Suffixe u und U sind neu. A.2.5.2 Zelcheokoostaoteo Eine Zeichenkonstante (character constant) ist eine Folge von einem oder mehre- ren Zeichen, eingeschlossen in einfache Anführungszeichen, also zum Beispiel 'x'. Der Wert einer Zeichenkonstanten mit einem einzigen Zeichen ist der numerische Wert des Zeichens im Zeichensatz der jeweiligen Maschine, auf der das Programm ausgeführt wird. Der Wert einer Zeichenkonstanten mit mehreren Zeichen ist implementierungsab- hängig. Zeichenkonstanten dürfen das Zeichen' sowie Zeilentrenner nicht enthalten; um diese und bestimmte andere Zeichen darzustellen, können die folgenden Ersatzdarstel- lungen verwendet werden: Zeilentrenner NL (LF) \n Tabulatorzeichen IIT \ t Vertikal-Tabulator VI' \v backs pace BS \b Wagenrücklauf CR \r Seitenvorschub FF \ f Klingelzeichen BEL \a Die Ersatzdarstellung \000 besteht aus einem Gegenschrägstrich \ gefolgt VOn 1, 2 oder 3 g!.f die als Wert des gewünschten Zeichens interpretiert werden. E in häUfi ges Beispiel dieser Konstruktion ist das NUL-Zeichen \0 (dabei darf keine Ziffer folgen). Die Ersatzdarstellung \xlIh besteht aus einem Gegenschrägstrich \ gefolgt VOn ]I; und hexade- zimalen Ziffern, die als Wert des gewünschten Zeichens interpretiert werden. Pj.A!1-  der Ziffern ist nicht beschränk t, aber das Resultat ist undefmiert, wenn der resultie- rende Wert größer ist als der Wert des größten Zeichens. Wenn in einer Implementie- rung der Typ char nicht vorzeichenlos ist, wird bei oktaler wie hexadezimaler Ersatzdar- stellung das Vorzeichen des Werts so propagiert, als ob der Wert in char umgewandelt worden wäre. Wenn das Zeichen nach \ nicht eines der angegebenen ist, so ist das Re- sultat undefmiert. Bei manchen Implementierungen gibt es einen erweiterten Zeichensatz, der nicht im Typ char repräsentiert werden kann. Eine Konstante in diesem erweiterten Satz wird mit einem vorangestellten L geschrieben, zum Beispiel L und sie wird ßrweiterte Ze i- chenkonstante ewide character const genannt. Eine solche Konstante hat den 1YP wchar _ t, das ist ein ganzzahliger Typ, der in der Standard- Defmitionsdatei < stddef.h > vereinbart ist. Wie bei gewöhnlichen Zeichenkonstanten können oktale oder hexadezi- Gegenschrägstrich Fragezeichen Anführungszeichen Doppelanführungszeichen " ohaleZalU  hexadezimale ZalU hh \ 1 \\ \1 \' \" \000 \xhh . 
186 I -Sprachbeschreibung male Ersatzdarstellungen verwendet werden; das Resultat ist undefmiert, wenn der ange- gebene Wert nicht mit wchar _t repräsentiert werden kann. Manche dieser Ersatzdarstellungen sind neu, insbesondere die hexadezimale Zeichendar- stellung. Erweitene Zeichenkonstanten sind ebenfalls neu. Die Zeichensätze, die nor- malerweise in Amerika und West-Europa verwendet werden, können so codiert werden, daß sie in den char-1yp passen; wc:r_t r ha  chlic.!t für asiatische Sprache n hinzugefüg!. A.2.s.3 Gleitpunktkonstanten Eine Gleitpunktkonstante besteht aus einem ganzzahligen Teil, einem Dezimal- punkt, einem Dezimalbruch, dem Zeichen e oder E, einem ganzzahligen Exponenten mit optionalem Vorzeichen und einem optionalen Typ-Suffix, nämlich einem der Buchstaben f, F, I oder L. Ganzzahliger Teil und Dezimalbruch sind Ziffemfolgen. Entweder der ganzzahlige Teil oder der Dezimalbruch kann fehlen (aber nicht beide); entweder der Dezimalpunkt oder der Exponent beginnend mit e kann fehlen (aber nicht beide). Der Typ der Konstanten wird durch das SuffIX bestimmt; F oder f mache!l, sie -.E! .!t, die SuffIXe L ode r I machen si  Z\!.!ong do !?le; andr alls_ hi_ _Typ double . SuffIXe an Gleitpunktkonstanten sind neu. A.2.5.4 Aufzählungskonstanten Namen, die in Aufzählungen vereinbart werden (siehe  A.8.4), sind Konstanten vom Typ int. A.2.6 Konstante Zeichenketten Eine konstante Zeichenkette (string litera/) ist eine Folge von Zeichen eingeschlos- sen in Doppelanführungszeichen, also ...... Eine_k()tante ZeichI1k_te_s yektor von_?-eichen mit Speicherklassstatic betrachtet (siehe unten  A.4) und mit den angege- benen Zeichen initialisiert. 9!>J.!itis.f!! !c()e_ich_n!c!:.!te!c!!__s!!t_d, hängt VOn der Implementi(:rung ab, ulld die Resultate eines Progr.amll1!_, eitt__- tichenketie zu_I11_vrut.i!. llDd [_ert. Aufeinanderfolgende konstante Zeichenketten werden in eine einzige Zeichenkette zusammengefaßt. Nach allen Verkettungen wird ein NUL-Zeichen \0 an die Zeichenket- te angefügt, damit Programme, die die Zeichenkette absuchen, ihr Ende fmden können. Um Zeilentrenner oder Doppelanführungszeichen in konstanten Zeichenketten darzu- stellen, gibt es die gleichen Ersatzdarstellungen wie bei Zeichenkonstanten; direkt dürfen sie nicht vorkommen. Wie Zeichenkonstanten werden auch konstante Zeichenketten in einem erweiter- ten Zeichensatz mit einem vorangestellten L geschrieben, also L...... Erweiterte konstan- te Zeichenketten (wide-character string literals) werden als Vektoren von wchar_t be- trachtet. Eine Verkettung von normalen und erweiterten konstanten Zeichenketten ist undefmiert. f Die Bestimmung, daß konstante Zeichenketten nicht verschieden sein müssen, und das Verbot, sie zu ändern, sind neu im ANSl-Standard, ebenso wie auch die Verkettung be- nachbaner konstanter Zeichenketten. ElWeitene konstante Zeichenketten sind neu. r I A.3 Syntax-Schre:- lse 187 A.3 Syntax-Schreibweise In der Syntax-Darstellung in diesem Anhang werden Grammatikbegriffe kursiv und reservierte Worte und Zeichen in Schreibmaschinenschrift geschrieben. Al- ternative Formulierungen erscheinen normalerweise auf verschiedenen Zeilen; in ein paar Fällen steht auch eine längere Liste von kurzen Alternativen auf einer Zeile, vor der dann "eins von" steht. Ein optionales Symbol ist mit dem Index ,,Opt" markiert: { expressionopt } bezeichnet zum Beispiel die optionale Angabe von expression, umgeben von geschweiften Klammern. Die Syntax wird in  A.B zusammengefaßt. Anders als in der Grammatik in der (englischen) Ersten Ausgabe, werden in der vorlie- genden Grammatik Vorrang und Assoziativität von Operatoren in Ausdrucken explizit dargestellt. A.4 Die Bedeutung von Namen Namen (identifier) bezeichnen eine Vielzahl VOn Dingen: Funktionen, Etiketten (tags) von Strukturen, Unionen und Aufzählungen; Komponenten von Strukturen und Alternativen von Unionen; Aufzählungskonstanten; Typnamen und schließlich Objekte. Ein Objekt, manchmal auch Variable genannt, ist ein Speicherbereich, und seine Inter- pretation hängt von zwei hauptsächlichen Attributen ab: der Speicherklasse und dem 'Typ. Die Speicherklasse bestimmt die Lebensdauer des Speicherbereichs, der mit einem Objekt verbunden ist; der Typ bestimmt die Bedeutung der Werte, die in diesem Speicherbereich gefunden werden. Ein Name hat auch einen Gültigkeitsbereich, das ist der Teil des Programms, in dem der Name bekannt ist, und eine Bindung (/inkage), von der abhängt, ob sich der gleiche Name in einem anderen Gültigkeitsbereich auf das glei- che Objekt oder die gleiche Funktion bezieht. Gültigkeitsbereich und Bindung werden in  A.ll besprochen. A.4.1 Speicherklasse Es gibt zwei Speicherklassen: automatisch und statisch. Zusammen mit dem Kon- text der Vereinbarung eines Objekts bestimmen mehrere reservierte Worte die Speicher- klasse. Automatische Objekte existieren lokal in einem Block (A.9.3) und werden bei Verlassen des Blocks aufgehoben. Defmitionen in einem Block erzeugen automatische Objekte, wenn keine Speicherklasse angegeben ist oder wenn auto verwendet wird. Mit register defmierte Objekte sind automatisch und werden (falls möglich) in schnellen Hardware-Registern gespeichert. Statische Objekte können lokal in einem Block oder auch außer halb von allen Blöcken vereinbart werden, aber sie behalten in beiden Fällen ihre Werte bei Verlassen von und Wiedereintritt in Funktionen und Blöcke. In einem Block, und auch in dem äußersten Block einer Funktion, werden statische Objekte mit static defmiert. Objekte, die außer halb von allen Blöcken, auf der gleichen Ebene wie Funktionen, definiert wer- den, sind immer statisch. Mit static können sie lokal für eine Übersetzungseinheit ver- einbart werden; dadurch erhalten sie interne Bindung (internallinkage). Für ein ganzes Programm werden sie global bekannt, wenn keine Speicherklasse explizit angegeben ist oder durch Verwendung von extern; dadurch erhalten sie externe Bindung (extema/ linkage ). 
prachbeschreibung A.5 Objekte und . 'erte 189 188 A A.4.2 Elementare Datentypeo Es gibt mehrere elementare Datentypen. Die Standard-Detinitionsdatei < Iimlts.h >, die im Anhang B beschrieben wird, definiert die größten und kleinsten Wer- te für jeden Typ in der lokalen Implementierung. Die im Anhang B angegebenen Zahlen sind die kleinstmöglichen Größen. Als char vereinbarte Objekte sind groß genug für jedes Zeichen aus dem Zeichen- satz der Maschine, auf der das Programm ausgeführt wird. Wenn wirklich ein Zeichen aus diesem Zeichensatz in einem char-Objekt gespeichert wird, entspricht sein Wert dem ganzzahligen Code dieses Zeichens und ist nicht negativ. In char- Variablen können auch andere Werte gespeichert werden, aber der mögliche Wertebereich und insbesondere, ob der Wert vorzeichenbehaftet ist, hängt von der Implementierung ab. Vorzeichenlose Zeichen, die als uoslgned char vereinbart werden, benötigen eben- soviel Platz wie einfache Zeichen, aber sie erscheinen niemals negativ; explizit mit Vor- zeichen versehene Zeichen, die als slgned char vereinbart werden, benötigen ebenfalls gleichviel Platz wie einfache Zeichen. unslgned char erscheint nicht in der Ersten Ausgabe, ist aber allgemein in Gebrauch. slgned char ist neu. Neben den char-Typen gibt es bis zu drei Größen von ganzzahligen Objekten, im folgenden Integer genannt, die als short Int, lot und loog lot vereinbart werden. "Einfa- che" Int-Objekte haben eine der jeweiligen Maschine entsprechende, natürliche GröBe ; 9,ie. aJi(:ren GröL3Il.s.i!t.(I fur4elle_ E!ford!!l!s.se gedacht. Längere Integer-Objekte bieten wenigstens soviel Speicherplatz wie die kürzeren, aber die Implementierung kann einfache Integer-Objekte äquivalent zu short Int oder zu long int machen. Falls nicht an- ders angegeben, repräsentieren alle lnt-Typen Werte mit Vorzeichen. Vorzeichenlose Integer-Werte, die mit unsigned vereinbart werden, gehorchen den üblichen arithmetischen Regeln modulo 1!', wobei n durch die Anzahl Bits in der Reprä- sentierung festgelegt wird. Arithmetik mit vorzeichenl_os , erten kann deshalb niemal s zu Overflow führen. Die Menge der nicht-negativen Werte, die in einem vorzeichenbe- hafteten Objekt gespeichert werden kann, ist eine Untermenge der Werte, die im ent- sprechenden vorzeichenlosen Objekt gespeichert werden können, und in beiden gemein- sam vorhandene Werte werden gleich repräsentiert. Jeder der Gleitpunkttypen, mit einfacher (Doat), doppelter (double) oder zusätzli- cher Genauigkeit (Iong double), kann zu anderen synonym sein, aber die, die in dieser Liste später auftauchen, sind wenigstens so genau wie die früheren. 10Dg double ist neu. In der Ersten Ausgabe war 10Dg noat äquivalent zu double, aber diese Umschreibung ist nicht mehr erlaubt. Aufzäh/ungen (enumerations) sind eindeutige Typen, die ganzzahlige Werte besit- zen; zu jeder Aufzählung gehört eine Menge benannter Konstanten (A.8.4). Aufzäh- lgn ve!hten sich wie Integer-Werte, aber eiIl Übersetzer wird häufig eine Warnung .1t'!5geben, wenn an ein Objekt mit einem bestimmten Aufzählungstyp etwas anderes zu- gewiesen wird, als eine seiner Konstanten oder ein Ausdruck mit dem gleichen Typ. Die bisher besprochenen Typen werden als arithmetische Typen bezeichnet, da ihre Objekte als numerische Werte interpretiert werden können. char- und Int-Typen aller Größen, mit und ohne Vorzeichen, wie auch Aufzählungstypen, werden im folgenden ins- gesamt als Integer- 'Typen bezeichnet. Doat, double und long double werden G/eitpunkttypen genannt. Der Typ void bezeichnet eine leere Menge von Werten. Er wird als Resultattyp von Funktionen verwendet, die keine Werte erzeugen. A.4.3 Abgeleitete Typen Zusätzlich zu den elementaren Typen kann eine praktisch unbegrenzte Zahl abge- leiteter Typen aus den elementaren Typen folgendermaßen konstruiert werden: Vektoren (an-ays) von Objekten mit einem bestimmten Typ; Funktionen, die Objekte eines bestimmten Typs liefern; Zeiger auf Objekte eines bestimmten Typs; Strukturen, die eine Folge von Objekten verschiedener Typen enthalten; Unionen, die jeweils ein Objekt aus einer Reihe verschiedener Typen enthalten. Diese Methoden zur Konstruktion von Objekten können überall rekursiv angewendet werden. A.4.4 Attribute rür Typen Der Typ eines Objekts kann zusätzliche Attribute (qua/ifiers) besitzen. Wenn ein Objekt als const vereinbart wird, bedeutet das, daß sein Wert nicht verändert wird; volatile bedeutet, daß ein 0i?kkt s:rieile- Eigens Chaften 'beSiiZt, die bei optim ierung r!i!g( eii i.1!i.e. -Ke' Attribut' beeinflußt de WertebereiC1i u oder 'die arithmetischen Eigenschaften eines Objekts. Attribute werden in  A.8.2 besprochen. A.S Objekte und L-Werte Ein Objekt ist ein benannter Speicherbereich; ein L- Wen ist ein Ausdruck, der ein Objekt bezeichnet. Ein triviales Beispiel für einen L-Wert-Ausdruck ist ein Name mit geeignetem Typ und passender Speicher klasse. Es gibt Operatoren, die L- Werte liefern: ist etwa E ein Ausdruck, der einen Zeigerwert liefert, dann ist *E ein L-ert-!l5druc! , der das Objekt bezeichnet, auf das E zeigt. Die Bezeichnung "L-Wert" erinnert an die Zuweis !!g .,.. = ,,. i der:.de.r.!i:_9r:.._e.i!t.. I:: e.!=_c(iil _lD uB: Bei der Beschreibung der Operatoren ist jeweils angegeben, ob ein Operator L- Werte als Operanden erwartet und ob er einen L-Wert als Resultat liefert. A.6 1YPumwandlungen Einige Operatoren können, je nach ihren Operanden, implizite Typumwandlungen verursachen, bei denen der Wert eines Operanden aus einem Datentyp in einen anderen Typ umgewandelt wird. In diesem Abschnitt wird das Resultat erklärt, das von diesen Typumwandlungen erwartet werden kann.  A.6.5 faßt die Typumwandlungen zusam- men, die die meisten gewöhnlichen Operatoren benötigen; eventuell nötige Ergänzungen fmdet man bei der Definition der einzelnen Operatoren. A.6.1 Integer-Enveiterung Ein Zeichen, ein kurzer Integer-Wert oder ein Integer-Bit-Feld, alle mit oder ohne Vorzeichen, sowie ein Objekt mit einem Aufzählungstyp, !Cji ber in inem Ausdruck 
190 '-Sprach beschreibung verwendet werdell,\Vo_. in Integer-Objekt benötigt d. Wenn Int alle Werte des ur- sprünglichen Typs darstellen kann, wird der Wert in Int umgewandelt; andernfalls wird der Wert in unslgned Int umgewandelt. Dieser Vorgang wird als Integer-Erweite1Ung (integral promotion) bezeichnet. A.6.2 Integer-Umwandlung Ein Integer-Wert wird folgendermaßen in einen vorgegebenen vorzeichenlosen Typ umgewandelt: Man fmdet den kleinsten, nicht-negativen Wert, der kongruent zum Integer-Wert ist. Als Modulus dient dabei der größte Wert, der im vorzeichenlosen Typ dargestellt werden kann, plus eins. Bei Darstellung im 2-Komplement ist das äquivalent zum Abschneiden am linken (signifikanten) Ende, wenn das Bit-Muster des vorzeichen- losen Typs schmäler ist, und zum Auffüllen von vorzeichenlosen Werten mit Null-Bits und zur Propagierung des Vorzeichens bei Werten mit Vorzeichen, wenn der vorzeichen- lose Typ breiter ist. Wenn ein beliebiger Integer-Wert in einen vorzeichenbehafteten Typ umgewandelt wird, bleibt der Wert unverändert, wenn er im neUen Typ dargestellt werden kann; an- dernfalls ist das Resultat implementierungsabhängig. A.6.3 Integer- und GleItpunktwerte Wenn ein Gleitpunktwert in einen Integer-Typ umgewandelt wird, wird der Dezi- malbruch abgeschnitten; kann der Resultatwert nicht im Integer-Typ dargestellt werden, ist der Effekt undetiniert. Insbesondere ist das Resultat unbestimmt, wenn negative Gleitpunktwerte in vorzeichenlose Integer-Typen umgewandelt werden. Wenn ein Wert aus einem Integer-Typ in einen Gleitpunkttyp umgewandelt wird und wenn der Wert im darstellbaren Bereich liegt aber nicht exakt dargestellt werden kann, kann das Resultat entweder der nächsthöhere oder der nächstniedrigere darstellba- re Wert sein. Liegt das Resultat nicht im zulässigen Bereich, ist der Effekt undetiniert. A.6.4 Gleitpunkttypen Wenn ein weniger genauer Gleitpunktwert in einen gleich oder höher genauen Gleitpunkttyp umgewandelt wird, bleibt der Wert unverändert. Wenn ein Gleitpunktwert höherer Genauigkeit in einen Gleitpunkttyp mit geringerer Genauigkeit umgewandelt wird, und wenn der Wert im darstellbaren Bereich liegt, kann das Resultat entweder der nächsthöhere oder der nächstniedrigere darstellbare Wert sein. Liegt das Resultat nicht im zulässigen Bereich, ist der Effekt undefiniert. A.6.5 Arithmetische Umwandlungen Viele Operatoren verursachen Typumwandlungen und liefern Resultattypen auf die gleiche Weise. Ihr Effekt besteht darin, daß die Operanden in einen gemeinsamen Typ umgewandelt werden, der dann auch der Resultattyp ist. Dieses Muster wird als Übliche arithmetische Umwandlungen bezeichnet. Zuerst wird, wenn einer der beiden Operanden long double ist, der andere in long double umgewandelt. Andernfalls wird, wenn einer der beiden Operanden double ist, der andere in double umgewandelt. A.6 Typumwar: 1gen 191 Andernfalls wird, wenn einer der beiden Operanden float ist, der andere in float um- gewandelt. Andernfalls werden beide Operanden der Integer-Erweiterung unterworfen; wenn dann einer der beiden Operanden unslgned long Int ist, wird der andere in unsigned long Int umgewandelt. Andernfalls, wenn ein Operand long int und der andere unsigned int ist, hängt der Effekt davon ab, ob long int alle Werte von unsigned int darstellen kann. Falls ja, wird der eine Operand von unslgned Int in long Int umgewandelt; falls nein, werden beide in unsigned iong int umgewandelt. Andernfalls, wenn ein Operand long int ist, wird der andere in iong int umgewandelt. Andernfalls, wenn einer der beiden Operanden unsigned Int ist, wird der andere in unsigned int umgewandelt. Andernfalls haben beide Operanden den Typ iot. Hier gibt es zwei Änderungen. Erstens kann !üitei}(J{If1 at-Operanden mit einf a- cCl" Glluigl«:it _er!()lgn, statt mit doppelter; die Erste Ausgabe verlangte, daß alle Gleitpunktarithmetik mit doppelter Genauigkeit erfolgte. Zweitens resultieren aus vor- zeichenlosen kürzeren 'JYpen, die mit größeren vorzeichenbehafteten 'JYpen kombiniert werden, keine vorzeichenlosen Resultattypen; in der Ersten Ausgabe setzten sich die vor- zeichenlosen immer durch. Die neuen Regeln sind ein biß ehen komplizierter, aber sie verringern bis zu einem gewissen Grad die Überraschungen, die sich ergeben, wenn ein vorzeichenloser Wert auf einen vorzeichenbehafteten Wert trifft. Unerwartet Erge ni!ö:' _ es_I111 J!!1!11_e!.g ebel1L  JE._voich_e_l!!0se!" ,l1sdruck mit einem vorzejc.hen- _bel1eI!.AI1 .&!e,lJer _(J!"öe verglichen wi!d. . A.6.6 Zeiger und Integer-Werte Ein Ausdruck mit einem Integer-Typ kann zu einem Zeiger addiert oder VOn ihm subtrahiert werden; der Integer-Ausdruck wird dabei so umgewandelt, wie bei der Be- schreibung der Addition (A.7.7) erklärt wird. Zwei Zeiger auf Objekte vom gleichen Typ, im gleichen Vektor, können voneinan- der subtrahiert werden; das Resultat wird so in einen Integer-Wert umgewandelt, wie bei der Beschreibung der Subtraktion (A.7.7) erklärt wird. Ein konstanter Integer-Ausdruck mit Wert 0 oder ein entsprechender Ausdruck, der in den Typ vold · umgewandelt wurde, kann durch einen Umwandlungsoperator (cast), durch Zuweisung oder durch Vergleich in einen Zeiger auf einen beliebigen Typ umgewandelt werden. Dadurch entsteht ein Nullzeiger, der gleich jedem anderen Null- zeiger auf den gleichen Typ ist, aber verschieden von jedem Zeiger auf eine Funktion oder ein Objekt. Bestimmte andere Typumwandlungen, bei denen Zeiger beteiligt sind, sind erlaubt, haben aber implementierungsabhängige Aspekte. Sie müssen durch einen expliziten Umwandlungsopcrator angegeben werden (A.75 und A.8.8). Ein Zeigerwert darf in einen Integer-Typ umgewandelt werden, der für ihn groß genug ist; die nötige Größe ist implementierungsabhängig. Die Abbildungsfunktion ist ebenfalls implementierungsabhängig. ! 
192 A ;prachbeschreibung Ein Objekt mit Integer-Typ kann explizit in einen Zeiger umgewandelt werden. Die Abbildung ist zwar implementierungsabhängig, führt aber einen Zeiger, der in einen genügend großen Integer-Typ umgewandelt wurde, wieder in den gleichen Zeiger zurück. Ein Zeiger auf einen Typ darf in einen Zeiger auf einen anderen Typ umgewandelt werden. Der resultierende Zeiger kann Adressierungsfehler verursachen, wenn der ur- sprüngliche Zeiger nicht auf ein Objekt zeigt, das geeignet im Speicher ausgerichtet ist. Es wird garantiert, daß ein Zeiger auf ein Objekt in einen Zeiger auf ein Objekt und zurück umgewandelt werden darf, ohne daß sich etwas ändert, wenn das zweite Objekt eine weniger oder gleich einschränkende Ausrichtung benötigt. Das Konzept der ,.Aus- richtUJ!g:: (l.. Umplementier E hän& aJ>e!,<lie .Q.!>i!c: der , cha !"..::!YP.e_n ha b.eAic: griI1gstc:n EMungc:nJli tli<!_Ausri<:!! tUJ1g. Wie in  A.6.8 beschrie- ben, darf ein Zeiger auch in den Typ vold · und zurück ohne Änderung umgewandelt werden. Ein Zeiger darf in einen anderen Zeiger umgewandelt werden, der den gleichen Typ besitzt, abgesehen davon, daß Attribute ( A.4.4, A.8.2) zum Objekttyp, auf den der Zeiger verweist, hinzugefügt oder davon entfernt werden. Werden Attribute hinzugefügt, ist der neue Zeiger äquivalent zum alten, abgesehen von Einschränkungen durch die neu- en Attribute. Werden Attribute entfernt, unterliegen die Operationen für das betroffene Objekt immer noch den Attributen der ursprünglichen Vereinbarung. Ein Zeiger auf eine Funktion kann schließlich in einen Zeiger auf einen anderen Typ von Funktion umgewandelt werden. Wie die Funktion über den umgewandelten Zei- ger aufgerufen wird, ist implementierungsabhängig; wenn aber der umgewandelte Zeiger wieder in seinen ursprünglichen Typ zurück umgewandelt wird, ist das Resultat identisch mit dem ursprünglichen Zeiger. A.6.7 void Der (nicht-existente) Wert eines Objekts mit Typ vold kann in keiner Weise verar- beitet werden, und er darf weder explizit noch implizit in einen anderen Typ umgewan- delt werden. Da ein Ausdruck mit Typ void einen nicht-existenten Wert bezeichnet, kann ein solcher Ausdruck nur dort verwendet werden, wo der Wert nicht benötigt wird, zum Beispiel als Anweisung (A.9.2) oder als linker Operand eines Komma-Operators ( A.7.18). Mit einem Umwandlungsoperator kann ein Ausdruck in den Typ void umgewan- delt werden. (void) als Umwandlungsoperator dokumentiert zum Beispiel, daß der Re- sultatwert eines Funktionsaufrufs nicht beachtet wird, der als Anweisung verwendet wird. void erschien nicht in der (englischen) Ersten Ausgabe, aber ist bereits gebräuchlich. A.6.8 Zeiger auf void Jeder Zeiger auf ein Objekt darf in den Typ void · umgewandelt werden, ohne daß Information verlorengeht. Wenn das Resultat zurück in den ursprünglichen Zeigertyp umgewandelt wird, wird der ursprüngliche Zeigerwert wiederhergestellt. Anders als bei den Zeiger-zu-Zeiger- Umwandlungen, die in  A.6.6 besprochen wurden und die einen expliziten Umwandlungsoperator ]>enötigen, können Zeiger von und an Zeiger mit Typ void · zugewiesen und mit solchen verglichen werden. r A.7 Ausdrücke 193 Diese Interpretation von void · ist neu; davor spielte char · die Rolle des generischen Zeigers. Der ANsl-Standard segnet explizit ab, daß void · auf Objektzeiger in Zuweisun- gen und Vergleichen trifft, während bei anderen Zeigermischungen explizite Umwand- lungsoperatoren verlangt werden. A. 7 Ausdrücke Pie UnterabschniJte in c:  At> S_!!.!!t_ bezug l!.(.@p._hc:dc:l!\:'.or!ing <le 9raJ o geordnE !. So sind zum Beispiel die Ausdrücke, die als Operanden von + angegeben werden, genau die Ausdrücke, die in  A.7.1-A.7.6 definiert werden. IIlJE- de!!L{)I! rabschnitt , haben die O p era !_gLe jche .Y..'?I!!l'!-.8' Ob Operatoren links- oder rechts-assoziativ sind, ist in jedem Unterabschnitt für die dort beschriebenen Operatoren angegeben. Die Grammatik beinhaltet Vorrang und Assoziativität der Operatoren in Ausdrücken; sie ist in  A.13 zusammengefaßt. VorranE u_n(L<>.ziativi!ät deI' Op!.ato!!l_ is'-()tändig festgelegt, aber die Rei- hell!olge, in der Ausdrücke bewertet werde!!, j!;!,_l!1it\VenigIl_A\!snahll1en, ndefiniert, und das selbst dann, wenn Teilausdrücke Nebenwirkungenverursachen. Das heißt, falls die Definition eines Operators nicht garantiert, daß seine Operanden in einer bestimm- ten Reihenfolge bewertet werden, bleibt es der Implementierung freigestellt, Operanden in jeder beliebigen Reihenfolge zu bewerten oder gar ihre Bewertungen überlappend vorzunehmen. Jeder Operator verbindet allerdings die Werte, die seine Operanden pro- duzieren, in einer Form, die kompatibel mit der Erkennung des Ausdrucks ist, in dem der Operator auftritt. Spät in ihren Verhandlungen beschloß die ANsl-Kommission, die bisherige Freiheit ein- zuschränken, daß Ausdrücke umstrukturiert werden dürfen, wenn sie Operatoren enthal- ten, die zwar in der Mathematik assoziativ und kommutativ sind, die aber numerisch nicht assoziativ sind. In der Praxis betrifft diese Änderung nur Gleitpunktberechnungen an der Grenze ihrer Genauigkeit sowie Situationen, in denen Overflow möglich ist. pie Behandlung von O qftq_w..L QivisiE _ßrc.h Nllll.!.l _ ande _!lI!_'l.hen _ei  r Bewertung von Aus !.!l_i._r_pr.: ni.tAe.f!:. Die meisten existieren- den C-Implementierungen ignorieren Overflow bei der Auswertung von vorzeichenbehaf- teten Integer-Ausdrücken und Zuweisungen, aber dieses Verhalten wird nicht garantiert. Die Behandlung von Division durch Null und allen Ausnahmen bei Gleitpunktrechnung varüert zwischen verschiedenen Implementierungen; manchmal kann sie durch nicht- standardisierte Bibliotheksfunktionen beeinflußt werden. A.7.1 Erzeugung von Zeigerwerten Ist der Typ eines Ausdrucks oder Teilausdrucks ..Vektor von T" für irgendeinen Typ T, dann ist der Wert des Ausdrucks ein Zeiger auf das erste Objekt im Vektor, und der Typ des Ausdrucks wird abgeändert in ..Zeiger auf T". Diese Typumwandlung findet nicht statt, wenn der Ausdruck als Operand des unären Adreß-Operators & oder der Operatoren ++, --, sizeof oder als linker Operand eines Zuweisungsoperators oder des Strukturoperators . angegeben ist. Analog wird ein Ausdruck vom Typ ..Funktion rnit Resultattyp T", der nicht als Operand des Adreß-Operators & verwendet wird, umge- wandelt in ..Zeiger auf Funktion, die T liefert". Ein Ausdruck, der derartig umgewandelt wurde, ist kein L-Wert. t 
194 . 2-Sprachbeschreibung A. 7.2 Primirausdrücke Primärausdrücke sind Namen, Konstanten, konstante Zeichenketten oder Aus- drücke in Klammern. primary-expression: identifier constant string ( expression ) Ein Name ist ein Primärausdruck, vorausgesetzt, er wurde geeignet vereinbart, wie später noch ausgeführt wird. Sein Typ wird durch seine Vereinbarung festgelegt. Ein Name ist ein L- Wert, wenn er sich auf ein Objekt (A.5) bezieht und wenn der Typ arith- metisch, eine Struktur, eine Union oder ein Zeiger ist. Eine Konstante ist ein Primärausdruck. Ihr Typ hängt von ihrer Form ab, wie in  A,2.5 besprochen. Eine konstante Zeichenkette ist ein Primärausdruck. Ihr Typ ist ursprünglich ..char- Vektor" (bei erweiterten konstanten Zeichenketten ..wchar t- Vektor"), aber auf Grund der Regel in  A.7.1 wird dies normalerweise in "Zeiger au(char" (wchar t) um- gewandelt, und das Resultat ist ein Zeiger auf das erste Zeichen in der Zeich;nkette. Die Typumwandlung erfolgt auch nicht bei bestimmten lnitialisierungen; siehe  A.8.7. Ein Ausdruck in Klammern ist ein Primärausdruck, dessen Typ und Wert identisch zu denen des Ausdrucks ohne Klammem sind. Die Klammem beeinflussen nicht, ob der Ausdruck ein L- Wert ist. A. 7.3 Postfix-AusdrOcke Post fix-Ausdrücke werden von links nach rechts zusammengefaßt. postflX-expression: primary-expression postflX-expression [ expression ] postflX-expression ( argument-expression-list opl ) postflX-expression . identifier postflX-expression -> identifier postflX-expression ++ postflX-expression -- argument-expression-/ist: assignment-expression argument-expression-list , assignment-expression A. 7 .3.1 Vektorelemente Ein PostflX-Ausdruck gefolgt von einem Ausdruck in eckigen Klammem ist ein PostflX-Ausdruck und bezeichnet einen indizierten Vektorverweis (subscripted a"ay reference). Einer der beiden Ausdrücke muß den Typ ..Zeiger auf T" besitzen, dabei ist T irgendein Typ, und der andere Ausdruck muß einen Integer-Typ besitzen; der Typ des In- dexausdrucks ist T. Der Ausdruck El[E2] ist (nach Definition) identisch zu *( (EI) + (E2». Dies wird in  A.8.6.2 weiter besprochen. r A.7 Ausdrücke 195 A. 7.3.2 Funktionsaurrure Ein Funktionsaufruf ist ein PostflX-Ausdruck, der sogenannte Funktionsbezeichner ifunction designator), gefolgt von einer Liste von Zuweisungsausdrücken (A,7.17), die durch Komma getrennt und zusammen in Klammem eingeschlossen sind. Die Liste von Ausdrücken innerhalb der Klammern kann leer sein; die Werte der Ausdrücke sind die Funktionsargumente. Wenn der PostflX-Ausdruck aus einem Namen besteht, für den es im aktuellen Gültigkeitsbereich keine Vereinbarung gibt, wird der Name implizit so de- klariert, als ob die Deklaration extern Int idenrifier(); im innersten Block angegeben worden wäre, der den Funktionsaufruf enthält. Der Post- fIX-Ausdruck (möglicherweise nach einer impliziten Deklaration und Erzeugung eines Zeigerwerts nach  A.7.1) muß den Typ "Zeiger auf Funktion, die T liefert" besitzen, für irgendeinen Typ T, und der Wert des Funktionsaufrufs hat den Typ T. In der Ersten Ausgabe war der 1YP auf "Funktion" beschränkt, und ein expliziter Inhalts- operator · war nötig, um Funktionen über Zeiger aufzurufen. Der ANSI-Standard segnet die Gepflogenheiten mancher existenter Übersetzer ab und erlaubt die gleiche Syntax zum Aufruf von Funktionen und von Funktionen, die durch Zeiger ausgewählt werden. Die ältere Syntax kann noch immer verwendet werden. Die Bezeichnung Argument wird für einen Ausdruck verwendet, der bei einem Funktionsaufruf übergeben wird; Parameter bezieht sich auf das Eingabeobjekt (oder sei- nen Namen), das die Funktionsdefinition empfängt oder das in einer Funktionsdeklarati- on beschrieben wird. Die Bezeichnungen Aktual- und Formalparameter dienen manch- mal zur gleichen Unterscheidung. In Vorbereitung für einen Funktionsaufruf wird von jedem Argument eine Kopie erzeugt; grundsätzlich werden nur Argumentwerte übergeben. Eine Funktion kann die Werte ihrer Parameterobjekte ändern, die Kopien der Argumentausdrücke sind, aber diese Änderungen können die Werte der Argumente nicht erreichen. Man kann jedoch einen Zeiger in der Absicht übergeben, daß die Funktion das Objekt ändert, auf das der Zeiger zeigt. Es gibt zwei Stile, in denen Funktionen deklariert werden können. Im neuen Stil sind die Typen der Parameter explizit angegeben, und sie sind Teil des Typs der Funktion; eine derartige Deklaration wird als Funktionsprototyp bezeichnet. Im alten Stil werden die Typen der Parameter nicht angegeben. Die Vereinbarung von Funktionen wird in  A.8.6.3 und A.10.1 besprochen. Wenn die für einen Aufruf geltende Funktionsdeklaration den alten Stil verwendet, wird eine voreingesteUte Erweiterung der Argumente auf jedes Argument folgender- maßen angewendet: Integer-Erweiterung (A.6.1) erfolgt für jedes Argument mit Integer-Typ, und jedes Argument mit Typ float wird in double umgewandelt. Der Effekt des Aufrufs ist undefmiert, wenn die Anzahl der Argumente nicht mit der Anzahl der Pa- rameter in der Funktionsdefinition übereinstimmt oder wenn der Typ eines Arguments nach Erweiterung nicht dem Typ des zugehörigen Parameters entspricht. Die Überein- stimmung der Typen hängt davon ab, ob die Funktion im neuen oder alten Stil definiert ist. Beim alten Stil werden der erweiterte Typ des Arguments aus dem Aufruf und der erweiterte Typ des Parameters verglichen; erfolgte die Definition im neuen Stil, muß dr 
196 -Sprachbeschreibung A.7 Ausdrücke 197 I e'!YiterJ'yp des Arguments der 1)p des Parameters selbst sein, der dann nicht erwei- tert wild. - -------.--- .------ --- _____'o.O<,_u_ -. halten ist, wird der Operand um 1 bei ++ inkrementiert oder bei -- dekrementiert. Der Operand muß ein L-Wert sein; man beachte die Beschreibung der additiven Operatoren (A.7.7) und der Zuweisung (A.7.17) für weitere Einschränkungen in bezug auf den Operanden und Details der Operation. Das Resultat ist kein L-Wert. A.7.4 Uniire Operatoren Ausdrücke mit unären Operatoren werden von rechts nach links zusammengefaßt. unary-expression: postflX-expression ++ unary-expression -- unary-expression unary-operator cast-expression sizeofunary-expresnon sizeof ( type-name) unary-operator: eins von & * + Gilt für einen Aufruf eine Funktionsdeklaration im neuen Stil, dann werden die Ar- gumente wie bei einer Zuweisung in die Typen der zugehörigen Parameter aus dem Funktionsprototyp umgewandelt. Die Anzahl der Argumente muß gleich der Anzahl der explizit beschriebenen Parameter sein, es sei denn, die Parameterliste in der Deklaration endet mit der Auslassung (, ...). In diesem Fall muß die Anzahl der Argumente gleich oder größer als die Anzahl der Parameter sein; auf Argumente, die nach den explizit be- schriebenen Parametern folgen, wird die im vorigen Absatz beschriebene voreingestellte Argumenterweiterung angewendet. Verwendet die Definition der Funktion den alten Stil, dann muß der Typ jedes Parameters in dem Prototyp, der beim Aufruf sichtbar ist, mit dem entsprechenden Parameter in der Definition übereinstimmen, nachdem die Ar- gumenterweiterung auf den Typ des Parameters in der Defmition angewendet wurde. Diese Regeln sind außerordentlich kompliziert, denn sie müssen mit einer Mischung von Funktionen alten und neuen Stils fertigwerden. Wenn möglich, sollten Mischungen ver- mieden werden. Die Reihenfolge, in der die Argumente bewertet werden, ist nicht festgelegt; man beachte, daß sich verschiedene Übersetzer hierin unterscheiden. Die Argumente und der Funktionsbezeichner werden jedoch vollständig bewertet, mit allen Nebenwirkungen, bevor die Ausführung der Funktion beginnt. Funktionen können immer auch rekursiv aufgerufen werden. A. 7 .4.1 Prinx-Inkrementlerung Ein unärer Ausdruck, dem der Operator ++ oder -- vorausgeht, ist ein unärer Ausdruck. Der Operand wird bei ++ um 1 inkrementiert und bei -- dekrementiert. Der Wert des Ausdrucks ist der Wert nach Inkrementierung (oder Dekrementierung). Der Operand muß ein L-Wert sein; man beachte die Beschreibung der additiven Opera- toren (A.7.7) und der Zuweisung (A.7.17) für weitere Einschränkungen in bezug auf den Operanden und Details der Operation. Das Resultat ist kein L-Wert. A. 7.3.3 Strukturverweise Ein Postfix-Ausdruck gefolgt von einem Punkt und einem Namen ist ein PostfIX- Ausdruck. Der erste PostflX-Ausdruck muß eine Struktur oder eine Union sein und der Name muß eine Komponente in der Struktur oder eine Alternative in der Union benen- nen. Der Wert ist der benannte Teil der Struktur oder Union, und sein Typ ist der Typ dieses Teils. Der Ausdruck ist ein L-Wert, wenn der erste Ausdruck ein L-Wert ist und wenn der Typ des zweiten Ausdrucks kein Vektortyp ist. Ein PostflX-Ausdruck gefolgt von einem Pfeil (konstruiert aus - und » und einem Namen ist wieder ein Postfix-Ausdruck. Der erste PostflX-Ausdruck muß ein Zeiger auf eine Struktur oder Union sein, und der Name muß eine Komponente in der Struktur oder eine Alternative in der Union benennen. Das Resultat bezieht sich auf den benann- ten Teil der Struktur oder Union, auf die der Zeigerausdruck zeigt, und sein Typ ist der Typ dieses Teils. Das Resultat ist ein L-Wert, wenn der Typ kein Vektortyp ist. Folglich ist der Ausdruck El->MOS der gleiche wie (*El).MOS. Struktur und Union werden in A.8.3 besprochen. In der Ersten Ausgabe gab es bereits die Regel, daß ein Komponenten- oder Alternati- J venname in einem derartigen Ausdruck zu der Struktur oder Union gehören sollte, die im PostflX-Ausdruck erwähnt wurde; ein Hinweis gab allerdings zu, daß diese Regel nicht streng geprüft wurde. Neuere Übersetzer, und ANSI, erzwingen es. A. 7 .4.2 Adreß-Operator Der unäre Qper!\t & ,!>er_(:ch..net c:lie. Adresse seines 0t>eranden. __I>__er .Qrerand muß ein L-Viert sein, der wder auf ein Bit-Feld noch auf ein Objekt verweist, das als register vereinbart wurde, oder er muß vom Typ Funktion sein. Das Resultat ist ein Zei- geraUC das Objekt oder die Funktion, auf die der L-Wert verweist. Hat der Operand den Typ T, so hat das Resultat den Typ "Zeiger auf TU. A. 7.4.3 Inhaltsoperator Der unäre Operator * dient für Verweise und liefert das Objekt oder die Funktion, auf die sein Operand zeigt. Das Resultat ist ein L-Wert, wenn der Operand ein Zeiger auf ein Objekt ist, dessen Typ arithmetisch, eine Struktur, eine Union oder ein Zeigertyp ist. Hat der Operand den Typ "Zeiger auf TU, so hat das Resultat den Typ T. A. 7 .3.4 Postfix-Inkrementlerung Ein PostflX-Ausdruck, dem der Operator ++ oder -- folgt, ist ein PostflX-Aus- druck. Der Wert des Ausdrucks ist der Wert des Operands. Nachdem der Wert festge- A.7.4.4 Unirer Plus-Operator Der Operand des unären Operators + muß einen arithmetischen Typ besitzen und das Resultat ist der Wert des Operanden. Für einen Integer-Operanden fmdet Integer- Erweiterung statt. Der Typ des Resultats ist der Typ des erweiterten Operanden. Das unäre + ist neu im ANsl-Standard. Es wurde aus Symmetrie zum unären - hinzu- gefUgt. 
198 -:-Sprachbeschreibung A.7 Ausdrücke 199 A.7.4.s Uniirer Minus-Operator Der Operand des unären Operators - muß einen arithmetischen Typ besitzen und das Resultat ist der negative Wert des Operanden. Für einen Integer-Operanden findet Integer-Erweiterung statt. Der negative Wert einer vorzeichenlosen Größe wird dadurch berechnet, daß der erweiterte Wert vom größtmöglichen Wert des erweiterten Typs sub- trahiert und eins addiert wird; der negative Wert von Null ist jedoch Null. Der Typ des Resultats ist der Typ des erweiterten Operanden. A.7.4.6 I-Komplement-Operator Der Operand des Operators - muß einen Integer-Typ besitzen und das Resultat ist das I-Komplement des Operanden. Integer-Erweiterung fmdet statt. Wenn der Ope- rand vorzeichenlos ist, wird das Resultat dadurch berechnet, daß der Wert vom größt- möglichen Wert des erweiterten Typs subtrahiert wird. Hat der Operand ein Vorzeichen wird der erweiterte Operand in den zugehörigen vorzeichenlosen Typ umgewandelt, ..: wird angewandt und dann wird zurück in den Typ mit Vorzeichen umgewandelt. Der Typ des Resultats ist der Typ des erweiterten Operanden. A.7.4.7 Logische Negation Der Operand des Operators! muß einen arithmetischen Typ besitzen oder ein Zei- ger sein. Das Resultat ist 1, wenn der Wert des Operanden gleich 0 ist; andernfalls ist das Resultat O. Der Typ des Resultats ist int. A. 7 .4.8 sizeof Der Operator sizeof liefert die Anzahl der Bytes, die benötigt werden, um ein Ob- jekt mit dem Typ seines Operanden zu speichern. Der Operand ist entweder ein Aus- druck, der nicht bewertet wird, oder ein Typname in Klammem. Wird sizeof auf char angewendet, ist das Resultat 1; wird sizeof auf einen Vektor angewendet, ist das Resultat die Gesamtzahl der Bytes im Vektor. Wird sizeof auf eine Struktur oder Union ange- wendet, ist das Resultat die Anzahl der Bytes im Objekt, inklusive etwaiger Bytes, die benötigt werden, damit das Objekt Element eines Vektors sein kann: die Größe eines Vektors mit n Elementen ist n mal die Größe eines Elements. Der Operator darf nicht auf ein Objekt vom Typ Punktion, auf einen unvollständigen Typ oder auf ein Bit-Feld angewendet werden. Das Resultat ist eine vorzeichenlose Integer-Konstante; der exakte Typ ist implementierungsabhängig. Die Standard-Definitions datei < stddef.h > (siehe Anhang B) definiert diesen Typ als size _ t. A.7.5 1YPumwandlungen Ein unärer Ausdruck, dem ein Typname in Klammern vorausgeht, wandelt den Wert des Ausdrucks in den angegebenen Typ um. cast-expression: unary-expression ( type-name) cast-expression Diese Konstruktion wird als Umwand/ungsoperation oder cast bezeichnet. Typnamen werden in  A.8.8 beschrieben. Die Effekte der Typumwandlungen werden in  A.6 be- schrieben. Ein Ausdruck mit einer Umwandlungsoperation ist kein L-Wert. A.7.6 Multiplikative Operatoren Die multiplikativen Operatoren *, fund % werden von links nach rechts zusam- mengefaßt. mu/tip/icative-expression: cast-expression mu/tip/icative-expression * cast-expression mu/tip/icative-expression / cast-expression mu/tip/icative-expression X cast-expression Die Operanden von * und f müssen arithmetische Typen besitzen; die Operanden von % müssen Integer-Typen besitzen. Für die Operanden fmden die üblichen arithmeti- schen Umwandlungen statt und sie definieren den Typ des Resultats. Der binäre Operator * bezeichnet Multiplikation. Der binäre Operator f liefert den Quotienten und der Operator % liefert den Rest nach Division des ersten Operanden durch den zweiten; ist der zweite Operand 0, so ist das Resultat undeflniert. Andernfalls gilt immer, daß (afb)*b + a%b gleich a ist. Sind . beide _Operanden nicht-negativ, dann ist der Rest nicht-negativ und kleiner als der öivi- .sor; falls nicht, wird nur garantieret, daß der absolute Wert des Rests kleiner ist als der ab- solute Wert des Divisors. ' - . A.7.7 Additive Operatoren Die additiven Operatoren + und - werden von links nach rechts zusammengefaßt. Wenn die Operanden arithmetische Typen besitzen, werden die üblichen arithmetischen Umwandlungen vorgenommen. Für jeden Operator gibt es noch einige andere mögliche Typen. additive-expression: mu/tip/icati ve-expression additive-expression + mu/tip/icati ve-expression additive-expression - mu/tip/icative-expression Der Operator + liefert die Summe seiner Operanden. Ein Zeiger, der auf ein Ob- jekt in einem Vektor verweist, und ein Integer-Wert dürfen addiert werden. Dabei wird der Integer-Wert in eine relative Adresse verwandelt, indem er mit der Länge des Ob- jekts multipliziert wird, auf das der Zeiger verweist. Die Summe ist ein Zeiger mit glei- chem Typ wie der ursprüngliche Zeiger, der auf ein anderes Objekt im gleichen Vektor verweist, das die entsprechende Adresse besitzt, relativ zum ursprünglichen Objekt. Wenn also P auf ein Objekt in einem Vektor zeigt, so zeigt P+ I auf das nächste Objekt in diesem Vektor. Wel!" <!i.S.!llJlm!lictl!lehr i!J. n Vektor zeigt und auch nicht auf die erste PO !!Q n n.l!ch dem _obe r n   Yekt() rs,-d ist das. R. tat I,tnde.!lI!i i;--- Die Möglichkeit von Zeigern unmittelbar hinter das Ende eines Vektors ist neu. Sie le- galisiert eine gebräuchliche Formulierung für eine Schleife über die Elemente in einem Vektor. Der Operator - liefert die Differenz seiner Operanden. Ein Integer-Wert darf von einem Zeiger subtrahiert werden, dann gelten Typumwandlungen und Bedingungen wie bei der Addition. 
200 -Sprachbeschreibung Wenn zwei Zeiger auf Objekte vom gleichen Typ subtrahiert werden, ist das Resul- tat ein Integer-Wert mit Vorzeichen, der den Abstand zwischen den Objekten repräsen- tiert, auf die die Zeiger zeigen; Zeiger auf aufeinanderfolgende Objekte unterscheiden sich um 1. Der Resultattyp ist implementierungsabhängig, aber er ist als ptrdifT_t in der Standard-Defmitionsdatei <stddef.h> vereinbart. Der Wert ist undefmiert, wenn die Zeiger nicht auf Objekte im gleichen Vektor zeigen; wenn allerdings P auf das letzte Ob- jekt im Vektor zeigt, dann hat (P+ 1) - P den Wert 1. A.7.8 Shift-Operatoren Die shift-Operatoren « und » werden von links nach rechts zusammengefaßt. Bei beiden Operatoren muß jeder Operand ein Integer-Wert sein und Integer-Erweite- rung fmdet statt. Der Resultattyp ist der Typ des erweiterten linken Operanden. pas e,ula istundC?f1J!.ie!.t!_e e.r_ rgj>e d negll tivist c:><ie_lUl. er e!t des !echt(: n O !.<iIt<!C?!1 ni !t!.kl!t!!._d!e.. des  e.n_2p! d (:_ Bits. shift-expression: additive-expression shift-expression «additive-expression shift-expression » additive-expression Der Wert von EI« E2 ist EI (interpretiert als Bit-Muster) um E2 Bits nach links ver- schoben; wenn kein Overflow eintritt, ist das äquivalent zu einer Multiplikation mit 1f2. Der Wert von EI»E2 ist EI um E2 Bit-Positionen nach rechts verschoben. Die Ver- schiebung nach rechts ist äquivalent zu einer Division durch 1f2, wenn EI vorzeichenlos ist oder keinen negativen Wert hat; andernfalls ist das Resultat implementierungsabhän- gig. A. 7.9 Vergleiche Vergleiche werden zwar VOn links nach rechts zusammengefaßt, aber diese Eigen- schaft nutzt nichts; a< b < c wird als (a< b) < C bearbeitet und a< b hat entweder den Wert o oder 1. re/ationaex,presnon: shift-ex,pression re/ationa/-ex,pression < shift-expression re/aliona/-ex,pression > shift-expression re/ationa/-expression <- shift-expression re/ationa/-expression >- shift-expression Die Operatoren< (kleiner), > (größer), <= (kleiner oder gleich) und >= (größer oder gleich) liefern alle 0, wenn die angegebene Relation falsch ist, und 1, wenn die Relation vorliegt. Der Resultattyp ist Int. Die üblichen arithmetischen Umwandlungen werden auf Operanden mit arithmetischen Typen angewendet. Zeiger auf Objekte mit gleichem Typ (dabei werden Attribute ignoriert) dürfen verglichen werden; das Resultat wird durch die relative Position der Objekte im Adreßraum bestimmt, auf die die Zeiger verweisen. Zeigervergleich ist nur für Teile des gleichen Objekts defmiert: wenn zwei Zeiger auf das gleiche einfache Objekt zeigen, sind sie gleich; wenn die Zeiger auf Komponenten der gleichen Struktur zeigen, gelten Zeiger auf später in der Struktur vereinbarte Komponen- ten als größer; wenn die Zeiger auf Alternativen in der gleichen Union zeigen, sind sie r A.7 Ausdrücke 201 gleich; wenn Zeiger auf Elemente des gleichen Vektors zeigen, ist der Vergleich äquiva- lent zum Vergleich der zugehörigen Indizes. Wenn P auf das letzte Element in einem Vektor zeigt, dann gilt P+l als größer, obgleich P+1 nicht mehr in den Vektor zeigt. Andernfalls ist der Vergleich von Zeigern undefmiert. Diese Regeln sind etwas weniger einschränkend als die Erste Ausgabe, denn sie erlauben Vergleiche von Zeigern auf verschiedene Teile einer Struktur oder Union. Sie erlauben außerdem den Vergleich mit einem Zeiger, der unmittelbar hinter einen Vektor zeigt. A. 7.10 Äquivalenzvergleiche equaJity-expression: re/ationa/-expression equa/ity-expression - re/ationa/-expression equa/ity-expression I - re/ationa/-expression Die Operatoren == (gleich) und ! = (nicht gleich) sind analog zu den anderen Ver- gleichsoperatoren in A.7.9, abgesehen von ihrem geringeren Vorrang. (a<b == c<d liefert also 1 genau dann, wenn die beiden Vergleiche a< bund c< d das gleiche Resultat liefern.) Die Äquivalenzvergleiche folgen den gleichen Regeln wie die Vergleichsoperato- ren, aber sie bieten zusätzliche Möglichkeiten: ein Zeiger darf mit einem konstanten Integer-Ausdruck mit Wert 0 oder mit einem Zeiger auf void verglichen werden. Siehe  A.6.6. A.7.11 UND-Verknüpfung von Bits AND-expression: equa/ity-expression AND-expression 6. equa/ity-ex,pression Die üblichen arithmetischen Umwandlungen finden statt; für das Resultat werden die Bits der beiden Operanden in jeder Position der UND- Verknüpfung unterworfen. Der Operator darf nur auf Integer-Operanden angewendet werden. A.7.12 Exklusive ODER-Verknüpfung von Bits exe/usive-OR-expression: AND-expression exe/usive-OR-expression " AND-expression Die üblichen arithmetischen Umwandlungen fmden statt; für das Resultat werden die Bits der beiden Operanden in jeder Position der exklusiven ODER- Verknüpfung unter- worfen. Der Operator darf nur auf Integer-Operanden angewendet werden. A.7.I3 ODER-Verknüpfung von Bits inclusive-OR-ex,pression: exclusive-OR-expression inclusive-OR-expression I exclusive-OR-expression Die üblichen arithmetischen Umwandlungen finden statt; für das Resultat werden die Bits der beiden Operanden in jeder Position der inklusiven ODER- Verknüpfung unterwor- fen. Der Operator darf nur auf Integer-Operanden angewendet werden. 
202 / -Sprachbeschreibung A.7.14 Logische UND-Verknüpfung logical-AND-expressioll: illclusive-OR-expressioll logical-AND-expression && inclusive-OR-expressioll Der Operator && wird von links nach rechts zusammengefaßt. Das Resultat ist I, wenn beide Operanden nicht gleich 0 sind; sonst ist das Resultat O. Anders ls  we r && - Operationen garantiert von links na .ch rechts durchgefü t: der erste Ope._.it #eii}li!WU:kulige-nber echne t ; is t er 0, so ist der Welt ds_lI.!!!_L And  fal!  d der rechte Operand berechnet; wenn er Oist, ist der Wert des Aus_Q_.n.()st t.. . Die Operanden brauchen nicht den gleichen Typ zu besitzen, aber jeder Operand muß einen arithmetischen Typ besitzen oder ein Zeiger sein. Das Resultat ist int. A.7.15 Logische ODER-Verknüpfung logical-OR-expression: logical-AND-expression logical-OR-expression 11 logical-AND-expression Der Operator 11 wird Von links nach rechts zusammengefaßt. Das Resultat ist 1, wenn wenigstens einer der beiden Operanden nicht gleich 0 ist; sonst ist das Resultat O. An- ders als I werden li-Operationen garantiert von links nach rechts durchgeführt: der er- ste Operand wird mit allen Nebenwirkungen berechnet; ist er verschieden von 0, so ist der Wert des Ausdrucks 1. Andernfalls wird der rechte Operand berechnet; wenn er ver- schieden von 0 ist, ist der Wert des Ausdrucks 1 und sonst O. Die Operanden brauchen nicht den gleichen Typ zu besitzen, aber jeder Operand muß einen arithmetischen Typ besitzen oder ein Zeiger sein. Das Resultat ist int. A.7.16 Bedingter Ausdruck collditiollal-expressioll: logical-OR-expression logical-OR-expressioll ? expressioll : cOllditiollal-expressioll Der erste Ausdruck wird mit allen Nebenwirkungen berechnet; falls er verschieden von 0 ist, ist das Resultat der Wert des zweiten Ausdrucks, sonst der des dritten Ausdrucks. In jedem Fall wird nur einer dieser beiden letzten Ausdrücke bewertet. Wenn der zweite und dritte Operand arithmetisch sind, werden die üblichen arithmetischen Umwandlun- gen angewendet, um sie zu einem gemeinsamen Typ zu bringen, und dies ist der Typ des Resultats. Sind beide void, oder Strukturen oder Unionen mit gleichem Typ, oder Zeiger auf Objekte mit gleichem Typ, dann hat das Resultat den gemeinsamen Typ. Ist einer dieser Operanden ein Zeiger und der andere die Konstante 0, wird 0 in den Zeigertyp umgewandelt, und das Resultat hat diesen Typ. Ist einer dieser Operanden ein Zeiger auf void und der andere ist ein anderer Zeiger, wird dieser andere Zeiger in einen Zeiger auf void umgewandelt, und das ist der Typ des Resultats. Beim Vergleich der Typen für Zeiger sind Typattribute (A.8.2) unerheblich im Typ, auf den der Zeiger zeigt, aber der Resultattyp erbt Attribute von beiden Zweigen der Bedingung. ,. A.7 Ausdrücke 203 A.7.17 Zuweisungen Es gibt verschiedene Zuweisungsoperatoren; alle werden von rechts nach links zu- sammengefaßt. assigttment-expression: conditional-expression unary-expression assigttment-operator assigttment-expression assigttment-operator: eins Von A_ I *- /- x- +- «- >>- &- - Der linke O perand muß immer ein L-Wert sein, und der L-Wert muß modifizi.erbar se: er darf kein VektöC sein, und er darf keinen unvollständigen Typ haben oder eme Funkti- on"Seh1."S cin Typ dälf ituch nicht das Attribut const haben; wenn er eine Strtur odr Union ist darf er weder direkt noch rekursiv eine Komponente oder Alternative beSit- zen, die  cODst-Attribut hat. Der. Resul!attyp einer uwE. _is .t de!:...des link en Ope- rande und der Wert ist der, der sich nach der ZuweISung 1lD lißken OperanaeiiDcliD- det. Bei der einfachen Zuweisung mit = ersetzt der Wert des Ausdrucks den Wert ds Objekts, das der L-Wert bezeichnet. Eine der folgenden Bedingungen muß gel.'en: bel- de Operanden besitzen arithmetische Typen, dann wird der rechte C?perand bei der u- weisung in den Typ des linken Operanden umgewandelt; oder belde Operanden smd Strukturen oder Unionen gleichen Typs; oder ein Operand ist ein Zeiger und der andee ist ein Zeiger auf vold; oder der linke Operand ist ein Zeiger d de rechte Operd 1St ein konstanter Ausdruck mit Wert 0; oder beide Operanden smd Zeiger auf Funktionen oder Objekte, deren Typen gleich sind, abgesehen davon, daß beim rechten Operanden const oder volatlle fehlen darf, Ein Ausdruck der Form EI op = E2 ist äquivalent zu E1 = EI op (E2), nur daß EI nur einmal bewertet wird. A. 7 .18 Komma als Operator expression: assigttment-expression expression , assigttment-expression Zwei Ausdrücke, die durch Komma getrc:nnt sind,"c:rden von" Ilc.h r,?(;htwertet; (ter Wert des linken" ÄUsdruCkS-Wird "bereChnet, aber nicht weiterverwendet. .!YP  d Wert des R esultats sind Typ und Wert des rechten Operanden. Alle Nebenwirkungen der Bewertung -destmken .Operanden weiden" abgesCh1o.ssen, Devor e Bewertung des rechten Operanden beginnt. In einem Kontext, in dem das Komma eme besondere Be- deutung hat, wie etwa in der Liste von Argumenten für eine.n Funktioufruf (A:7.3.2) und in Listen von lnitialisierungen (A.8.7), ist die notwendige syntaktISche Gruppierung ein Zuweisungsausdruck, folglich kann das Komma als Operator nur innerhalb von Klammem auftreten; beispielsweise hat fee, et3, t+2), c) drei Argumente, von denen das zweite den Wert 5 hat. 
204 I ..Sprachbeschreibung A.7.19 Konstante Ausdrücke Syntaktisch ist ein konstanter Ausdruck ein Ausdruck, in dem nur eine Untermen- ge der Operatoren verwendet werden darf: constant-expression: conditional-expressioll Ausdrücke, die ein konstantes Resultat liefern, werden an verschiedenen Stellen verlangt: nach case, zur Dimensionierung von Vektoren und als Längen von Bit-Feldern, als Wert einer Aufzählungskonstanten, bei lnitialisierungen und in bestimmten Ausdrücken für den Preprozessor. Konstante Ausdrücke dürfen keine Zuweisungen, Inkrement- oder Dekrement- Operatoren, Funktionsaufrufe oder Komma-Operatoren enthalten, ausgenommen in ei- nem Operanden von sizeof. Wenn ein konstanter Ausdruck einen Integer-Wert besitzen muß, dann müssen seine Operanden Integer-, Aufzählungs-, Zeichen- oder Gleitpunkt- konstanten sein; Umwandlungsoperationen müssen Integer-1Ypen angeben und Gleit- punktkonstanten müssen in Integer umgewandelt werden. Damit sind zwangsweise Vek- tor-, Inhalts-, Adreß- und Strukturkomponenten-Operationen nicht erlaubt. (Beliebige Operanden sind jedoch für sizeof erlaubt.) Konstante Ausdrücke in lnitialisierungen sind weniger eingeschränkt; die Operan- den können beliebige Konstanten sein, und der unäre Adreß-Operator & kann auf exter- ne oder statische Objekte sowie auf externe oder statische Vektoren mit einem konstan- ten Ausdruck als Index angewendet werden. Der unäre Adreß-Operator & kann auch implizit verwendet werden, dadurch daß Vektoren ohne Index sowie Funktionen auftre- ten. Der Wert einer lnitialisierung muß entweder konstant sein oder die Adresse eines früher vereinbarten externen oder statischen Objekts plus oder minus eine Konstante. Konstante Integer-Ausdrücke nach #if sind mehr eingeschränkt: sizeof- d!i!k ufzäl:1lungskonstanten und Umwandlungsoperationen sind nicht eilaubt. Siehe  A.12.5. A.8 Vereinbarungen Vereinbarungen legen die Interpretation der einzelnen vom Benutzer eingeführten Namen fest; sie reservieren nicht unbedingt den Speicherplatz, der zu einem Namen gehört. yereinbarungen, die Speicherplatz reservieren, werden Definitionen genannt. Vereinbarungen haben folgende Form: declaration: declaration-specifiers init-declarator-/istopt ; Die Deklaratoren in der init-declarator-/ist enthalten die Namen, die vereinbart werden; declaration-specifiers bestehen aus einer Folge von Angaben zu Typ und Speicherklasse. declaration-specifiers: storage-class-specifier declaration-specifiers opt type-specifier declaration-specifiers Opl type-qua/ifier declaration-specifiers opt init-declarator-/ist: init-declarator init-declarator-list , init-declarator r A.8 Vereinbart: n 205 init-declarator: declarator declarator - initializer Deklaratoren werden später besprochen (A.8.5); sie enthalten die Namen, die verein- bart werden. Eine Vereinbarung muß wenigstens einen Deklarator enthalten, oder ihr type-specifier muß ein Struktur-Etikett (tag), ein Union-Etikett oder die Konstanten einer Aufzählung vereinbaren; leere Vereinbarungen sind nicht erlaubt. A.8.1 Speicherklassen Es gibt folgende Angaben zur Speicherklasse: storl!ge-class -specifier: auto register static extern typedef Speicherklassen wurden in  A.4 erklärt. Die Angaben auto und register legen für die vereinbarten Objekte die automati- sche Speicherklasse fest und können nur in Funktionen verwendet werden. Derartige Vereinbarungen sind gleichzeitig Defmitionen und reservieren Speicherplatz. Eine reglster-Defmition ist äquivalent zu einer auto-Defmition, soll aber andeuten, daß häufig auf die vereinbarten Objekte zugegriffen wird. Nur wenige Objekte werden wirklich in Registern abgelegt, und nur bestimmte 1Ypen eignen sich dazu; die Einschränkungen sind implementierungsabhängig. Wenn jedoch für ein Objekt register vereinbart wird, darf der unäre Adreß-Operator & weder explizit noch implizit darauf angewendet wer- den. Die Regel, daß die Adresse eines Objekts nicht berechnet werden darf, fUr das register vereinbart wurde, das aber in Wirklichkeit als auto angelegt wurde, ist neu. Die Angabe static legt für die vereinbarten Objekte die statische Speicherklasse fest und kann innerhalb oder außerhalb von Funktionen verwendet werden. Innerhalb einer Funktion ist die Angabe eine Defmition und sorgt für Speicherplatz; der Effekt außerhalb einer Funktion wird in  A.ll.2 beschrieben. Innerhalb einer Funktion legt eine Vereinbarung mit extern fest, daß Speicherplatz für die vereinbarten Objekte an anderer Stelle definiert wird; der Effekt außerhalb einer Funktion wird in  A.l1.2 beschrieben. Die Angabe typedef reserviert keinen Speicherplatz und wird nur zur Vereinfa- chung der Syntax als Speicherklassenangabe bezeichnet; sie wird in  A.8.9 besprochen. In einer Deklaration darf höchstens ein storage-class-specifier angegeben werden. Ist nichts angegeben, gelten folgende Regeln: für Objekte, die innerhalb einer Funktion vereinbart werden, wird auto angenommen; Funktionen, die innerhalb einer Funktion vereinbart werden, gelten als extern; Objekte und Funktionen, die außerhalb einer Funk- tion vereinbart werden, gehören zur statischen Speicherklasse mit externer Bindung. Sie- he  A.lO-A.ll. 
206 A I 1)rachbeschreibung A.8 Vereinbart' n 207 A.8.2 'JYpangaben Die Typangaben sind type-specifier: void char ahort int long float double aigned unsigned stluct-or-union-specifier enum-specifier typedef-name Höchstens eins der Worte long oder short darf zusammen mit int angegeben werden; die Bedeutung ist so, als ob int nicht angegeben wurde. long darf zusammen mit double an- gegeben werden. Höchstens eins der Worte signed oder unsigned darf zusammen mit int, oder mit einer seiner short oder long Varianten oder auch mit char angegeben wer- den. unsigned oder signed können allein verwendet werden; dazu wird int angenommen. signed ist zweckmäßig, um zu erzwingen, daß char-Objekte vorzeichenbehaftet sind; bei anderen Integer-Typen ist signed zwar redundant, aber zulässig. Davon abgesehen darf höchstens ein type-specifier in einer Vereinbarung angege- ben werden. Fehlt ein type-specifier in einer Vereinbarung, so wird int angenommen. Typen können auch mit Attributen versehen werden, um besondere Eigenschaften der vereinbarten Objekte auszudrücken. type-qua/ifier: const volatile Attribute können mit jeder Typangabe auftreten. Ein const-Objekt darf initialisiert wer- den, darf aber anschließend nicht Ziel einer Zuweisung sein. Es gibt keine implementie- rungsunabhängige Semantik für volatile-Objekte. Die Eigenschaften oonst und volatile sind neu im ANSI-Standard. Mit oonst sollen Objek- te eingefUhrt werden, die im schreibgeschützten Speicher angelegt - weiden -}clfnncri; außerdem können dleOptimieiungsmÖgIichkeiten verbessert werden. volatile dient da- zu, in einer Impleßln..iee.!!!c:..9...p_!C?.f1:!l!&..v.!-h!.n_<!.c:f!!_z. u kÖI!'1. n die-s onst erfo Igen kÖnnte. Greift eine Maschine beispielsweise über spezielle Speicheradressen auf peri- phere Geräte zu, hat sie also memory-mapped input/output, so könnte ein Zeiger auf ein Geräteregister als volatile vereinbart werden, damit der Übersetzer nicht anscheinend redundante Verweise entfernt, die diesen Zeiger verwenden. Abgesehen davon, daß ein Übersetzer Fehlermeldungen ausgeben sollte, wenn explizit versucht wird, ein const- Objekt zu ändern, darf ein Übersetzer die Attribute ignorieren. A.8.3 Strukturen und Unionen Eine Struktur ist ein Objekt, das aus einer Folge von benannten Komponenten mit verschiedenen Typen besteht. Eine Union ist ein Objekt, das zu verschiedenen Zeiten ei- ne von mehreren Alternativen mit verschiedenen Typen enthält. Struktur- und Union- Vereinbarungen haben die gleiche Form. stluct-or-union-specifier: stluct-or-union identifier opl ( struct-dec/aration-list ) stluct-or-union identifier stluct-or-union: atruct union Eine stluct-declaration-list ist eine Folge von Deklarationen für die Komponenten der Struktur oder die Alternativen der Union: stluct-declaration-list: stluct-dec/aration Stluct-dec/aration-list struct-dec/aration Stluct-declaration: specifier-qualifier-list struct-dec/arator-list ; specifier-qualifier-list: type-specifier specifier-qualifier-listopt type-qualifier specifier-qualifier-listopt stluct-declarator-list: stluct-dec/arator stluct-dec/aralor-list , stluct-dec/arator Normalerweise ist ein stluct-dec/arator einfach ein Deklarator für eine Komponente oder Alternative, Eine Strukturkomponente darf auch aus einer explizit angegebenen Anzahl Bits bestehen. Eine solche Komponente wird auch als Bit-Feld (bit-field oder einfach field) bezeichnet. Die Feldlänge wird vom Deklarator für den Bit-Feld-Namen durch einen Doppelpunkt getrennt. stluct-dec/arator: declaralor dec/arator opl : constant-expression Eine Typangabe der Form stluct-or-union identifier ( struct-dec/aration-list ) vereinbart den identifier als Etikett (tag) der Struktur oder Union, die durch die Liste vereinbart wird. Eine nachfolgende Vereinbarung im gleichen oder einem inneren Gül- tigkeitsbereich kann auf den gleichen Typ bezugnehmen, indem nur noch das Etikett oh- ne die Liste angegeben wird: stluct-or-union identifier _Erh e.i,nt eine JJP,I!!1RQ J!lit e e._.t ett aber o e.__eine Liste, danll_e_Iltste.e.in':l- volls!..sr.1}p, \Venn das Etikett nicht vereinbart ist. Objekte mit einem unvollständl- 
208 AC rachbeschreibung A.8 Vereinbarun 209 gen Struktur- oder Union-Typ dürfen nur in Kontexten vorkommen, wo ihre Größe nicht benötigt wird, zum Beispiel in Deklarationen (aber nicht DefInitionen), bei der Kon- struktion eines Zeigers, oder wenn mit typedef ein Typ erzeugt wird, aber sonst nicht. Der Typ wird durch eine nachfolgende Typangabe mit dem Etikett vervollständigt, die ei- ne Liste von Deklarationen enthält. Auch in Typangaben mit einer Liste ist der Struktur- oder Union-Typ, der gerade vereinbart wird, innerhalb der Liste unvollständig, und er wird erst an der geschweiften Klammer } vollständig, die die Typangabe abschließt. Eine Struktur darf keine Komponente mit unvollständigem Typ enthalten. Deshalb kann keine Struktur oder Union vereinbart werden, die sich selbst enthält. Abgesehen davon, daß sie einen Struktur- oder Union-Typ mit einem Namen versehen, erlauben Eti- ketten die Definition von Strukturen, die auf sich selbst verweisen; eine Struktur oder i Union kann einen Zeiger auf ihren eigenen Typ enthalten, denn Zeiger auf unvollständi- I ge Typen dürfen vereinbart werden. Eine ganz besondere Regel gilt für folgende Art von Deklaration strnct-or-unioll idelltifier ; die eine Struktur oder Union vereinbart, aber weder eine Liste von Deklarationen noch Deklaratoren enthält. Auch wenn der identifier ein Struktur- oder Union-Etikett ist, das bereits in einem äußeren Gültigkeitsbereich (A.ll.1) vereinbart wurde, macht diese Vereinbarung den Namen zum Etikett einer neuen, unvollständigen Struktur oder Union im aktuellen Gültigkeitsbereich. Diese spitzfindige Regel ist neu im ANsI-Standard. Sie soll dazu dienen, mit gegenseitig rekursiven Strukturen fertig zu werden, die in einem inneren Gültigkeitsbereich verein- bart werden, deren Etiketten aber vielleicht schon im äußeren Gültigkeitsbereich verein- bart wurden. Die Angabe einer Struktur oder Union mit einer Liste, aber ohne Etikett, erzeugt einen eindeutigen Typ; auf ihn kann direkt nur in der Vereinbarung bezug genommen werden, in der er vorkommt. Die Namen von Struktur- oder Union-Teilen und Etiketten können weder mitein- ander noch mit gewöhnlichen Variablen kollidieren. Ein Komponentenname darf nicht zweimal in der gleichen Struktur erscheinen, oder ein Alternativenname in einer Union, aber der gleiche Name kann in verschiedenen Strukturen oder Unionen benutzt werden. In der Ersten Ausgabe waren Namen in Strukturen oder Unionen nicht auf ihre Eltern bezogen. Diese Zuordnung wurde jedoch in Übersetzern lange vor dem ANsI-Standard üblich. Mit Ausnahme von Bit-Feldern können die Teile von Strukturen oder Unionen be- liebige Typcn besitzen. Eine Bit-Feld-Komponente (die keinen Deklarator haben muß, und die daher namenlos bleiben kann) besitzt den Typ iot, unsigned int oder signed int und wird als ganzzahliges Objekt mit der angegebenen Anzahl Bits interpreticrt; ob ein int-Feld vorzeichenbehaftet ist, ist implementierungsabhängig. Benachbarte Bit-Felder in Strukturen werden in implementierungsabhängigen Speicherstücken in einer im ple- mentierungsabhängigen Richtung angelegt. Paßt ein Bit-Feld, das einem anderen Bit- Feld folgt, nicht in ein teilweise gefülltes Speicherstück, kann es auf zwei Stücke verteilt werden, oder der Rest des angefangenen Stücks kann ausgelassen werden (padding). Ein unbenanntes Bit-Feld mit Breite 0 erzwingt dies; damit beginnt das nächste Feld an der Kante des nächsten Speicherstücks. Der ANsI-Standard macht Bit-Felder sogar noch mehr implementierungsabhängig, als das in der Ersten Ausgabe geschah. Es empfiehlt sich, die Sprachregeln zur Speicherung von Bit-Feldern ohne jede Einschränkung als implementierungsabhängig zu betrachten. Strukturen mit Bit-Feldern können als portable Methode verwendet werden, mit der man versucht, den Speicherbedarf einer Struktur zu verringern (mit dem wahrscheinli- chen Kostenfaktor einer Vergrößerung des Programm texts und der Ausführungszeit, die zum Zugriff auf die Bit-Felder benötigt werden), oder als nicht-portable Methode, um eine Speicheranordnung zu beschreiben, die auf der Bit-Ebene bekannt ist. Im zweiten Fall muß man die Regeln der lokalen Implementierung verstehen. Die Adressen der Komponenten einer Struktur werden in der Reihenfolge ihrer Yt?reÜ! eiigi-ßCr.bgesehen von J:H!_ f'elrn, wird itie-S_trturk?Ill!?nen ai eine Adresse ausgerichtet, die _ v()n ihrem 1YP abhäJ1gt; ein Strllktur. kd,e h uIl""fu: - nannte Löcher enthalten. Wenn ein Zeiger auf eine Struktur in den Typ eines Zeigers aUf ihieersie-kompo nent e umgewandelt wird, verweist das Resultat auf die erste Kom- ponente. Eine Union kann als Struktur angesehen werden, bei der alle Alternativen bei der relativen Adresse 0 beginnen, und die groß genug ist, um jede Alternative einzeln aufzu- nehmen. Höchstens eine Alternative auf einmal kann zu jedem Zeitpunkt in einer Union gespeichert werden. Wenn ein Zeiger auf eine Union in den Typ eines Zeigers auf eine Alternative umgewandelt wird, verweist das Resultat auf diese Alternative. Ein einfaches Beispiel einer Strukturvereinbarung ist struct tnode ( eher tword[20]i int count i struct tnode *lefti struct tnode *righti }i Diese Struktur enthält einen Vektor mit 20 Zeichen, einen int-Wert und zwei Zeiger auf gleichartige Strukturen. Wenn diese Vereinbarung getroffen worden ist, vereinbart struct tnode s, *SPi s als derartige Struktur und sp als Zeiger auf eine derartige Struktur. Mit diesen Verein- barungen verweist der Ausdruck sp->count auf die count-Komponente der Struktur, auf die sp zeigt; s .left bezeichnet den linken Unterbaum-Zeiger der Struktur s; und s.right->tword[O] verweist auf das erste Zeichen der Komponente tword im rechten Unterbaum von s. Allgemein darf auf eine Alternative einer Union nicht zugegriffen werden, wenn der Wert der Union nicht mit Hilfe dieser Alternative zugewiesen wurde. Eine besonde- re Garantie vereinfacht allerdings die Benutzung VOn Unionen: wenn e ine Ynion mehre- ....e Stl'1llcturell_!I ein .c:ID insame [ Je_ilt?!!L l!!!. wE.. di.eJJrtion zUr Zeiteme dieser Strukturen enthält, darf auf die gemeamE AnfIiJ1.ssfolgC?_1l j.der -der enthaltenen StruktÜreomgeg,lffen werden: Folgendes Fragment ist beispielsweise legal:- . - . 
210 / ' -Sprachbeschreibung A.8 Vereinbart n 211 union { struct { int type; } n; struct { int type; int intnode; } ni; struct { int type; ftoat floatnode; } nf; } u; Aufzählungen sind neu seit der (englischen) Ersten Ausgabe dieses Buches, aber sie sind schon seit einigen Jahren Teil der Sprache. if (u.n.type .. FLOAT) ... sin(u.nf.floatnode) ... A.8.5 Deklaratoren Deklaratoren haben folgende Syntax: dec/arator: pointer opt direct-dec/arator direct-dec/arator: identifier ( dec/arator ) direct-dec/arator [ constant-expressionopt ] direct-dec/arator (parameter-type-/ist ) direct-dec/arator ( identifier-/ist op1 ) pointer: * type-qua/ifier-/ist op1 * type-qua/ifier-/ist oP1 pointer type-qua/ifier-/ist: type-qua/ifier type-qua/ifier-/ist type-qlla/ifier Die Struktur von Deklaratoren erinnert an Inhaltsoperationen, Funktionen und Vektor- ausdrücke; sie werden in gleicher Weise zusammengefaßt. u.nf.type = FLOAT; u.nf.floatnode = 3.14; A.8.4 Aufzählungen Aufzählungen (enumerations) sind eindeutige Typen mit Werten aus einer Menge von benannten Konstanten, die Auftäh/ungskonstanten (enumerators) genannt werden. Aufzählungen werden ähnlich formuliert wie Strukturen und Unionen. enum-specifier: enum identifier opt { enumerator-/ist } enum identifier enumeralor-/ist: enumerator enumerator-/ist , enumerator enumerator: identifier identifier - constant-expression Die Namen in ein er enumerator-/ist werden als Konstanten vom Typ int vereinbart, und s i e k önnen ü be r anauI'tret' eii:wo'"l( önst anteii-veifaiig. siild.- Cm;res"keine AufZäliIungs- konstanten mit =, dann beginnen die Werte der entsprechenden Konstanten bei 0 und sie werden in Schritten von 1 in der Reihenfolge der Vereinbarung von links nach rechts fortgezählt. Eine Aufzählungskonstante mit = definiert für den zugehörigen Namen den angegebenen Wert; nachfolgende Namen werden von dort ab mit aufsteigenden Werten vereinbart. Die Namen von Aufzählungskonstanten im gleichen Gültigkeitsbereich müssen alle voneinander und von Variablennamen verschieden sein, aber die Werte müssen nicht ver- schieden sein. LeJ!Qlle des Namens im enum-specifier ist analog zum Struktur-Etikett in einem stTr:tEt-specifier; er bezeichnet eine bestimmte AufzählUng: Die Regeltl fÜr enum- specifiers mit und ohne Etikett und Liste sind die gleichen wie für Strukturen oder Unionen, nur gibt es keine ynvotlständigen Aufzählungen; in einem enum-specifier ohne Liste muß sich das Etikett auf einen Aufzählungstyp im Gültigkeitsbereich mit einer Li- ste beziehen. A.8.6 Die Bedeutung von Deklaratoren Eine Liste von Deklaratoren erscheint nach einer Folge von Angaben zu Typ und Speicherklasse. Jeder Deklarator vereinbart einen eindeutigen zentralen Namen, näm- lich den aus der ersten Alternative der Regel für direct-dec/arator. Die Angaben zur Spei- cherklasse beziehen sich direkt auf diesen Namen, aber der Typ hängt von der Form des Deklarators ab. Ein Deklarator gilt als Zusicherung, daß ein Objekt vom angegebenen Typ resultiert, wenn der Name aus dem Deklarator in einem Ausdruck auftritt, der die gleiche Form hat wie der Deklarator. Berücksichtigt man nur die Typangaben in einem dec/aration-specifier (A.8.2) und einen einzigen Deklarator, so hat eine Vereinbarung die Form "T D", wobei T ein Typ und D ein Deklarator ist. Der Typ, den der Name durch die verschiedenen Formen des Deklarators erlangt, wird mit dieser Notation induktiv beschrieben. Ist in einer Vereinbarung T D der Deklarator D nur ein einfacher Name, so ist der Typ des Namens T. Hat der Deklarator D in einer Vereinbarung T D die Form ( Dl ) dann ist der Typ des Namens in Dl der gleiche wie in D. Die Klammern älldern den Typ nicht, aber sie können den Vorrang bei komplexen Deklaratoren ändern. 
212 A 'prachbeschreibung A.8 Vereinbarun, 213 A.8.6.1 Deklaratoren für Zeiger Hat in einer Vereinbarung T D der Deklarator D die Form * type-qualifier-listopt D1 und hat der Name in einer Vereinbarung T DI den Typ ,Jype-modifier T". dann hat der Name von D den Typ ,Jype-modifier type-quaJifier-list Zeiger auf T". Folgen Attribute auf *, so beziehen sie sich auf den Zeiger selbst und nicht auf das Objekt, auf das der Zeiger zeigt. Als Beispiel betrachten wir die Deklaration int *lIp[]; Hier spielt ap[] die Rolle von DI; durch eine Vereinbarung ..Int ap[]" hätte ap den Typ ..Vektor von Int" (siehe unten), die type-qualifier-list ist leer und der type-modifier ist ..Vektor von". Folglich gibt die tatsächliche Deklaration ap den Typ ..Vektor VOn Zeigern auf Int". Als weitere Beispiele vereinbaren int i, *pi, *const cpi . &i; const int ci = 3, *pci; eine int- Variable I und einen Int-Zeiger pi. Der Wert des konstanten Zeigers epl darf nicht geändert werden. Der Zeiger zeigt immer auf die gleiche Adresse; der Wert. auf den er zeigt, darf jedoch geändert werden. Die tnt- Variable ci ist konstant und darf nicht geändert werden (sie darf allerdings so wie hier initialisiert werden). pet hat den Typ ..Zeiger auf eonst int" und pei selbst kann geändert werden und auf eine andere Adresse zeigen, aber der Wert, auf den pd zeigt, kann nicht durch eine Zuweisung über pei geän- dert werden. stlltic int x3d[3] [5] [7]; einen statischen, dreidimensionalen Vektor von int-Werten mit Rang 3>6><7. Im Detail betrachtet, ist x3d ein Vektor mit drei Elementen; jedes Element ist ein Vektor aus fünf Vektoren; jeder der letzteren Vektoren hat sieben iot-Elemente. Jeder der Ausdrücke x3d, x3d[i], x3d[i] UJ, x3d[i] UJ [k] kann vemünftigerweise in einem Ausdruck vorkom- men. Die ersten drei haben den Typ ..Vektor", der letzte hat den Typ int. Exakter ist x3d[l] UJ ein Vektor mit 7 Int-Elementen und x3d[l] ist ein Vektor mit 5 Vektoren von je 7 int-Elementen. Indizierte Vektorverweise sind so definiert, daß EI [E2] identisch mit *(EI + E2) ist. Trotz ihres asymmetrischen Aussehens ist die Indexoperation kommutativ. Wegen der Typumwandlungsregeln für + und Vektoren (UA.6.6, A.7.1. A.7.7) bezieht sich EI[E2] auf das E2-te Element in EI; dabei muß EI ein Vektor und E2 ein Integer-Wert sein. In dem Beispiel ist x3d[l] UJ [k] äquivalent zu *(x3d[l] UJ + k). Der erste Teilaus- druck x3d[i] UJ wird nach  A.7.1 in den Typ ..Zeiger auf Vektor von int" umgewandelt; nach A.7.7 gehört zur Addition eine Multiplikation mit der Größe eines Int-Werts. Aus den Regeln folgt, daß Vektoren zeilenweise gespeichert werden (der letzte Index ändert sich am schnellsten) und daß die erste Dimensionierung in der Deklaration hilft, den Speicherplatzbedarf eines Vektors zu bestimmen, daß sie aber sonst an der Indexberech- nung nicht beteiligt ist. A.8.6.3 Deklaratoren für Funktionen Hat in einer Funktionsvereinbarung neuen Stils T D der Deklarator D folgende Form A.8.6.2 Deklaratoren rDr Vektoren Hat in einer Vereinbarung T D der Deklarator D folgende Form D1 [constant-expressionopt] und hat der Name in einer Vereinbarung T DI den Typ .Jype-modifier 1"'. dann hat der Name von D den Typ ,Jype-modifier Vektor von 1"'. Ist eine constant-expression vorhan- den, muß sie einen Integer-Typ und einen positiven Wert besitzen. Fehlt die constant- expression, die die Vektorlänge angibt, so hat der Vektor einen unvollständigen Typ. Ein Vektor darf aus einem arithmetischen Typ, aus einem Zeiger. einer Struktur oder Union gebildet werden, sowie aus einem anderen Vektor (um einen mehrdimensio- nalen Vektor zu erzeugen). Jeder Typ. aus dem ein Vektor gebildet wird, muß vollstän- dig sein; er darf weder Vektor noch Struktur mit unvollständigem Typ sein. Daraus folgt, daß bei einem mehrdimensionalen Vektor nur die erste Dimensionierung fehlen darf. Der Typ eines Objkt mit unvollständigem Vektortyp wird durch eine andere, vollständi- ge Vereinbarung für das Objekt vervollständigt (A.1O.2). oder durch lnitialisierung (A.8.7). Zum Beispiel vereinbart flollt fll[17], *lIfp[17]; einen Vektor mit noat-Elementen und einen Vektor mit Zeigern auf noat-Werte. Weiter vereinbart D 1 (parameter-type-list) und hat der Name in einer Vereinbarung T DI den Typ ,,type-modifier 1"', dann hat der Name Von D den Typ ,,type-modifier Funktion mit Argumenten parameter-type-list und Resultattyp 1"'. Die Parameter haben folgende Syntax: parameter-type-list: parameter-/ist parameter-list , parameter-list: parameter-declaration parameter-list , parameter-declaration parameter-declaration: declaralion-specifiers declarator declaration-specifiers abstract-declarator opl Im neuen Stil der Vereinbarung legt die Parameter liste die Typen der Parameter fest. Als Sonderfall hat der Deklarator für eine Funktion im neuen Stil ohne Parameter als parameter-type-list nur das Wort void. Endet die parameter-type-list mit .., ...... dann darf die Funktion mehr Argumente akzeptieren als Parameter explizit beschrieben sind; siehe  A.7.3.2. ' ......-- . 
214 :-Sprachbeschreibung A.8 Vereinbaru ;} 215 Die Typen von Parametern, die Vektoren oder Funktionen sind, werden nach den Regeln für Parameterumwandlung in Zeiger abgeändert, siehe  A.lO.1. Als einzige Speicherklassenangabe ist in einer Parameterdeklaration register erlaubt, und diese An- gabe wird ignoriert, wenn der Funktionsdeklarator nicht eine FunktionsdefJnition einlei- tet. Wenn die Deklw:atoren bei Parameterdeklarationen Namen enthalten und wenn der Funktionsdeklarator nicht eine Funktionsdeflnition einleitet, endet der Gültigkeitsbereich der Parameternamen sofort. Abstrakte Deklaratoren, in denen keine Namen verwendet werden, werden in  A8.8 besprochen. Hat in einer Funktionsvereinbarung alten Stils T D der Deklarator D folgende Form A.S.7 loitialisieruog Wenn ein Objekt definiert wird, kann sein init-declarator einen Anfangswert für den Namen festlegen, der gerade vereinbart wird. Vor der lnitialisierung steht =, und sie ist entweder ein Ausdruck oder eine Liste von lnitialisierungen, die mit geschweiften Klammern umgeben ist. Die Liste darf mit einem Komma enden, als Hilfe zur gefälligen Formatierung. initializer: assignment-expression { initializer-list } { initializer-list , initiaJizer-list: initializer initializer-list , initializer Alle Ausdrücke in der lnitialisierung eines statischen Objekts oder Vektors müssen konstante Ausdrücke sein, wie in A7.19 beschrieben. Die Ausdrücke in der lnitialisie- rung eines auto- oder register-Objekts oder eines Vektors müssen ebenfalls konstante Ausdrücke sein, wenn die lnitialisierung eine mit geschweiften Klammern umgebene Li- ste ist. Ist die lnitialisierung eines automatischen O'?jetjp,ip,P!1!4r!lcJ<, m üB er nicht kODStant sem,-sonrlern 1D1.J3 nurefiien Typ besitzen, der an das Objekt zuge- . wiesen werdenkäiUi: 0'__' Die Erste Ausgabe erlaubte keine lnitialisierung von automatischen Strukturen, Unionen oder Vektoren. Der ANSI-Standard erlaubt es, aber nur mit konstanten Konstruktionen, es sei denn, die Initialisierung kann als einfacher Ausdruck angegeben werden. . Ein statisches Objekt, das nicht explizit initialisiert ist,. wird so initialisiert, ,.als, 01> die Konstante 0 an. das Objekt (oder seine Teile) zugewiesen d._J)er AI1f3!1wert ,ei- nes automatischen Objekts, das nicht explizit initialisiert wurde, ist ,llI1defini.ert. Ein Zeiger oder ein Objekt mit arithmetischem Typ wird durch einen einzigen Ausdruck initialisiert, der auch in geschweiften Klammern stehen kann. Der Ausdruck wird an das Objekt zugewiesen. Die lnitialisierung für eine Struktur ist entweder ein Ausdruck mit dem gleichen Typ oder eine mit geschweiften Klammern umgebene Liste von lnitialisierungen für die Komponenten der Reihe nach. Unbenannte Bit-Feld-Komponenten werden ignoriert und nicht initialisiert. Enthält die Liste weniger lnitialisierungen, als die Struktur Kom- ponenten hat, werden die restlichen Komponenten mit 0 initialisiert. Mehr lnitialisierun- gen als Komponenten dürfen nicht angegeben werden. Die Initialisierung für einen Vektor ist eine mit geschweiften Klammern umgebene Liste von lnitialisierungen für die Elemente. Ist die Größe des Vektors nicht bekannt, legt die Anzahl der lnitialisierungen die Größe des Vektors fest, und der Typ des Vektors wird vervollständigt. Liegt die Größe des Vektors fest, dürfen nicht mehr lnitialisierun- gen als Elemente angegeben sein; sind es weniger, werden die restlichen Elemente mit 0 initialisiert. Als Sonderfall darf ein Zeichenvektor mit einer konstanten Zeichenkette initiali- siert werden; die Zeichen der Zeichenkette initialisieren die Elemente des Vektors der Dl (identifier-listopt) und hat der Name in einer Vereinbarung T Dl den Typ ,,type-modifier TU, dann hat der Name von D den Typ ,,type-modifier Funktion mit unbekannten Argumenten und Resul- tattyp TU. Falls Parameter angegeben sind, haben sie folgende Form: identifier-list: identifier identifier-list . identifier Im Deklarator alten Stils darf die idelltifier-list nur angegeben werden, wenn der Deklara- tor eine FunktionsdefInition einleitet (AlO.1). Die Vereinbarung liefert keine Informa- tion über die Typen der Parameter. Beispielsweise vereinbart int f(). *fpi(). (*pfi)()i eine Funktion f mit Resultattyp ißt, eine Funktion fpi, die einen Zeiger auf int liefert, und einen Zeiger pli auf eine Funktion, die iot als Resultat liefert. Die Vereinbarungen verwenden den alten Stil, in keiner sind die Parametertypen angegeben. In der Deklaration neuen Stils int strepy(ehar *dest. eonst ehar *souree). rand(void)i ist strcpy eine Funktion mit int-Resultat und zwei Argumenten, das erste Argument ist ein Zeiger auf char, das zweite ein Zeiger auf konstante Zeichen. Die Parameternamen sind effektiv Kommentare. Die zweite Funktion raßd akzeptiert keine Argumente und liefert ißt. Funktionsdeklaratoren mit Parameterprototypen sind bei weitem die wichtigste Sprachänderung, die der ANsI-Standard einführt. Sie haben einen Vorteil gegenüber den Deklaratoren ..alten Stils" der Ersten Ausgabe, denn sie erlauben Fehlererkennung und Typumwandlung von Argumenten bei Funktionsaufrufen, aber das hat seinen Preis: Auf- ruhr und Verwirrung bei ihrer Einführung und die Notwendigkeit, beide Formen zu un- terstützen. Aus Kompatibilitätsgründen wurden gewisse syntaktische Unschönheiten in Kauf genommen, nämlich void als explizite Markierung der Funktionen neuen Stils ohne Parameter. Die Schreibweise mit ..,m" für Funktionen mit variabler Argumentliste ist ebenfalls neu. Zusammen mit den Makros aus der Standard-Definitionsdatei <stdarg.h> formalisiert sie einen Mechanismus, der in der Ersten Ausgabe offiziell verboten war, der aber inoffi- ziell vergeben wurde. Die Schreibweisen wurden von C++ übernommen. 
216 A ;prachbeschreibung A.8 Vereinbarunge 217 Reihe nach. Ebenso darf eine erweiterte Zeichenkette (A.2.6) einen Vektor vom Typ wchar_t initialisieren. Ist die Größe des Vektors nicht bekannt, bestimmt die Anzahl der Zeichen in der Zeichenkette, Unter Berücksichtigung des abschließenden NUL-Zeichens, die Größe des Vektors; liegt die Größe des Vektors fest, dürfen ohne das abschließende NUL-Zeichen höchstens so viele Zeichen in der konstanten Zeichenkette sein, wie der Vektor Elemente hat. Die lnitialisierung für eine Union ist entweder ein einfacher Ausdruck mit dem gleichen Typ, oder eine mit geschweiften Klammern umgebene lnitialisierung für die er- ste Alternative der Union. Die Erste Ausgabe erlaubte keine lnitialisierung von Unionen. Die Erste-Alternative- Regel ist unbeholfen, aber sie kann nur schwer ohne neue Syntax verallgemeinert wer- den. Mit dieser ANSI-Regel kann man Unionen wenigstens auf primitive Art initialisie- ren; außerdem macht sie die Semantik von statischen Unionen klar, die nicht explizit in- itialisiert werden. Ein Aggregat ist eine Struktur oder ein Ve ktor. Wenn ein Aggregat Bestandteile hat, die ebenfalls einen Aggregat-Typ besitzen, gelte n die lnitialisierungsregeln rekursiv. In de!!i!"1I:8,_ki'>_E.Il...Ae£e.!!\e.J<lI,I!1IJle!IU2!ge..!lIi£!,}D_ _Ile.l.r-. den: Ist ein Bestandteil eines Aggregats selbst ein Aggregat und beginnt seine lnitialisie- rung mit einer linken geschweiften Klammer, dann initialisiert die anschließende, mit Komma getrennte Liste von Initialisierungen die Bestandteile des inneren Aggregats; da- bei dürfen nicht mehr lnitialisierungen als Bestandteile vorkommen. Beginnt jedoch die lnitialisierung des inneren Aggregats nicht mit einer linken geschweiften Klammer, dann werden nur genügend Initialisierungen für die Bestandteile des inneren Aggregats aus der Liste entnommen; etwa verbleibende lnitialisierungen sind für den nächsten Bestand- teil des äußeren Aggregats übrig. Zum Beispiel defmiert und initialisiert int xe] = ( 1, 3, 5 }i X als eindimensionalen Vektor mit drei Elementen, da keine Größe angegeben war und da drei lnitialisierungen vorhanden sind. float y[4] [3] = ( ( 1, 3, 5 ), ( 2, 4, 6 ), ( 3, 5, 7 ), float y[4] [3] = ( ( 1 ), ( 2 ), ( 3 ), ( 4 ) }i ist eine vollständig geklammerte lnitialisierung: 1, 3 und 5 initialisieren die erste Zeile des Vektors y[O], nämlich die Elemente y[O][O], y[O][l] und y[O][2]. Analog initialisie- ren die nächsten beiden Zeilen y[1] und y[2]. Die lnitialisierung hört vorzeitig auf, des- halb werden die Elemente von y[3] mit 0 initialisiert. Exakt der gleiche Effekt hätte mit float y[4] [3] = ( 1, 3, 5, 2, 4, 6, 3, 5, 7 }i initialisiert die erste Spalte von y (als Matrix interpretiert) und setzt den Rest auf O. Schließlich zeigt char msg[] = "Syntax error on l ine Xs\n"i einen Zeichenvektor, dessen Elemente mit einer konstanten Zeichenkette initialisiert werden; zur Größe gehört das abschließende NUL-Zeichen dazu. A.8.8 'JYpnamen Manchmal (bei einer expliziten Typumwandlung mit Hilfe eines cast-Operators, bei der Deklaration von Parametertypen in Funktionsdeklaratoren und als Argument von sizeol) wird de r Name eines Datentyps benötigt. Dies kann durch einen type-name ge- schehen, syntaktisch.<lurcdie Ve!ein_3!-'!£9.je.!s_'!li.eI1!-,gewünschten Typ, in der der Namees Objekts fehlt. type-name: specifier-qua/ifier-/ist abstract -declarator opt abstract-dec/arator: pointer pointer opt direct-abstract-declarator direct-abstract-dec/arator: ( abstract-declarator ) direct-abstract-declarator opt [ constant-expressionopt ] direct-abstract-declarator opt (parameter-type-/istopt ) Man kann eindeutig den Punkt im abstract-declarator feststellen, wo ein Name stehen würde, wenn die Konstruktion ein Deklarator in einer Vereinbarung wäre. Der type- name bezeichnet dann den Typ, den der hypothetische Name hätte. Die Angaben int int * i nt * [3] int (*) [] int *() int (* [] Hvoid) bezeichnen der Reihe nach die Typen "Integer", "Zeiger auf Integer", "Vektor mit 3 Zei- gern auf Integer", "Zeiger auf Vektor mit unbestimmter Anzahl von Integern", "Funkti- on mit unbekannten Parametern, die Zeiger auf Integer liefert" und "Vektor mit unbe- stimmter Größe von Zeigern auf Funktionen ohne Parameter, die ein Integer-Resultat liefern" . }i A.8.9 typedef Vereinbarungen mit der Speicherklasse typedef vereinbaren keine Objekte, son- dern Namen für Typen. Ein solcher Name wird als typedef-name bezeichnet. typedef-name: identifier erzielt werden können. Die lnitialisierung für y beginnt mit einer linken geschweiften Klammer, nicht aber die für y[O]; deshalb werden drei Elemente der Liste benutzt. Ana- log werden die nächsten drei der Reihe nach für y[1] und dann für y[2] verwendet. 
-Sprachbeschreibung A.9 Anweisungen 219 218 Eine typedef-Vereinbarung gibt jedem Namen in ihren Deklaratoren wie üblich einen Typ (siehe  A.8.6). Anschließend ist jeder derartige typedef-name syntaktisch äquivalent zu einem reservierten Wort als Typangabe für den zugehörigen Typ. Zum Beispiel sind nach typedef lang Blockno, *Blockptr; typedef struct ( double r, theta; ) Camplex; die Konstruktionen Blockno b; extern Blockptr bp; Camplex z, *zp; legale Vereinbarungen. Der Typ von b ist lon& die Variable bp ist ein "Zeiger auf long" und z hat die angegebene Struktur; zp ist ein Zeiger auf eine solche Struktur. typedef führt kein c:_e.!!e n ein, so ndern nur Synonyme für 1'ypeIl' die auch anders angegeben werden könnten. In dem Beispiel hat b den gleichen Typ wie jedes an- dere long-Objekt. Ein typedef-name darf in einem inneren Gültigkeitsbereich neu vereinbart werden, aber die Liste der Typangaben darf nicht leer sein. Zum Beispiel vereinbart extern Blockno; Blockno nicht neu, wohl aber extern int Blockno; A.9.1 Marken an Anweisungen Vor Anweisungen können Marken stehen: labeled-statement: identifier : statement case constant-expression : statement default : statement Eine Marke, die aus einem Namen besteht, vereinbart den NaJI1_:"". gine_derart - K ! ureTvonjf-fw.rdc::"Qr .9eis" des Nns. .ist die aktuelle Funktion. Da Marken ihren eigenen Namensraum besitzen, können sie nicht mit anderen Nam-eii kollidIeren-iiiiaSie 1connen' ficht erneut vereinbart werden." Siehe . A.1U. case- und default-Marken werden zusammen mit der switch-Anweisung (A.9.4) verwendet. Der konstante Ausdruck bei case muß einen Integer-Typ besitzen. Von sich aus ändern Marken den Ablauf eines Programms nicht. A.9.2 Ausdruck als Anweisung Die Berechnung eines Ausdrucks ist die am häufigsten verwendete Anweisung; sie hat folgende Form: expression-statement: expressionopt ; Solche Anweisungen sind normalerweise Zuweisungen oder Funktionsaufrufe. Alle Ne- benwirkungen des Ausdrucks werden abgeschlossen, bevor die nächste Anweisung aus- geführt wird. Fehlt der Ausdruck, wird die Konstruktion als leere Anweisung (null statement) bezeichnet; sie wird oft für eine Schleife oder zum Anbringen einer Marke be- nutzt. A.8.10 Äquivalenz von Typen Zwei type-specifier-Listen sind äquivalent, wenn sie die gleiche Menge von Typan- gaben enthalten, wobei berücksichtigt wird, daß manche Angaben von anderen impliziert werden (zum Beispiel impliziert long allein schon long Int). St !!l.L,!l!1i_o. !len ..!!1!.4 p.u7hI Ullgenmi. vereß etteE:_sind ve.rsc!ll edn und ohne Etikett defmiert eiDe Union, Struktur oder Aufzahlring einen eindeutigen Typ. Zwei Typen sind gleich, wenn ihre abstrakten Deklaratoren (A.8.8) bis auf die Äquivalenz der type-specifier-Listen gleich sind; dabei werden typedef-Typen expandiert und die Namen von Funktionsparametern entfernt. Vektorgrößen und die Parameterty- pen von Funktionen sind signifikant. A.9 Anweisungen Falls nicht anders beschrieben, werden Anweisungen sequentiell nacheinander aus- geführt. Anweisungen werden für ihre Effekte ausgeführt, sie haben keine Werte. Es gibt verschiedene Gruppen: statement: labeled-statement expression-statement compound-statement selection-statement iteration-statement jump-statement A.9.3 Block Damit mehrere Anweisungen verwendet werden können, wo eine einzelne Anwei- sung erwartet wird, gibt es die zusammengesetzte Anweisung, die auch als Block bezeich- net wird. Der Rumpf einer Funktionsdefmition ist ein Block. compound-statemen { declaration-listopt statement-list opl } declaration-list: declaration declaration-list declaration statement-list: statement statement-list statement Wenn ein Name in der declaration-list bereits außerhalb des Blocks vereinbart war, wird die äußere Vereinbarung innerhalb des Blocks ausgesetzt (siehe A.11.1) und nach dem Block wiederhergestellt. Ein Name darf einmal im gleichen Block vereinbart werden. Diese Regeln beziehen sich auf Namen im gleichen Namensraum (A.ll); Namen in verschiedenen Namensräumen werden als verschieden behandelt. 
:-Sprachbeschreibung A.9 Anweisung, 221 220 A.9,4 Auswablaoweisungen Auswahlanweisungen wählen einen von mehreren Programmabläufen. se/ection-statement: if ( expressioll ) statement if ( expression) statement else statement switch ( expression) statement Bei beiden Formen der i r-AIl:w _eiung wird der Ausdruck, der einell.a.ritl1metischen Typ oder Zeigertyp besitzen mß, mit allen Nebenwirkungen berechnet. Ist das Resultat verschieden von 0, wird die erste abhängige Anweisung ausgeführt. In der zweiten Form wird die zweite abhängige Anweisung ausgeführt, wenn der Ausdruck 0 ist. Die Mc< .bI- deutigl<ei bI ir<l da<llll:ch .l:!ll<:!!!e<le!lL<l. elsejws d(:lJl_zul.t:tliuIgetrete- neEe!elosen, ir af der gleichen Verscha.chtelullge.(:ne VOn Blö<:!,l ge.llet wird. Die switcb-Anweisung setzt den Programm ablauf mit einer von mehreren Anwei- sungen in Abhängigkeit vom Wert eines Ausdrucks ab, der einen Integer-Typ besitzen muß. Typischerweise ist die von switcb kontrollierte Anweisung ein Block. Jede Anwei- sung innerhalb der abhängigen Anweisung kann mit einer oder mehreren case-Marken (A.9.1) markiert sein. Für den kontrollierenden Ausdruck findet Integer-Erweiterung statt (A.6.1), und die case-Konstanten werden in den erweiterten Typ umgewandelt. Nach der Typumwandlung dürfen keine zwei case-Konstanten, die zum gleichen switcb gehören, den gleichen Wert besitzen. Zu einem switcb darf auch höchstens eine derault-Marke gehören. switcb-Anweisungen dürfen verschachtelt werden; eine case- oder derault-Marke gehört zum kleinsten switcb, der sie enthält. Zur Ausführung der switcb-Anweisung wird der Ausdruck mit allen Nebenwirkun- gen bewertet und mit allen case-Konstanten verglichen. Gibt es eine case-Konstante, die denselben Wert hat wie der Ausdruck, so wird der Programm ablauf mit der Anweisung bei der case-Marke fortgesetzt. Gibt es keine passende case-Konstante, geht der Pro- grammablauf mit der Anweisung bei derault weiter, falls eine derault-Marke vorhanden ist. Gibt es weder eine passende case-Konstante noch eine default-Marke, so wird keine der von switcb abhängigen Anweisungen ausgeführt. In der Ersten Ausgabe mußten der Kontrollausdruck bei switch, wie auch die ca se- Konstanten, Werte mit dem Typ int besitzen. Bei den wbile- und do-Anweisungen wird die abhängige Anweisung so lange wie- derholt ausgeführt, wie der Wert des Ausdrucks verschieden von 0 ist; der Ausdruck muß einen arithmetischen Typ oder Zeigertyp besitzen. Bei wbile erfolgt der Test, mit allen Nebenwirkungen des Ausdrucks, vor jeder Ausführung der abhängigen Anweisung; bei do erfolgt der Test nach jeder Wiederholung. Bei der ror-Anweisung wird der erste Ausdruck einmal bewertet und dient deshalb zur lnitialisierung der Schleife. Der Typ des Ausdrucks unterliegt keiner Einschränkung. Der zweite Ausdruck muß einen arithmetischen Typ oder Zeigertyp besitzen; er wird vor jeder Wiederholung berechnet, und wenn er den Wert 0 besitzt, wird ror beendet. Der dritte Ausdruck wird nach jeder Wiederholung bewertet und gibt deshalb eine Reinitiali- sierung für die Schleife an. Der Typ des Ausdrucks unterliegt keiner Einschränkung. Nebenwirkungen von jedem Ausdruck werden unmittelbar nach seiner Berechnung abge- schlossen. Wenn die abhängige Anweisung continue nicht enthält, ist eine Anweisung tor ( expression 1 ; expression 2 ; expression 3 ) statement äquivalent zu expression 1 ; wh il e ( expression 2 ) ( statement expression 3 ; Die lnitialisierung von automatischen Objekten erfolgt jedesmal, wenn der Block sequentiell von außen her erreichtwd,undgeschICht inder Reihenfolge der Deklara- ren. 'WiId ein Sprung iD einen BIock ajlSgeführt, fmden diese lnitialisieningen nicht statt. Ffusiaiic-Objekte erfolgen lnitialisierungen nur einmal, bevor die A\lSfQw.!,t!l& des Pro- gramms beginnt. ) Jeder der drei Ausdrücke darf fehlen. Fehlt der mittlere Ausdruck, ist der impli- zierte Test äquivalent zum Test einer von 0 verschiedenen Konstanten. A.9.6 Sprunganweisungen Durch Sprunganweisungen wechselt der Programmablauf unbedingt. jump-statement: goto identifier ; continue ; break ; return expressionopt ; Bei der goto-Anweisung muß der Name eine Marke (A.9.1) in der aktuellen Funktion sein. Der Programm ablauf wird bei der markierten Anweisung fortgesetzt. Eine contioue-Anweisung darf nur innerhalb einer Wiederholungsanweisung auf- treten. Durch diese Anweisung wird der Programmablauf am Wiederholungspunkt der kleinsten umgebenden derartigen Anweisung fortgesetzt. Exakter formuliert, bei jeder der folgenden Anweisungen while (...) ( do ( tor (...) ( A.9.5 Wiederbolungsanweisungen Wiederholungsanweisungen formulieren Schleifen. iteration-statemellt: while ( expression) statemellt do statemellt while ( expression) for ( expressioll op1 ; expressionopt; expressionopt) statement contin: ; contin: contin: ; } } while (,..); } ist eine cooUnue-Anweisung, die nicht noch in einer kleineren Wiederholungsanweisung enthalten ist, äquivalent zu goto contin. Eine break-Anweisung darf nur in einer Wiederholungsanweisung oder in einer switcb-Anweisung auftreten und beendet die Ausführung der kleinsten umgebenden der- 
222 A 5prachbeschreibung artigen Anweisung; der Programm ablauf geht mit der Anweisung weiter, die der beende- ten Anweisung folgt. Eine Funktion kehrt mit der return-Anweisung zu ihrem Aufrufer zurück. Wenn nach return ein Ausdruck folgt, wird dieser Wert dem Aufrufer der Funktion geliefert. Der Ausdruck wird wie bei einer Zuweisung in den Resultattyp der Funktion umgewan- delt, in der er auftritt. Erreicht der Programmablauf das Ende einer Funktion, ist das äquivalent zur Aus- führung von returo ohne einen Ausdruck. In jedem Fall ist dann der Resultatwert unde- finiert. A.I0 Externe Vereinbarungen Die Eingabe für den C-Übersetzer wird als Übersetzungseinheit bezeichnet; sie be- steht aus einer Folge von externen Vereinbarungen, die entweder Vereinbarungen oder Funktionsdefmitionen sind. trans/ation-unit: extema/-dec/aration trans/ation-unit extema/-declaration extema/-dec/aration: function-definition declaration Der Gültigkeitsbereich von externen Vereinbarungen reicht bis zum Ende der Übersetzungseinheit, in der sie auftreten, ebenso wie der Effekt von Vereinbarungen in einem Block bis zum Ende des Blocks reicht. Die Syntax von externen Vereinbarungen ist dieselbe wie bei allen Vereinbarungen, abgesehen davon, daß nur auf dieser Ebene der Code von Funktionen angegeben werden kann. A.I0.l Funktionsdelinltionen Funktionsdefmitionen haben folgende Form: function-definüion: dec/aration-specifiersopt dec/arator dec/aration-/istopt compound-statement Bei den declaration-specifiers sind als Speicherklassen nur extern oder static erlaubt; der Unterschied wird in A.l1.2 erklärt. Eine Funktion kann als Resultat einen arithmetischen Typ, eine Struktur, eine Union, einen Zeiger oder vold liefern, nicht aber eine Funktion oder einen Vektor. Der Deklarator in einer Funktionsvereinbarung muß explizit ausdrücken, daß der vereinbarte Name einen Funktionstyp besitzt, das heißt, er muß eine der folgenden Formen enthalten (siehe A.8.6.3) direct-declarator (parameter-type-/ist ) direct-declarator ( identifier-/istopt ) dabei ist der direct-declarator ein Name oder ein Name in Klammern. Er darf insbeson- dere nicht mit Hilfe von typedef seinen Funktionsstatus erlangen. In der ersten Form ist die Definition eine Funktion im neuen Stil, und ihre Para- meter werden zusammen mit ihren Typen in der parameter-type-/ist deklariert; nach dem A.l0 Externe Ve' 'barungen 223 i I i I i I I I I I I \ I I I ! I I i I I I I I I I Funktionsdeklarator darf keine declaration-list folgen. Falls die parameter-type-/ist nicht nur aus void besteht und damit zeigt, daß die Funktion keine Parameter akzeptiert, muß jeder Deklarator in der parameter-type-/ist einen Namen enthalten. Endet die parameter- type-list mit '" ...", dann darf die Funktion mit mehr Argumenten als Parametern aufgeru- fen werden; der Makro va_arg und die in der Definitionsdatei < stdarg.h > vereinbarte und im Anhang B beschriebene Technik muß verwendet werden, um auf die zusätzlichen Argumente zuzugreifen. Funktionen mit einer variablen Anzahl von Argumenten müs- sen wenigstens einen benannten Parameter besitzen. In der zweiten Form erfolgt die Definition im alten Stil; die identifier-/ist benennt die Parameter und die declaration-list gibt ihnen Typen. Wenn für einen Parameter keine Deklaration angegeben ist, erhält er den Typ Int. Die declaration-list darf nur Parameter vereinbaren, die in der identifier-/ist angegeben sind, lnitialisierung ist nicht erlaubt, und als einzige Speicherklasse kann register angegeben werden. Bei beiden Arten von Funktionsdefmitionen wird so verfahren, als ob die Parame- ter unmittelbar am Anfang des Blocks deklariert wurden, der den Rumpf der Funktion bildet; folglich können die gleichen Namen in diesem Block nicht nochmals vereinbart werden (sie können natürlich, wie andere Namen, in inneren Blöcken neu vereinbart werden). Wird ein Parameter mit dem Typ "Vektor VOn 'Typ" deklariert, so wird die De- klaration in "Zeiger auf 'Typ" abgeändert; analog, wird ein Parameter mit dem Typ "Funktion mit Resultat 'Typ" deklariert, so wird die Deklaration in "Zeiger auf Funktion mit Resultat 'Typ" abgeändert. Beim Aufruf einer Funktion werden die Argumente faUs nötig umgewandelt und an die Parameter zugewiesen; siehe  A.7.3.2. Der neue Stil für Funktionsdefinitionen ist neu im ANst-Standard. Es gibt auch eine klei- ne Änderung in den Details zur Erweiterung: in der Ersten Ausgabe war festgelegt, daß die Deklarationen von noat-Parametern in double abgeändert wurden. Der Unterschied macht sich bemerkbar, wenn in einer Funktion ein Zeiger auf einen Parameter erzeugt wird. Ein komplettes Beispiel einer Funktionsdefinition im neuen Stil ist int max(int 8, int b, int c) { int m; m = (8 > b) 1 8 : b; return (m > c) 1 m : c; > int ist hier der declaration-specifier, max(int a, int b, int c) ist der Funktionsdeklarator und { ... } ist der Block mit dem Code der Funktion. Die entsprechende Definition im alten Stil wäre int maX(8, b, c) int 8, b, c; { /* ... */ } Jetzt ist int max(a, b, c) der Deklarator und int a, b, c; ist die declaration-/ist für die Para- meter. 
224 I ' -Sprachbeschreibung All Gültigkeitf 'eich und Bindung 225 A.I0.2 Externe Vereinbarungen Externe Vereinbarungen legen die Charakteristika von Objekten, Funktionen und anderen Namen fest. Der Begriff "extern" bezieht sich auf ihre Position außerhalb von Funktionen und ist nicht direkt mit dem reservierten Wort extern verbunden; die Spei- cherklasse für ein extern vereinbartes Objekt kann leer bleiben, oder sie kann als extern oder static angegeben werden. In der gleichen Übersetzungseinheit dürfen mehrere externe Vereinbarungen für den gleichen Namen existieren, wenn sie nach 'IyP und Bindung übereinsÜ!1: en und wenn es hiX:hstenseineDefJ!Ü.tionJi!r <!e!l_!JIll!gibt. Ob zwei Vereinbarungen für ein Objekt oder eine Funktion im 'IyP übereinstim- men, wird nach den Regeln in  A8.10 entschieden. Darüber hinaus, wenn sich die Ver- einbarungen unterscheiden, weil ein 'IyP ein_UIlvollständiger Struktur-, Union- oder Auf- zählungstyp ist (A8.3), und der andere ist der entsprechende vollständige 'IyP mit dem gleichen Etikett, werden die Typen als übereinstimmend angesehen. Außerdem, wenn ein 'IyP ein unvollstäDdiger Vektortyp ist '(Ä.8.6.2j:und der andere ist ein vollständiger Vektortyp, gelten die 'lyPen, wenn sie sonst gleich sind, als übereinstimmend. Ist schließ- lich ein 'IyP eine Funktion alten Stils, und der andere eine ansonsten identische Funktion neuen Stils mit Parameterdeklarationen, gelten die Typen als übereinstimmend. Enthält die erste externe Vereinbarung für eine Funktion oder ein Objekt die An- gabe static, hat der Name inteme Bindung (intema/ /inkage); andernfalls hat er externe Bindung (extema/ /inkage). Bindung wird in A.ll.2 besprochen. Eine externe Vereinbarung für ein Objekt ist eine Definition, wenn sie eine lnitiali- sierung enthält. Eine externe Vereinbarung für ein Objekt ohne eine lnitialisierung und ohne die Angabe extern gilt als vorläufige Definition (tentative definition). Wenn eine De- finition für ein Objekt in einer Übersetzungseinheit erscheint, werden vorläufige Defini- tionen nur als redundante Deklarationen behandelt. Wenn in der Übersetzungseinheit keine Definition für das Objekt erscheint, werden alle vorläufigen Definitionen zu einer einzigen Definition mit der lnitialisierung O. Jedes Objekt muß exakt eine Definition besitzen. Bei Objekten mit interner Bin- dung gilt diese Regel separat für jede Übersetzungseinheit, denn Objekte mit interner Bindung existieren eindeutig für jede Übersetzungseinheit. Bei Objekten mit externer Bindung gilt das für das ganze Programm. Die Eine-Deflnition-Regel wird zwar in der Ersten Ausgabe etwas anders formuliert, aber sie ist effektiv identisch mit der hier angegebenen Regel. Manche Implementierun- gen sind weniger streng und erweitern die Idee der vorläufigen Definition. In der alter- nativen Formulierung, die bei UNIX Systemen üblich ist und vom Standard als gebräuchli- che Erweiterung anerkannt wird, werden alle vorläufigen Definitionen für ein Objekt mit externer Bindung, in allen Übersetzungseinheit zusammen, gemeinsam und nicht für jede Übersetzungseinheit separat betrachtet. Existiert eine Definition irgendwo im Pro- gramm, werden die vorläufigen Definitionen einfach zu Deklarationen, erscheint aber keine Definition, werden alle vorläufigen Definitionen zusammen zu einer Definition mit Initialisierung O. A.ll Gültigkeitsbereich und Bindung Ein Programm muß nicht insgesamt auf einmal übersetzt werden; das Quellpro- gramm kann in mehreren Dateien aufbewahrt werden, die Übersetzungseinheiten enthal- ten, und früher übersetzte Routinen können aus Bibliotheken geladen werden. Die Funktionen eines Programms können durch Aufrufe sowie durch die Manipulation von externen Daten korrespondieren. Zwei Gültigkeitsbereiche müssen folglich unterschieden werden: einerseits der Gü/tigkeitsbereich im Text (/exica/ scope) für einen Namen, das ist der Abschnitt eines Programms, in dem die Charakteristika eines Namens verstanden werden; andrerseits der exteme Gü/tigkeitsbereich für Objekte und Funktionen mit externer Bindung, der die Verbindungen zwischen Namen in separat bearbeiteten Übersetzungseinheiten bestimmt. A.ll.1 Gültigkeitsbereich im Text Namen gehören zu mehreren Namensräumen, die nicht miteinander kollidieren; der gleiche Name darf für verschiedene Zwecke verwendet werden, sogar im gleichen Gültigkeitsbereich, wenn die Verwendung in verschiedenen Ne!lSrä\lJ!leJl erfolgt. Die Klassen. sind: Qbj ekt J:ll!1!Q.!le_n,.,c:Ie!-,!!,.I!Pß.,l!!@ gsk<?nst ten; Mro: - kn; _E_i!ce!l_.Yp.  tr !.!:l,r!?!!'J!!li<:)!l.e1l \1n,d .!o._':!.[.3n; und Kompone!le,n..jde_ein- z<:ell Strulctur un.d_ ternativen jeder einzelnen Union. Diese Regeln weichen in mehreren Punkten von denen ab, die in der Ersten Ausgabe be- schrieben wurden. Marken hatten früher keinen eigenen Namensraum; Etiketten von Strukturen und Unionen hatten jeweils ihren eigenen Namensraum, und bei manchen Implementierungen auch die Etiketten von Aufzählungen; daß verschiedene Arten von Etiketten in einem Namensraum zusammengefaßt werden, ist eine neue Einschränkung. Die wichtigste Abweichung von der Ersten Ausgabe ist, daß jede Struktur und Union einen eigenen Namensraum für ihre Bestandteile erzeugt, so daß der gleiche Name in mehreren verschiedenen Strukturen auftreten darf. Diese Regel war schon seit mehre- ren Jahren allgemein in Gebrauch. Der Gültigkeitsbereich im Text beginnt für den Namen eines Objekts oder einer Funktion in einer externen Vereinbarung am Ende des Deklarators und reicht bis zum Ende der Übersetzungseinheit, in der er erscheint. Der Gültigkeitsbereich eines Para- meters in einer Funktionsdefinition beginnt am Anfang des Funktionsblocks und reicht über die ganze Funktion; der Gültigkeitsbereich eines Parameters in einer Funktionsde- klaration endet am Ende des Deklarators. Der Gültigkeitsbereich eines Namens, der am Anfang eines Blocks vereinbart wird, beginnt am Ende seines Deklarators und reicht bis m Ende des Blocks. Der Gültigkeitsbereich einer Marke ist die ganze Funktion, in der sIe erscheint. Der Gültigkeitsbereich des Etiketts einer Struktur, einer Union oder einer Aufzählung, oder einer Aufzählungskonstanten beginnt, wenn der Name in einer 'lyPan- gabe erscheint, und reicht bis zum Ende der Übersetzungseinheit (bei externen Verein- barungen) oder bis zum Ende des Blocks (bei Vereinbarungen in einer Funktion). Wenn ein Name explizit am Anfang eines Blocks vereinbart wird, auch am Anfang des Funktionsrumpfs, wird jede Vereinbarung des Namens außerhalb des Blocks bis zum Ende des Blocks ausgesetzt. 
226 A Sprachbeschreibung A,12 Der Preprc >or Z27 Wie in  A,10.2 besprochen, gibt die erste externe Vereinbarung eines Namens dem Namen interne Bindung, wenn static verwendet wird, und sonst externe Bindung. Wenn eine Vereinbarung für den Namen eines Objekts innerhalb eines Blocks extern nicht be- inhaltet, dann besitzt der Name keine Bindung und existiert eindeutig in der Funktion. Enthält eine Funktionsvereinbarung keine Angabe zur Speicherklasse, wird so verfahren, als ob extern angegeben wurde. Wenn extern angegeben ist und wenn eine externe Ver- einbarung für den Namen im Gültigkeitsbereich um den Block aktiv ist, dann hat der Na- me die gleiche Bindung wie die externe Vereinbarung, und er bezieht sich auf das gleiche Objekt oder die gleiche Funktion; ist aber keine externe Vereinbarung sichtbar, so hat der Name externe Bindung. A.12 Der Preprozessor pr_ P!eprozessor ersetzt M.akos, srgt. für b..e.dingte. pbersetzun.g (collditional compilation) .und . nn [)ate ien_ .!._ ....ei ügen. Der Preprozess oE wird in Zeilen angesprochen, die mit # beginnen; vor # kann noch Zwischenraum stehen. Die Syntax dieser Zeilen ist unabhängig vom Rest der Sprache; sie können an beliebigen Stellen vor- kommen; ihr Effekt hält bis zum Ende der Übersetzungseinheit an, unabhängig von den anderen Überlegungen zu Gültigkeitsbereichen. Zeilengrenzen sind signifikant; jede Zeile wird einzeln analysiert (Zeilen können aber verbunden werden, siehe  A.12.2). Für den Preprozessor ist ein Symbol (token) jedes Symbol der Sprache oder auch eine Zeichenfolge, die einen Dateinamen definiert, wie bei der #include-Anweisung (A,12.4); zusätzlich ist jedes Zeichen, das nicht anders definiert ist, ein Symbol. Inner- halb von Preprozessor-Zeilen ist jedoch der Effekt von Zwischenraum, mit Ausnahme von Leerzeichen und (horizontalen) Tabulatorzeichen, undefmiert. Der Preprozessor arbt.itet in verschiedenen Phasen logisch nacheinander; in einer Implementierung können die Phasen auch zusammengefaßt sein. 1. Zuerst werden die in A,12.1 beschriebenen Drei-Zeichen-Folgen durch ihre äquiva- lenten Zeichen ersetzt. Wenn die Betriebssystem-Umgebung dies verlangt, werden Zeilentrenner zwischen die Zeilen der Quelle eingefügt. 2. Wenn ein Gegenschrägstrich \ gefolgt von einem Zeilentrenner auftritt, werden bei- de entfernt; damit werden Zeilen verbunden (A.12.2). 3. Das Programm wird in Symbole zerlegt, die durch Zwischenraumzeichen getrennt sind; Kommentare werden durch einzelne Leerzeichen ersetzt. Dann werden die Preprozessor-Anweisungen befolgt und Makros ( A,12.3-A,12.10) expandiert. 4. Ersatzdarstellungen in Zeichenkonstanten und konstanten Zeichenketten ( A,2.5.2, A,2.6) werden durch die äquivalenten Zeichen ersetzt; anschließend werden benach- barte konstante Zeichenketten aneinandergehängt. 5. Das esultat .wir ?bersetzt und mit anderen Programmen und Bibliotheken gebun- den, mdem die notlgen Programme und Daten gesammelt sowie externe Funktionen und Objektverweise mit ihren Definitionen verknüpft werden. A.12.1 Dm-ZeIchen-Folgen . Der Zeichensatz von C-Quellprogrammen ist im 7-Bit-ASOI enthalten, aber er ist eme _ Obermee vom ISO 646-1983 Invariant Code Set. Damit Programme im einge- schränkten Zeichensatz dargestellt werden können, werden die folgenden Drei-Zeichen- Flgen (trigraph sequences) jeweils durch das entsprechende einzelne Zeichen ersetzt. Dieser Ersatz erfolgt vor jeder anderen Bearbeitung. 77'" # 77( [ 71< ( 77 I \ 77)] 11>} 77' 771 I 77- Anderer derartiger Ersatz findet nicht statt. Drei-Zeichen-Folgen sind neu im ANSl-Standard. A.ll.2 Bindung Innerhalb einer Übersetzungseinheit beziehen sich alle Vereinbarungen des glei- chen Objekts oder Funktionsnamens mit interner Bindung auf das Gleiche, und das Ob- jekt oder die Funktion ist in der Übersetzungseinheit eindeutig. Alle Vereinbarungen des gleichen Objekts oder Funktionsnamens mit externer Bindung beziehen sich auf das Gleiche, und das Objekt oder die Funktion wird im ganzen Programm gemeinsam be- nutzt. A.12.2 Verbinden von Zeilen Zeilen, .e mt dem Gegenschrägstrich \ enden, werden zusammengefaßt, indem der. Gegensagstnch und der nachfolgende Zeilentrenner entfernt werden. Dies ge- schieht, bevor m Symbole zerlegt wird. A.12.3 Makrodeßnition und Expansion Eine Kontrollzeile der Form 1I def ine identijier token-sequence veranlaßt, daß der Preprozessor anschließend den angegebenen Namen durch die ange- gebene Fole von ymbolen ersetzt; Zwischenraum vor und nach der Symbolfolge wird entfernt. Eme zweite #define-Anweisung für den gleichen Namen gilt als Fehler es sei denn, die zweite Symbolfolge ist identisch zur ersten, wobei alle trennenden Zwischen- räume als äquivalent angesehen werden. Eine Zeile der Form 1I define identifier( identifier-list ) token-sequence defIniert en Makr mt Parametern, die durch die identifier-list festgelegt werden; in der DeflDltlon muß die linke Klammer dem Makronamen unmittelbar folgen. Wie bei der ersten Form wird Zwischenraum vor und nach token-sequence entfernt und der Ma- kro darf nur neu defIniert werden, wenn Anzahl und Namen der Paramter sowie die Symbolfolge identisch sind. Eine Kontrollzeile der Form 1I undef identifier löscht die DefInition des Namens für den Preprozessor, Es ist kein Fehler, wenn #undeC auf einen unbekannten Namen angewendet wird. Wenn ein Makro mit der zweiten Art von Definition eingeführt wird, besteht ein Makroaufruf aus dem Makronamen, dem Zwischenraum folgen kann, danach einer lin- 
.Sprachbeschreibung A,12 Der Prel essor 229 228 J ken Klammer, einer Folge von Symbolen, die durch Komma getrennt sind, und einer rechten Klammer. Die Argumente des Makroaufrufs sind die Symbolfolgen, die durch Komma getrennt sind; Kommas innerhalb von verschachtelten Klammern, Zeichenkon- stanten oder konstanten Zeichenketten gelten nicht als Argumenttrenner. Bei der Zu - sammenstellung des Makroaufr,ufs fin<iet, keine M"a!ro-<i!lsjQIl,_iI1, C<:Il Ar g),l!!lllt!;n statt.- Die AnZahl der Argumentebdm Aufruf muß gleich der Anzahl der Parameter in der Makrodefinition sein. Wenn die Argumente isoliert sind, wird Zwischenraum vor und nach jedem Argument entfernt. Dann wird die Symbolfolge jedes Arguments über- all dort an Stelle des entsprechenden Parameternamens in der Symbolfolge aus der Ma- krodeflnition eingesetzt, wo der Parametername außerhalb von Zeichenkonstanten und konstanten Zeichenketten auftritt. W nn <I !;_!' !-l!meteLin de<,r Ersatz-Symbolfolge -kcin 1t,\'QriiSK!tt! ..<!_ wenn _ ihm If _ d_r V_!!!lochJ__ dendie _ _Ar.&!! l!1- SYJ:I.1 lfo IBn\lf aIa-()ufruf.e unter<:ht,,'!11..L!!.<1arf '!Y!! JWin4 r!..... .!1JPittel \2;y, bevor einges ,:_ Zwei besondere Operatoren beeinflussen die Ersetzung. Erstens, wenn einem Pa- rameter in der Ersatz-Symbolfolge # unmittelbar vorausgeht, wird das zugehörige Argu- ment mit Doppelanführungszeichen umgeben, und dann werden # und der Parameterna- me insgesamt durch das Zeichenketten-Argument ersetzt. Ein Gegenschrägstrich \ wird vor jedem Doppelanführungszeichen und jedem Gegenschrägstrich \ eingefügt, die als Begrenzung oder innerhalb einer konstanten Zeichenkette oder Zeichenkonstante im Ar- gument auftreten. Zweitens, wenn die token-sequence jeder der beiden Formen einer MakrodefInition das Symbol ## enthält, wird unmittelbar na(lE..e. r Parameter jedes ## gelöc ht! l1d .zw zus  mi  enrum auf beiden Se ten, so d_aßd.i_b,_ chbarten S - bol E ett !.....'W'!:c!Jl!I d ei!1 neubol bilden ", Der Effekt ist undefiniert, wenn un- gültige Symbole produziert werden oder wenn das Resultat von der Reihenfolge der Be- arbeitung mehrerer ## abhängt. ## darf außerdem weder am Anfang noch am Ende der Ersatz-Symbolfolge auftreten. Bei beiden Arten von Makros wird die Ersatz-Symbolfolge wiederholt auf weitere definierte Namen abgesucht. Wenn allerdings bei einer Expansion ein bestimmter Name einmal ersetzt wurde, wird er nicht mehr ersetzt, wenn er bei der Suche erneut auftaucht; er bleibt dann unverändert. uch weil..!!. <!_!l..Q&ÜligJ 2pan Q..I!,!Il__M,1lg:oslIli J?e.t, &iH sie_.pchtal P!'Q sso !-\Veisl!ng. Der ANsl-Standard definiert die Details der Makro-Expansion exakter, als das in der Er- sten Ausgabe geschah. Die wichtigste Änderung sind die neuen Operatoren # und ##, die Umwandlung in Zeichenketten und Verkettung zulassen. Manche der neuen Regeln, insbesondere diejenigen im Zusammenhang mit Verkettung, sind bizarr. (Siehe das nachfolgende Beispiel.) Mit Makros können zum Beispiel wesentliche Konstanten defmiert werden: #define TABSIZE 100 int table[TABSIZE]i Die Definition #define ABSDIFF(a, b) «a»(b)? (a)-(b) : (b)-(a» defmiert einen Makro, der den absoluten Wert der Differenz seiner Argumente liefert. Anders als bei einer Funktion, die dasselbe tun soll, können die Argumente und der Re- sultatwert beliebige arithmetische Typen besitzen oder sogar Zeiger sein. Außerdem werden die Argumente, die auch Nebenwirkungen verursachen können zweimal bewer- tet, einmal für die Bedingung und einmal, um den Resultatwert zu berechnen. Mit der Definition #define tfile(dir) #dir "/%S" liefert der Makroaufruf tempfile(/usrjtmp) "/UsrftlJ1)" "fXs" und das Resultat wird anschließend zu einer einzigen konstanten Zeichenkette zusam- mengefügt. Nach #define cat(x, y) x ## y liefert er Auruf cat(var,123) den Wert var123. Der Aufruf cat(cat(I,2),3» ist jedoch undefimert: dte Angabe von ## verhindert die Expansion der Argumente des äußeren Aufrufs. Der Aufruf liefert also die Symbolfolge cat ( 1 , 2 )3 und )3 (ie Verkettung ds letten Symbols vom ersten Argument mit dem ersten Symbol des zweiten Arguments) Ist kelO legales Symbol. Wenn man eine weitere Makrodefiniti- on einführt #define xcat(x,y) cat(x,y) funktioniert es besser; xcat(xcat(l, 2), 3) liefert wirklich 123, denn die Expansion von xcat selbst enthält ## nicht. Ebenso produziert AßSDIFF(ABSDIFF(a,b),c) das erwartete, voll expandierte Re- sultat. A.12.4 Einrtigen von Dateien Eine Kontrollzeile der Form 11 include <filename> wird durch den Inhalt der angegebenen Datei ersetzt. Als Zeichen im Namen filename dürfen weder > noch ein Zeilentrenner auftreten, und der Effekt ist undefiniert, wenn ftlename ., " \ oer j* enthält. Nach der angegebenen Datei wird an einer Folge von Stellen gesucht, die von der Implementierung abhängen. Analog wird bei einer Kontrollzeile der Form 11 include "filellame" Z\lr ,Ü!1_ l!1apgl!!!tß.E. '!!i!..l!&!i<:!!. en Q ,!ell.<!L&..s!l_cht (absichtlich imple- mentterungsabhängig formuliert), und \\l_n_eseu<:he rfolgl,o.I eib !!.!!!l!!.()._in der erst_en _F.:.m. Der Effekt von " \ oder j* im Dateinamen bleibt undeflniert, aber > ist erlaubt. 
230 A c.. Jrachbeschreibung A.12 Der Pn zessor 231 Schließlich wird eine Anweisung der Form 11 include token-sequence die keiner der vorhergehenden Formen entspricht, so interpretiert, daß zunächst die tQ !en- sJ!!P!.. ence wie normaler Text expandiert wird. Eine der beiden Formen mit <... > oder ..... muß entstehen. und sie wird dann so bearbeitet wie vorher beschrieben. A.12.5 Bedingte Übersetzung Teile eines Programms können bedingt übersetzt werden. wie im folgenden Syn- tax-Schema beschrieben: preprocessor-conditional: if-/ine tat e/if-parts else-partoplllendif if-/ine: 11 if constant-expression 11 ifdef identifier 11 1£ndef identifier elif-parts: e/if-line tat e/if-partsopt elif-/ine: lIelifcomtanexpreßwn else-part: else-/ine tat else-/ine: fj else Jede der Anweisungen (if-line, elif-line, else-/ine und #endif) erscheint allein auf einer Zeile. Die konstanten Ausdrücke in #iC- und nachfolgenden #eIiC-Zeilen werden der Reihe nach bewertet, bis ein Ausdruck mit einem von Null verschiedenen Wert gefunden wird; der Text, der einer Zeile mit Wert 0 folgt, wird übergangen. Der Text im Anschluß an die erfolgreiche Anweisungszeile wird normal verarbeitet. tat bezeichnet hier beliebi- ges Material, inklusive Preprozessor-Zeilen, das nicht Teil der #iC-Struktur ist; dieser Teil kann auch leer sein. Wenn eine erfolgreiche #iC- oder #eIiC-Zeile gefunden und der zugehörige Text verarbeitet wurde, werden nachfolgende #eIiC- und #else-Zeilen zusam- men mit ihrem Text übergangen. Sind alle Ausdrücke null und gibt es #else, wird der Text nach #else normal verarbeitet. Text, der von inaktiven Zweigen der #iC-Struktur kontrolliert wird, wird ignoriert, abgesehen davon, daß die Verschachtelung der #iC- Strukturen kontrolliert wird. Jm . kOnsrn!!tA A.d.!.\I<;.kbe.L l!iL\ll!d1RC_ f1I!!J1!!I!.al.e Makr()- ExpallSiQl1s.. Außerdem werden Ausdrücke der Form defined identifier verbleiben. werden sie durch OL ersetZ! . Schließlich wird für jede Integer-Konstante L als Suffix angenommen, damit die Arithmetik im Bereich long oder unsigned long statt- fmdet. Der resultierende konstante Ausdruck (A.7.19) ist eingeschränkt: er muß ganz- zahlig sein und darf sizeoC, Umwandlungsoperationen und Aufzählungskonstanten nicht enthalten. Die Kontrollzeilen fj 1£def identifier 11 ifndef identifier sind jeweils zu 11 if defined identifier fj 1£ I defined identifier äquivalent. #eUt ist neu seit der Ersten Ausgabe, war aber in manchen Preprozessoren vorhanden. Der Preprozessor-Operator defined ist ebenfalls neu. oder A.12.6 Zeilenkontrolle Als Hilfe für andere Preprozessoren. die C-Programme generieren, sorgen Zeilen folgender Form fj line comtant "filename" fj 1 ine constant dafür, daß der Übersetzer für Fehlermeldungen annimmt, daß die dezimale Integer-Kon- stante die Zeilennummer der nächsten Quellzeile ist und daß filename den Namen der aktueen Eingabedatei festlegt. Fehlt die Angabe eines Dateinamens, bleibt der vorher gespeicherte Name unverändert. Makros auf der Zeile werden expandiert, bevor die Zeile interpretiert wird. A.12.7 Fehlermeldungen Eine Preprozessor-Zeile der Form 11 error token-sequence opt ver anlaßt den Prozessor, eine Fehlermeldung auszugeben, die token-sequence enthält. A.12.8 pragma Eine Kontrollzeile der Form fj pragma token-sequence opt veranlaßt den Prozessor, eine implementierungsabhängige Aktion durchzuführen. Eine unbekannte pragma-Anweisung wird ignoriert. A.12.9 Leere Anweisung Eine Preprozessor-Zeile der Form fj hat keinen Effekt. defined ( identifier) .crsetzt,VO! nacakrosgct__!!n !:Y'.3!. durch-.! ! IlI!eamein.!_Prep!o- zessor de(m1ert ist 1 . un4durch.t),.!ls.l1t._WE.!111..!1!_ 0- gxpnsion I!.of}in 
232 A C-Sprachbeschreibung A.13 GrammatiJr 233 A.12.10 Vordefinierte Namen Einige Namen sind vordefiniert und liefern besondere Information, wenn sie ex- pandiert werden. Wie auch der Preprozessor-Operator defined dürfen sie weder gelöscht noch umdefmiert werden. __LI NE_ Eine dezimale Konstante, die die Nummer der aktuellen Quellzeile enthält. FILE_ Eine konstante Zeichenkette, die den Namen der Datei enthält, die gerade übersetzt wird. _DATE_ Eine konstante Zeichenkette, die das Datum der Übersetzung enthält, in der Form "Mnm dd yyyy". _TJHE_ Eine konstante Zeichenkette, die die Uhrzeit der Übersetzung enthält, in der Form "hh:nm:ss". __STDC_ Die Konstante 1. Dieser Name soll mit Wert 1 nur in Implementierungen definiert werden, die dem Standard genügen. #error und #pragm8 sind neu im ANSI-Standard; die vordefInierten Preprozessor-Ma- kros sind neu, aber einige von ihnen waren in manchen Implementierungen schon vor- handen. declaration-list: declaration declaration-list declaration declaration-specifiers: storage-class-specifier declaration-specifiersopl type-specifier declaration-specifiersopl type-qualifier declaration-specifiersopl storage-class-specifier: eins von auto register static extern typedef type-specifier: eins von void char short int long float double signed uns igned struct-or-union-specifier enum-specifier typedef-name type-qualifier: eins von const volatile struct-or-union-specifier: stlUct-or-union identifier opt ( struct-declaration-list ) Struct-or-union identifier struct-or-union: eins von struct union struct-declaration-list: struct-declaration struct-declaration-list struct-declaration init-declarator-list: init-declarator init-declarator-list , init-declarator init-declarator: declarator declarator - initializer struct-declaration: specifier-qualifier-list struct-declarator-list ; specifier-qualifier-list: type-specifier specifier-qualifier-listopl type-qualifier specifier-qualifier-listopl struct-declarator-list: struct-declarator struct-declarator-list , struct-declarator struct-declarator: declarator dec/arator opI : constant-expression enum-specifier: enum identifier opI ( enumerator-list) enum identifier A.13 Grammatik Hier folgt nochmals die Grammatik, die schon in den vorhergehenden Abschnitten dieses Anhangs gezeigt wurde. Diese Grammatik hat exakt den gleichen Inhalt, aber ei- ne andere Reihenfolge. Die Grammatik hat die bislang undefinierten Eingabesymbole integer-constant, character-constant, floating-co1lstant, identifier, string und enumeratio1l-co1lstant; Worte in Schreibmaschinenschrift sowie Symbole aus Sonderzeichen sind Eingabe- symbole, die unverändert angegeben werden. Die Grammatik kann mechanisch in eine Eingabe transformiert werden, die ein automatischer Parser-Generator akzeptiert. Dazu müssen die Alternativen in den Regeln syntaktisch korrekt markiert werden. Außerdem müssen die Regeln mit "eins von" expandiert und (je nach den Vorschriften des Parser- Generators) die Regeln mit opt mit dem und ohne das optionale Symbol dupliziert wer- den. Entfernt man dann noch die Regel typedef-name: identifier und vereinbart typedef- name als Eingabesymbol, so akzeptiert der Parser-Generator YACC dies e Grammatik. Die Grammatik hat einen einzigen-Ko nflikt, d erv on der Ir-eIse-Mehrd eutigkeit herrü h rt. translation-unit: extemal-declaratioll translation-Imit extemal-declaration extemal-declaration: function-defi1l i tion declaration function-defi n itio1l: declaration-specijiers opt declarator declaration-list opl compolmd-statement declaratio1l: declaration-specifiers i1lit-declarator-list opt ; 
234 enumerator-list: enumerator enumerator-list, enumerator enumerator: identifier identifier - constant-expression declarator: pointer opt direct-declarator direct-declarator: identifier ( declarator ) direct-declarator [ constant-expression opl ] direct-declarator ( parameter-type-list) direct-declarator ( identifier-listopt) pointer: * type-qualifier-listopt * type-qualifier-listopt pointer type-qualifier-list: type-qualifier type-qualifier-list type-qualifier parameter-type-list: parameter-list parameter-list , parameter-list: parameter-declaration parameter-list ,parameter-declaration parameter-declaration: declaration-specifiers declarator declaration-specifiers abstract-declarator opt identifier-list: identifier identifier-list , identifier initializer: assignment-expression ( initializer-list ) ( initializer-list . initializer-list: initializer initializer-list , initializer type-name: specifier-qualifier-list abstract-declarator opt I .Sprachbeschreibung 235 A.13 Grammal abstract-declarator: pointer pointer opt direct-abstract-declarator direct-abstract-declarator: ( abstract-declarator) direct-abstract-declarator opl [ constant-expression opl ] direct-abstract-declarator opl ( parameter-type-listopt) typedef-name: identifier statement: labeled-statement expression-statement compound-statement selection-statement iteration-statement jump-statement labeled-statement: identifier: statement ca se constant-expression statement default : statement expression-statemen t: expressionopt ; compound-statement: ( declaration-list opl statement-list opl ) statement-list: statement statement-list statement selection-statement: if (expression) statement if (expression) statement else statement swi tch (expression) statement iteration-statement: while (expression) statement do statement while ( expression) for (expressionopI; expressionopt ; expressionopt ) statement jump-statement: goto identifier ; continue ; break ; return expression opl ; expression: assignment-expression expression . assignment-expression 
236 I '-Sprachbeschreibung A.B GrammaC '137 assignment-expression: conditional-expression unary-expression assignment-operator assignment-expression assignment-operator: eins von . - *- /- 'X- +- «- >>- &- A - 1- multiplicative-expression: cast-expression multiplicative-expression * cast-expression multiplicative-expression / cast-expression multiplicative-expression 'X cast-expression cast-expression: unary-expression ( type-name) cast-expression unary-expression: postfix-expression ++ unary-expression -- unary-expression unary-operator cast-expression sizeof unary-expression sizeof (type-name) unary-operator: eins von & * + postflX-expression: primary-expression postflX-expression [ expression ] postflX-expression ( argument-expression-list opt ) postfix-expression . identifier postfix-expression -> identifier postflX-expression ++ postflX-expression -- primary-expression: identifier constant string ( expression ) argument-expression-list: assignment-expression argument-expression-/ist . assignment-expression constant: integer-constallt character-constallt f/oatillg-colIstallt enumeration-colIstaflt conditional-expression: logical-OR-expression logical-OR-expression ? expression : conditional-expression constant-expression: conditional-expression logical-OR-expression: logical-AND-expression logica/-OR-expression Illogical-AND-expression logical-AND-expression: inclusive-OR-expression logical-AND-expression && inclusive-OR-expression inc/usive-OR-expression: exclusive-OR-expression inclusive-OR-expression I exclusive-OR-expression exclusive-OR-expression: AND-expression exclusive-OR-expression A AND-expression AND-expressioll: equality-expression AND-expression & equality-expression equality-expression: relational-expression equality-expression - re/ational-expression equality-expression ,- re/ational-expression relatiolla/ -expression: shift-expression re/ational-expression < shift-expression re/ationa/-expression > shift-expressioll re/ationa/-expression <- shift-expression relationa/-expressioll >- shift-expression shift-expression: additive-expression shift-expression «additive-expression shift-expression »additive-expression additive-expression: multiplicative-expression additive-expression + multiplicative-expression additive-expression - multiplicative-expression 
238 A ( )fachbeschreibung 239 Die folgende Grammatik für den Preprozessor faßt die Struktur der Kontrollzeilen zusammen, aber sie eignet sich nicht für eine mechanisierte Erkennung. Sie enthält das Symbol text, das sich auf gewöhnlichen Programmtext, unbedingt zu bearbeitende Pre- prozessor-Kontrollzeilen oder vollständige bedingte Preprozessor-Konstruktionen be- zieht. B Die Standard-Bibliothek contro/-/ine: fl define idefltifier token-sequence fl define idefltijier( identijier , , identijier) token-sequence fl undef identijier # inc1ude kname> fl inc1ude "fi/ename" fl inc1ude token-sequence fl 1ine constant "fi/ename" fl 1 ine constant fl error token-sequenceopt fl pragma tokefl-sequenceopt # preprocessor-conditiona/ preprocessor-conditiona/: if-line text e/if-parts else-part opl fl end 1£ if-line: fl if constaflt-expression fl ifdef idefltijier fl ifndef idefltijier e/if-parts: elif-line text elif-parts opt elif-line: # e1if constaflt-expression else-part: else-line text else-line: fl else Dieser Anhang ist eine kurze Beschreibung der Bibliothek, die im ANSI-Standard definiert wird. Die Standard-Bibliothek ist kein Teil der Programmiersprache C selbst, aber eine Umgebung, die Standard-C realisiert, wird auch die Funktionen, iypen und Makros dieser Bibliothek zur Verfügung stellen. Wir haben ein paar Funkti<?nen  ge- lassen. die nur begrenzt nützlich oder leicht aus anderen aufgebaut werden können; wir  Multi-Byte-Zeichen weggelassen. und wir cJ11l pationale Gesich tspunkte nicht, das heißt, Eigenschaften. die von der lokalen Sprache, Na ti on ali tät oder Küfiurab- hängen. Die Funktionen. Typen und Makros der Standard-Bibliothek sind in Standard-De- finitionsdateien deklariert: <assert.h> <float.h> <ctype.h> <limits.h> <errno.h> <locale.h> <math.h> <set jß1). h> <signal.h> <stdarg.h> <stddef.h> <stdio.h> <stdl ib.h> <string.h> <time.h> Auf eine Defmitionsdatd header wird mit .include <header>- zugegriffen. Defmitionsdateien können in beliebiger Reihenfolge und beliebig oft mit #include eingefügt werden. Eine Defmitionsdatei muß außerhalb von allen externen Vereinbarungen eingefügt werden, und zwar bevor irgendetwas benutzt wird, das in der Definitionsdatei vereinbart wird. Eine Defmitionsdatei muß keine Quelldatei sein. Für die Standard-Bibliothek sind exte.me N s_ die mit e41 eJL1I!!tc;.r- strich -,,ibeginnen Sowie alle anderen Namen. die mit einem Unterstrich und einem 'Großb uc hstaben -oder einem weiteren Unterstrich beginnen. B.l Ein- und Ausgabe: < stdio.h > Die Ein- und Ausgabefunktionen, Typen und Makros, die in <stdio.h> vereinbart sind, machen nahezu ein Drittel der Bibliothek aus. Ein Datenstrom (stream) ist Quelle oder Ziel von Daten und wird mit einer Platte oder einem anderen Peripheriegerät verknüpft. !:>i _Bibliothek..-.!lDtstützt zwei Arten vopt_enströme.IC?xt__nd__!?iJ.t_i!!()![I}a(} die erdings ..be.i J1.1_3J.! n Syste - men und nd !i.!:1_ N.!.x ide  .fh sind. Ein Textstrom ist eine Folge von Zeilen; j ede Zeil e enthält null oder mehr Zeichen und ist mit '\n' abgeschlossen. Eine Umge- bung muß möglicherweise zwischen einem Textstrom und einer anderen Repräsentierung umwandeln (also zum Beispiel ,\n' als Wagenrücklauf und Zeilenvorschub abbilden). Ein Binärstrom ist eine Folge unbearbeiteter Bytes zur Aufzeichnung intemer Daten. Wird ein Binärstrom geschrieben und auf dem gleichen System wieder eingelesen, so ent- steht die gleiche Information. Ein Strom wird durch Eroffnen (open) mit einer Datei oder einem Gerät verbun- den; die Verbindung wird durch Abschließen (close) wieder aufgehoben. Eröffnet man eine Datei, so erhält man einen Zeiger auf ein Objekt vom iyp FILE, wo alle Information hinterlegt ist, die zur Kontrolle des Stroms nötig ist. Wenn die Bedeutung eindeutig ist, werden wir die Begriffe FILE-Zeiger und Datenstrom gleichberechtigt verwenden. 
240 B Standard-Bibliothek Wenn die Ausführung eines Programms beginnt, sind die drei Ströme stdin, stdout und stderr bereits eröffnet. B.1.1 Dateioperationen Die folgenden Funktionen beschäftigen sich mit Dateioperationen. Der 1YP size_t ist der vorzeichenlose, ganzzahlige Resultattyp des sizeof-Operators. FILE *fopen(const char *filename, const char *mode) fopen eröffnet die angegebene Datei und liefert einen Datenstrom oder NULL bei Mißerfolg. Zu den erlaubten Werten von mode gehören "r" Textdatei zum Lesen eröffnen "w" Textdatei zum Schreiben erzeugen; gegebenenfalls alten Inhalt wegwerfen "8" anfügen; Textdatei zum Schreiben am Dateiende eröffnen oder erzeugen "r+" Textdatei zum Ändern eröffnen (Lesen und Schreiben) "w+" Textdatei zum Ändern erzeugen; gegebenenfalls alten Inhalt wegwerfen "8+" anfügen; Textdatei zum Ändern eröffnen oder erzeugen, Schreiben am Ende Ändern bedeutet, daß die gleiche Datei gelesen und geschrieben werden darf; mush oder eine Funktion zum Positionieren in Dateien muß zwischen einer Lese- und einer Schreib- operation oder umgekehrt aufgerufen werden. Enthält mo de nach dem ersten Zeichen noch b, also etwa "rb" oder "w+b", dann wird aufeiDebiDäre Datei zugegriffen. Dateina- men sin d auf FILENAME_ MAX Zeichen begrenzt. Höchstens FOPEN _ MÄX 15'äteien kön- nen gleichzeitig offen sein. FILE *freopen(const char *filename, const char *mode, FILE *stream) freopen eröffnet die Datei für den angegebenen Zugriff mode und verknüpft stream damit. Das Resultat ist stream oder NULL bei einem Fehler. Mit freopen ände':.  man norm <lle_rwe<i! pateien, die mit stdin ,stdout oder stderr verüI!ruiQ<!'. int fflush(FILE *stream) Bei einem Ausgabestrom sorgt mush dafür, daß gepufferte, aber noch nicht ge- schriebene Daten geschrieben werden; bei einem Eingabestrom ist der Effekt undefi- niert. Die Funktion liefert EOF bei einem Schreibfehler und sonst Null. mush(NULL) bezieht sich auf alle offenen Dateien. int fclose(FILE *stream) felose schreibt noch nicht geschriebene Daten für stream, wirft noch nicht gelese- ne, gepufferte Eingaben weg, gibt automatisch angelegte Pu(fer frei und schließt den Da- tenstrom. Die Funktion liefert EOF bei Fehlern und sonst Null. int remove(const char *filename) remove entfernt die angegebene Datei, so daß ein anschließender Versuch, sie zu eröffnen, fehlschlagen wird. Die Funktion liefert bei Fehlern einen von Null verschiede- nen Wert. int rename(const char *oldname, const char *newname) reoame ändert den Namen einer Datei und liefert nicht Null, wenn der Versuch fehlschlägt. B.1 Ein- und Au e: <stdio.h> 241 I I I i i I I I I I I I I FILE *tmpfile(void) tmpfile erzeugt eine temporäre Datei mit Zugriff "wb+", die automatisch gelöscht wird, wenn der Zugriff abgeschlossen wird, oder wenn das Programm normal zu Ende geht. tmpfile liefert einen Datenstrom oder NULL, wenn die Datei nicht erzeugt werden konnte. char *tmpnam(char s[L_tmpnam]) tmpnam(NULL) erzeugt eine Zeichenkette, die nicht der Name einer existenten Datei ist, und liefert einen Zeiger auf einen internen Vektor im statischen Speicherbe- reich. tmpnam(s) speichert die Zeichenkette in s und liefert auch s als Resultat; in s müssen wenigstens L tmpnam Zeichen abgelegt werden können. tmpnam erzeugt bei jedem Aufruf einen deren Namen; man kann höchstens von TMP _ MAX verschiedenen Namen während der Ausführung des Programms ausgehen. Zu beachten ist, daß tmpnam einen Namen und keine Datei erzeugt. . int setvbuf(FILE *stream, char *buf. int mode, size_t size) setvbuf kontrolliert die Pufferung bei einem Datenstrom; die Funktion muß aufge- rufen werden, bevor gelesen oder geschrieben wird, und vor allen anderen Operationen. Hat mode den Wert IOFBF, so wird vollständig gepuffert, IOLBF sorgt für zeilenweise Pufferung bei Textdateien und IONBF verhindert Puffern Wenn buf nicht NULL ist, wird buf als Puffer verwendet; adernfalls wird ein Puffer angelegt. slze legt die Puffer- größe fest. Bei einem Fehler liefert setvbuf nicht Null. void setbuf(FILE *stream, char *buf) Wenn buf den Wert NULL hat, wird der Datenstrom nicht gepuffert. Andernfalls ist setbuf äquivalent zu (vold) setvbuf(stream, buf, _IOFBF, BUJo'SIZ). B.1.2 Formatierte Ausgabe Die prlntf-Funktionen ermöglichen Ausgabe-Umwandlungen unter Formatkon- trolle. int fprintf(FILE *stream. const char *format. ...) fprlntf wandelt Ausgaben um und schreibt sie in stream unter Kontrolle von format. Der Resultatwert ist die Anzahl der geschriebenen Zeichen; er ist negativ, wenn ein Fehler passiert ist. Die Format-Zeichenkette enthält zwei Arten von Objekten: gewöhnliche Zeichen, die in die Ausgabe kopiert werden, und Umwandlungsangaben, die jeweils die Umwand- lung und Ausgabe des nächstfolgenden Arguments von fprlntf veranlassen. Jede Um- wandlungsangabe beginnt mit dem Zeichen % und endet mit einem Umwandlungszei- chen. Zwischen % und dem Umwandlungszeichen kann, der Reihe nach, folgendes ange- gb,en wer<fen: "------. .. .. . -----, " . Steuerzeichen (flags) (in beliebiger Reihenfolge), die die Umwandlung modifIZieren: - veranlaßt Ausrichtung des umgewandelten Arguments in seinem Feld nach links. + bestimmt, daß die Zahl immer mit Vorzeichen ausgegeben wird. 
242 )ie Standard-Bibliothek Leerzeichen wenn das erste Zeichen kein Vorzeichen ist, wird ein Leerzeichen vorange- stellt. o legt bei numerischen Umwandlungen fest, daß bis zur Feldbreite mit führen- den Nullen aufgefüllt wird. * verlangt eine alternative Form der Ausgabe. Bei 0 ist die erste Ziffer eine Null. Bei x oder X werden Ox oder OX einem von Null verschiedenen Resultat vorangestellt. Bei e, E, f, g und G enthält die Ausgabe immer einen Dezimal- punkt; bei g und G werden Nullen am Schluß nicht unterdrückt. . eine Zahl, die eine minimale Feldbreite festlegt. Das umgewandelte Argument wird in einem Feld aus gegebe D:Hda:Sr;;indestens so breit ist und bei Bedarf auch breiter. Hat das umgewandelte Argument weniger Zeichen als die Feldbreite verlangt, wird links (oder rechts, wenn Ausrichtung nach links verlangt wurde) auf die Feldbreite aufgefüllt. Normalerweise wird mit Leerzeichen aufgefüllt, aber auch mit Nullen. wenn das entsprechende Steuerzeichen angegeben wurde. . Ein t, der die Feldbreite von der Genauigkeit (precision) trennt. · Eine Zahl, die Genauigkeit, die die. mI!l E-_e_11. festlegt, die von ei- ner Zeichenkette ausgegeben werden. oder die Anzahl Ziffern. die nach dem Dezi- malpunkt bei e, E oder f Umwandlungen ausgegeben werden. oder die Anzahl signifi- kanter Ziffern bei g oder G Umwandlung oder die minimale Anzahl von Ziffern. die bei einem g lln77.ahlig en Wert ausgegeben werden sollen (führende Nullen werden dann bis zur gewünschten Breite hinzugefügt). . Ein Buchstabe als Längenangabe: h, I oder L. "h" bedeutet, d das zuehörige Ar - gument als short oder unslgned short aw;seeben wird; "I" bedeutet, daß das Argu- ment long oder unslgned long ist; "L" bedeutet, daß das Argument long double ist. Als Feldbreite oder Genauigkeit kann jeweils * angegeben werden; dann wird der Wert durch Umwandlung von dem nächsten oder den zwei nächsten Argumenten festgelegt, die den 1YP Int besitzen müssen. Die Umwandlungszeichen und ihre Bedeutung erklärt Tabelle B-l. Wenn das Zei- chen nach % kein Umwandlungszeichen ist, ist der Verlaufundefmiert. int printf(const char *format, ., ,) printC(...) ist äquivalent zu I'printC(stdout,...). int sprintf(char *s, const char *format, .,.) sprintf funktioniert wie printf, nur wird die Ausgabe in den Zeichenvektor s ge- schrieben und mit '\0' abgeschlossen. 5 muß groß genug für das Resultat sein. Im Re- sultatwert wird '\0' nicht mitgezählt. vprintf(const char *format, va_list arg) vfprintf(FILE *stream, const char *format. va_list arg) vsprintf(char *s, const char *format, va_list arg) Die Funktionen vprintC, vfprlntf und vsprintf sind äquivalent zu den entsprechen- den printf-Funktionen. jedoch wird die variable Argumentliste durch arg ersetzt. Dieser Wert wird mit dem Makro va start und vielleicht mit Aufrufen von va arg initialisiert. Siehe dazu die Beschreibung vn < stdarg.h > im Abschnitt B.7. - 243 B.1 Ein- und Au: e: <stdio.h> I I ! Zeichen ! d, i I 0 f x,X I I I u c I s f I e, E I I I g,G P n X Tabelle B-l. prlntf Umwandlungen Argument; Umwandlung in Int; dezimal mit Vorzeichen. Int; oktal ohne Vorzeichen (ohne führende Null). int; hexadezimal ohne Vorzeichen (ohne führendes Ox oder OX), mit abcdef bei Ox oder ABCDEF bei OX. Int; dezimal ohne Vorzeichen int; einzelnes Zeichen. nach Umwandlung in unslgned char. char *; aus der Zeichenkette werden Zeichen ausgegeben bis vor '\0' oder so viele Zeichen. wie die Genauigkeit verlangt. double; dezimal als [- }mmm.ddd, wobei die Genauigkeit die Anzahl der d festlegt. Voreinstellung ist 6; bei 0 entfällt der Dezimalpunkt. double; dezimal als [- }m.ddddddei:xx oder [- }m.ddddddEi:xx, wobei die Genauigkeit die Anzahl der d festlegt. Voreinstellung ist 6; bei 0 entfällt der Dezimalpunkt. double;"ce oder %E wird verwendet, wenn der Exponent kleiner als -4 oder nicht kleiner als die Genauigkeit ist; sonst wird %I' benutzt. Null und Dezimalpunkt am Schluß werden nicht ausgegeben. vold *; als Zeiger (Darstellung hängt von Implementierung ab). Int *; die Anzahl der bisher von diesem Aufruf vpn prlntr ausgegebenen Zeichen wird im Argument abgelegt. Ein Argument wird nicht umgewandelt. kein Argument wird umgewandelt; ein % wird ausgegeben. 8.1.3 Formatierte Eingabe Die scanf-Funktionen behandeln Eingabe-Umwandlungen unter Formatkontrolle. int fscanf(FILE *stream, const char *format, ...) fscanf liest von stream unter Kontrolle von format und legt umgewandelte Werte mit Hilfe von nachfolgenden Argumenten ab, die alle Zeiger sein müssen. Die Funktion wird beendet, wenn format abgearbeitet ist. fscanf liefert EOF, wenn vor der ersten Um- wandlung das Dateiende erreicht wird oder ein Fehler passiert; andernfalls liefert die Funktion die Anzahl der umgewandelten und abgelegten Eingaben. Die Format-Zeichenkette enthält normalerweise Umwandlungsangaben, die zur Interpretation der Eingabe verwendet werden. Die Format-Zeichenkette kann folgendes enthalten: . Leerzeichen oder Tabulatorzeichen, die ignoriert werden. . Gewöhnliche Zeichen (nicht aber %), die dem nächsten Zeichen nach Zwischenraum im Eingabestrom entsprechen müssen. . Umwandlungsangaben, bestehend aus %; einem optionalen Zeichen *, das die Zuwei- sung an ein Argument verhindert; einer optionalen Zahl, die die maximale Feldbreite festlegt; einem optionalen Buchstaben h, I oder L, der die Länge des Ziels beschreibt; und einem Umwandlungszeichen. 
244 B.1 Ein- und" abe: <stdio.h> lie Standard-Bibliothek Eine Umwandlungsangabe bestimmt die Umwandlung des nächsten Eingabefelds. Normalerweise wird das Resultat in der Variablen abgelegt, auf die das zugehörige Argu- ment zeigt. Wenn jedoch * die Zuweisung verhindern soll, wie bei %*s, dann wird das Eingabefeld einfach übergangen und eine Zuweisung findet nicht statt. Ein Eingabefeld ist als Folge von Zeichen definiert, die keine Zwischenraumzeicheo sind; es reicht entwe- der bis zum nächsten Zwischenraumzeichen, oder bis eine explizit angegebene Feldbreite erreicht ist. Dar1IJ()!&!L4!!Ls !.nf über Zeill!&fe1! z_e.1!._i!in"wegJiet un11;inE, Eingabe u_fde_n, denn Zeilentrenner sind Zwischenraumzeichen. (Zwischenraumzeichen sind Leerzeichen, . ,!:abulatorzei<:hen _entreer \D! _W..genrücklauf \1", Vertikak Iabula- tor \v 1!.I1 _Seit[lvQrschub \1). - - ... Das Umwandlungszeichen gibt die Interpretation des Eingabefelds an. Das zu- gehörige Argument muß ein Zeiger sein. Die erlaubten Umwandlungszeichen zeigt Ta- belle B-2. Den Umwandlungszeichen d, I, n, 0, u und x kann b vorausgehen, wenn das Argu- ment ein Zeiger auf sbort statt iot ist, oder der Buchstabe I, wenn das Argument ein Zei- ger auf long ist. Vor den Umwandlungszeichen e, fund g kann der Buchstabe I stehen, wenn ein Zeiger auf double und nicht auf Ooat in der Argumentliste steht, und L, wenn es sich um einen Zeiger auf long double handelt. int scanf(const char *format, ...) scaof(...) ist äquivalent zu fscanf(stdio,...). int sscanf(char *s, const char *format, ...) sscaof(s, ... ) ist äquivalent zu scaof(...), mit dem Unterschied, daß die Eingabezei- chen aus der Zeichenkette s stammen. Zeichen d e,f,g 8.1.4 Ein- uod Ausgabe von Zeicben int fgetc(FILE *stream) fgetc liefert das nächste Zeichen aus stream als unsigned cbar (umgewandelt in int) oder EOF bei Dateiende oder bei einem Fehler. char *fgets(char *s, int n, FILE *stream) fgets liest höchstens die nächsten n-l Zeichen in s ein und hört vorher auf, wenn ein Zeilentrenner gefunden wird. Der Zeilentrenner wird i m Vektor abgeleg t. Der Vek- tor wird mit '\0' abgeschlossen. fgets liefert s oder NULL bei Dateiende oder bei einem Fehler. int fputc(int c, FILE *stream) fputc schreibt das Zeichen c (umgewandelt in unsigoed cbar) in stream. Die Funk- tion liefert das ausgegebene Zeichen oder EOF bei Fehler. int fputs(const char *s, FILE *stream) fputs schreibt die Zeichenkette s (die '\n' nicht zu enthalten braucht) in stream. Die Funktion liefert einen nicht-negativen Wert oder EOF bei einem Fehler. (...] (A...] 245 o Tabelle B-2. scanf Umwandlungen Eingabedaten; Argumenttyp dezimal, ganzzahlig; int *. ganzzahlig; Int *. Der Wert kann oktal (mit 0 am Anfang) oder hexadezimal (mit Ox oder OX am Anfang) angegeben sein. oktal ganzzahlig (mit oder ohne 0 am Anfang); int *. dezimal ohne Vorzeichen; unsigned int *. hexadezimal ganzzahlig (mit oder ohne Ox oder OX am Anfang); int *. ein oder mehrere Zeichen; cbar *. Die nachfolgenden Eingabezeichen werden im angegebenen Vektor abgelegt, bis die Feldbreite erreicht ist; Voreinstellung is 1. '\0' wird nicht angefügt. In diesem Fall wird Zwischenraum nicht überlesen; das nächste Zeichen nach Zwischenraum liest man mit %1s. Folge von Nicht-Zwischenraum-Zeichen (ohne Anführungszeichen); cbar *, der auf einen Vektor zeigen muß, der die Zeichenkette und das abschIieende '\0' aufnehmen kann, das dazukommt. Gleitpunkt; Ooat *. Das Eingabeformat erlaubt für Ooat ein optionales Vorzeichen. eine Ziffernfolge, die auch einen Dezimalpunkt enthalten kann, und einen optionalen Exponenten. bestehend aus E oder e und einer ganzen Zahl, optional mit Vorzeichen. Zeiger, wie ihn printf("%p") ausgibt; void *. legt im Argument die Anzahl Zeichen ab, die bei diesem Aufruf bisher gelesen wurden; lot *. Vom Eingabestrom wird nicht gelesen, die Zählung der Umwandlungen bleibt unbeeinflußt. erkennt die längste nicht-leere Zeichenkette aus den Eingabezeichen in der angegebenen Klasse; char *. Dazu kommt '\0'. Die Klasse []...] enthält auch ]. erkennt die längste nicht-leere Zeichenkette aus den Eingabezeichen nicht in der angegebenen Klasse; cbar *. Dazu kommt '\0'. Die Klasse ["]...] enthält auch] . erkennt %; eine Zuweisung findet nicht statt. u x c s p n x int getc(FILE *stream) gete ist äquivalent zu fgete, kann aber ein Makro sein und dann das Argument für stream mehr als einmal bewerten. int getchar(void) getebar ist äquivalent zu getc(stdJn). char *gets(char *s) gets liest die nächste Zeile von stdin in den Vektor s und ersetzt dabei den ab - schIießend _C?l1tr.e.rUJ"4 '\0'. Die Funktion liefert s oder NULL bei Dateiende oder bei einem Fehler. 
246 B Standard-Bibliothek B.2 Tests für Z cnklassen: <ctype.h> 247 int putc(int c, FILE *stream) pute ist äquivalent zu fpute, kann aber ein Makro sein und dann das Argument für stream mehr als einmal bewerten. int putchar(int c) putchar(c) ist äquivalent zu putc(c,stdout). int puts(const char *s) puts schreibt die Zeichenkette s und einen Zeilentrenner in stdout. Die Funktion liefert EOF, wenn ein Fehler passiert, andernfalls einen nicht-negativen Wert. int ungetc(int c, FILE *stream) ungete stellt c (umgewandelt in UDslgned char) in stream zurück, von wo das Zei- chen beim nächsten Lesevorgang wieder geholt wird. Man kann sich nur darauf verlas- sen, daß pro Datenstrom ein Zeichen zurückgestellt werden kann. EOF darf nicht zurückgestellt werden. ungetc liefert das zurückgestellte Zeichen oder EOF bei einem Fehler. void rewind(FILE *stream) rewind(fp) ist äquivalent zu fseek(fp,OL,SEEK_ SET); c1earerr(fp). int fgetpos(FILE *stream, fpos_t *ptr) fgetpos speichert die aktuelle Position für stream bei .ptr. Der Wert kann später mit fsetpos verwendet werden. Der Datentyp fpos_t eignet sich zum Speichern von sol- chen Werten. Bei Fehler liefert fgetpos einen von Null verschiedenen Wert. int fsetpos(FILE *stream. const fpos_t *ptr) fsetpos positioniert stream auf die Position, die von fgetpos in .ptr abgelegt wurde. Bei Fehler liefert fsetpos einen von Null verschiedenen Wert. 8.1.5 Direkte Ein- und Ausgabe size_t fread(void *ptr, size_t size, size_t nobj. FILE *stream) fread liest aus stream in den Vektor ptr höchstens nobj Objekte der Größe size ein. fread liefert die Anzahl der eingelesenen Objekte; das kann weniger als die gefor- derte Zahl sein. Der Zustand des Datenstroms muß mit feof und ferror untersucht wer- den. size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *stream) fwrIte schreibt nobj Objekte der Größe slze aus dem Vektor ptr in stream. Die Funktion liefert die Anzahl der ausgegebenen Objekte; bei Fehler ist das weniger als nobj. 8.1.6 Positionieren In Dateien int fseek(FILE *stream, long offset, int origin) fseek setzt die Dateiposition für stream; eine nachfolgende Lese- oder Schreibope- ration wird auf Daten von der neuen Position ab zugreifen. Für eine binäre Datei wird die Position auf offset Zeichen relativ zu orlgin eingestellt; dabei können die Werte SEEK_SET (Dateianfang) SEEK_CUR (aktuelle Position) oder SEEK_END (Dateiende) angegeben werden. Für einen Textstrom muß offset Null sein oder ein Wert, der von nell stammt (dafür muß dann origin den Wert SEEK_SET erhalten). fseek liefert einen von Null verschiedenen Wert bei Fehler. long ftell(FILE *stream) f'tellliefert die aktuelle Dateiposition für stream oder -lL bei Fehler. 8,1.7 FehlerbehandJung Viele der Bibliotheksfunktionen notieren Zustandsangaben, wenn ein Dateiende oder ein Fehler gefunden wird. Diese Angaben können explizit gesetzt und getestet wer- den. Außerdem kann der Integer-Ausdruck errno (der in <errno.h> deklariert ist) eine Fehlernummer enthalten, die mehr Information über den zuletzt aufgetretenen Fehler liefert. void clearerr(FILE *stream) clearerr löscht die Dateiende- und Fehlernotizen für stream. int feof(FILE *stream) feof liefert einen von Null verschiedenen Wert, wenn für stream ein Dateiende no- tiert ist. int ferror(FILE *stream) ferror liefert einen von Null verschiedenen Wert, wenn für stream ein Fehler no- tiert ist. void perror(const char *s) perror(s) gibt s und eine von der Implementierung definierte Fehlermeldung aus, die sich auf die Fehlernummer in errno bezieht. Die Ausgabe erfolgt im Stil von fprintf(stderr I "Xs: Xs\n" I s, "Fehlermeldung") Siehe strerror im Abschnitt B.3. B.2 Tests für Zeichenklassen: <ctype.h> Die DefInitionsdatei < ctype.h > vereinbart Funktionen zum Testen von Zeichen. Jede Funktion hat ein Int-Argument, dessen Wert entweder EOF ist oder als unsigned char dargestellt werden kann, und der Resultatwert hat den 1YP Int. Die Funktionen lie- fern einen von Null verschiedenen Wert (wahr), wenn das Argument c die beschriebene Bedingung erfüllt; andernfalls liefern sie Null. isalnun(c) isalpha(c) oder isdigit(c) ist wahr isalpha(c) isupper(c) oder islower(c) ist wahr iscntrl(c) Steuerzeichen iSdigit(c) dezimale Ziffer 
248 r :e Standard-Bibliothek isgrap/!(c) islower(c) isprint(c) i spunctC c) sichtbares Zeichen, kein Leerzeichen Kleinbuchstabe (aber kein Umlaut oder ß) sichtbares Zeichen, auch Leerzeichen sichtbares Zeichen, mit Ausnahme von Leerzeichen, Buchstabe oder Ziffer Leerzeichen, Seitenvorschub (\1), Zeilentrenner (\0), Wagenrücklauf (\r), Tabulatorzeichen (\t), Vertikal- Tabulator (\v) Großbuchstabe (aber kein Umlaut) hexadezimale Ziffer ;sspace(c) ; supper(c) isxd;g;t(c) Im 7-Bit ASOI-Zeichensatz sind die sichtbaren Zeichen Ox20 (' ') bis Ox7E ('-'); die Steu- erzeichen sind ° (NUL) bis OxlF (US) sowie Ox7F (DEL). Zusätzlich gibt es zwei Funktioneo zur Umwandlung zwischen Groß- und Klein- buchstaben: int tolower(int c) wandelt e in einen Kleinbuchstaben um ;nt toupper( int c) wandelt e in einen Großbuchstaben um Wenn e ein Großbuchstabe ist, liefert tolower(e) den entsprechenden Kleinbuchstaben; andernfalls ist das Resultat e. Wenn e ein Kleinbuchstabe ist, liefert toupper(c) den ent- sprechenden Großbuchstaben; andernfalls ist das Resultat e. B.3 Funktionen für Zeichenketten: < string.h > In der Deftnitionsdatei < strlng.h > werden zwei Gruppen von Funktionen für Zei- chenketten vereinbart. Die erste Gruppe hat Namen, die mit str beginnen; die Namen der zweiten Gruppe beginnen mit memo Sieht man von memmove ab, ist der Effekt der Funktionen undefmiert, wenn zwischen überlappenden Objekten kopiert wird. In der folgenden Tabelle sind die Variablen sund t vom 1YP ehar *; die Parameter es und ct haben den 1YP eonst char *; der Parameter 0 hat den 1YP size t; und e ist ein int-Wert, der in ehar umgewandelt wird. Die Vergleichsfunktionen behandeln ihre Argu- mente als uosigned char Vektoren. char *strcpy(S,ct) Zeichenkette ct in Vektor s kopieren, inklusive '\0'; liefert s. char *strncpy(s,ct,n) höchstens n Zeichen aus ct in s kopieren; liefert s. Mit '\0' auffüllen, wenn ct weniger als 0 Zeichen hat. char *strcatCs,ct) Zeichenkette ct hinten an die Zeichenkette s anfügen; liefert s. char *strncat(s,ct,n) höchstens 0 Zeichen von ct hinten an die Zeichenkette s anfügen und s mit '\0' abschließen; liefert s. ;nt strcllp(cs,ct) Zeichenketten es und ct vergleichen; liefert <0 wenn cs<ct, o wenn es= = ci. oder > 0 wenn es > cl. int strncllp(cs,ct,n) höchsteos 0 Zeichen von es mit der Zeichenkette ct vergleichen; liefert <0 wenn es < ct, 0 wenn es==ct, oder >0 wenn es >cl. char *strchr(cs,C) liefert Zeiger auf das erste e in es oder NULL, falls nicht vorhanden. f B.4 Mathemaf e Funktionen: <math.h> 249 char *atrrchr(ca,c) liefert Zeiger auf das letzte e in es, oder NULL, falls nicht vorhanden. aize_t strspn(cs,ct) liefert Anzahl der Zeichen am Anfang von es, die sämtlich in ct vorkommen. a;ze_t atrcspn(cs,ct) liefert Anzahl der Zeichen am Anfang von es, die sämtlich nicht in et vorkommen. char *strpbrk(cs,ct) liefert Zeiger auf die Position in cs, an der irgendein Zeichen aus ct erstmals vorkommt, oder NULL, falls keines vorkommt. char *strstr(cs,ct) liefert Zeiger auf erste Kopie von d in es oder NULL, falls nicht vorhanden. size_t strlen(cs) liefert Länge von cs (ohne '\0'). char *strerror(n) liefert Zeiger auf Zeichenkette, die in der Implementierung für Fehler n defmiert ist. char *strtok(a,ct) strtok durchsucht s nach Zeichenfolgen, die durch Zeichen aus et begrenzt sind; siehe unten. Eine Folge von Aufrufen von strtok(s,ct) zerlegt s in Zeichenfolgen, die jeweils durch ein Zeichen aus ct begrenzt sind. Beim ersten von einer Reihe von Aufrufen ist s nicht NULL. Dieser Aufruf fmdet die erste Zeichenfolge in s, die nicht aus Zeichen in ct besteht; die Folge wird dadurch abgeschlossen, daß das nächste Zeichen in s mit '\0' überschrieben wird; der Aufruf liefert dann einen Zeiger auf die Zeichenfolge. Jeder weitere Aufruf der Reihe ist kenntlich dadurch, daß NULL für s übergeben wird; er liefert die nächste derartige Zeichenfolge, wobei unmittelbar nach dem Ende der vorhergehen- den mit der Suche begonnen wird. strtok liefert NULL, wenn keine weitere Zeichenfolge gefunden wird. Die Zeichenkette ct kann bei jedem Aufruf verschieden sein. Die mem... Funktionen sind zur Manipulatioo von Objekten als Zeichenvektoren gedacht; sie sollen eine Schnittstelle zu effIzienten Routinen sein. In der folgenden Ta- belle haben s und t den 1YP void *; die Argumente cs und ct sind vom 1YP const void *; das Argument 0 hat den 1YP size_t; und e ist ein iot-Wert, der in unsigned char umge- wandelt wird. void *memcPy(s,ct,n) void *memmove(s,ct,n) kopiert 0 Zeichen von ct nach s; liefert s. wie memepy, funktioniert aber auch, wenn die Objekte überlappen. vergleicht die ersten n Zeichen von es mit ct; Resultat analog zu strcmp. liefert Zeiger auf das erste e in es oder NULL, wenn das Zeichen in den ersten n Zeichen nicht vorkommt. setzt die ersten n Zeichen von s auf den Wert e, liefert s. ;nt memcllp(cs,ct,n) vo;d *memchr(cs,c,n) vo;d *memset(s,c,n) B.4 Mathematische Funktionen: < math.h > Die Defmitionsdatei < math.h > vereinbart mathematische Funktionen und Ma- kros. Die Makros EDOM und ERANGE (die man in <errno.h> fmdet), sind von Null verschiedene ganzzahlige Konstanten, mit denen Fehler im Argument- und Resultatbe- 
250 f 'ie Standard-Bibliothek B5 Hilfsfunk: en: <stdlib.h> 251 reich der Funktionen angezeigt werden; HUGE_ VAL ist ein positiver double-Wert. Ein Argumentfeh/er (domain efTOr) liegt vor, wenn ein Argument nicht in dem Bereich liegt, für den eine Funktion definiert ist. Bei einem Argumentfehler erhält errno den Wert EDOM; der Resultatwert hängt von der Implementierung ab. Ein Resultatfeh/er (range efTOr) liegt vor, wenn das Resultat der Funktion nicht als double dargestellt werden kann. Ist das Resultat absolut zu groß, also bei overflow, liefert die Funktion HUGE _VAL mit dem korrekten Vorzeichen und errno erhält den Wert ERANGE. Ist das Resultat zu nahe bei Null, also bei underflow, liefert die Funktion null; je nach Im plementation kann errno den Wert ERANGE erhalten. In der folgenden Tabelle sind x und y vom 1YP double, das Argument n ist ein int- Wert, und alle Funktionen liefern double. Winkel werden bei trigonometrischen Funktio- nen in Radian angegeben. sin(x) Sinus von x cos(x) Kosinus von x tan(x) Tangens von x asin(x) arcsin(x) im Bereich [..,,/2, 11"/2], x E [-1, 1]. acos(x) arccos(x) im Bereich [0, 11"], X E [-1,1]. atan(x) arctan(x) im Bereich [-11"/2,11"/2]. atan2(y,x) arctan(y Ix) im Bereich [-11",11"]. sinh(x) Sinus Hyperbolicus von x cosh(x) Cosinus Hyperbolicus vonx tanh(x) Tangens Hyperbolicus von x exp(x) Exponentialfunktion e X log(x) natürlicher Logarithmus ln(x), x >0. log10(x) Logarithmus zur Basis 10 10glO(x),X>0. pow(X, y) x Y . Ein Argumentfehler liegt vor bei x=O und r-:;O, oder bei x<O und y ist nicht ganzzahlig. sqrt(x) v'i: x. cei lex) kleinster ganzzahliger Wert, der nicht kleiner als x ist, als double. floor(x) größter ganzzahliger Wert, der nicht größer als x ist, als double. fabs(x) absoluter Wert I x I ldexp(x,n) x.2" frexp(x, int *exp) zerlegt x in eine normalisierte Mantisse im Bereich [1/2,1), die als Resultat geliefert wird, und eine Potenz von 2, die in *exp abgelegt wird. Ist x null, sind beide Teile des Resultats null. modf<x, double *ip) zerlegt x in einen ganzzahligen Teil und einen Rest, die beide das gleiche Vorzeichen wie x besitzen. Der ganzzahlige Teil wird bei *ip abgelegt, der Rest ist das Resultat. fmod(x,y) Gleitpunktrest von x/y, mit dem gleichen Vorzeichen wie x. Wenn y null ist, hängt das Resultat von der Implementierung ab. B.S Hilfsfunktionen: < stdlib.h > Die Defmitionsdatei < stdlib.h > vereinbart Funktionen zur Umwandlung von Zah- len. für Speicherverwaltung und ähnliche Aufgaben. double atof(const char *s) ator wandelt s in double um; die Funktion ist äquivalent zu strtod(s, (char**) NULL) int atoi(const char *s) atol wandelt s in int um; die Funktion ist äquivalent zu (int)strtol(s, (char**) NULL, 10) long atol(const char *s) atol wandelt s in long um; die Funktion ist äquivalent zu atrtol(a, (char**) NULL, 10) double strtod(const char *s, char **endp) strtod wandelt den Anfang der Zeichenkette 5 in double um, dabei wird Zwischen- raum am Anfang ignoriert. Die Funktion speichert einen Zeiger auf einen nicht umge- wandelten Rest der Zeichenkette bei *endp, falls endp nicht NULL ist. Falls das Ergebnis zu groß ist, (also bei overflow), wird als Resultat HUGE_VAL mit dem korrekten Vorzei- chen geliefert; liegt das Ergebnis zu dicht bei Null (also bei underflow), wird Null gelie- fert. In beiden Fällen erhält errno den Wert ERANGE. long strtol(const char *s, char **endp, int base) strtol wandelt den Anfang der Zeichenkette s in long um, dabei wird Zwischen- raum am Anfang ignoriert. Die Funktion speichert einen Zeiger auf einen nicht umge- wandelten Rest der Zeichenkette bei *endp, falls endp nicht NULL ist. Hat base einen Wert zwischen 2 und 36, erfolgt die Umwandlung unter der Annahme, daß die Eingabe in dieser Basis repräsentiert ist. Hat base den Wert Null, wird als Basis 8, 10 oder 16 ver- wendet; eine führende Null bedeutet dabei oktal und Ox oder OX zeigen eine hexadezima- le Zahl an. In jedem Fall stehen Buchstaben für die Ziffern von 10 bis base-1; bei Basis 16 darf Ox oder OX am Anfang stehen. Wenn das Resultat zu groß werden würde, wird je nach Vorzeichen LONG_MAX oder LONG_MIN geliefert und errno erhält den Wert ERANGE. unsigned long strtoul(const char *s, char **endp, int base) strtoul funktioniert wie strtol, nur ist der Resultattyp unsigned long und der Feh- lerwert ist ULONG MAX. int rand(void) rand liefert eine ganzzahlige Pseudo-Zufallszahl im Bereich von 0 bis RAND_MAX; dieser Wert ist mindestens 32767. void srand(unsigned int seed) srand benutzt seed als Ausgangswert für eine neue Folge von Pseudo-Zufallszah- len. Der erste Ausgangswert ist 1. 
252 B T Standard-Bibliothek B.6 Fehlersucl < assert.h > 253 void *calloc(size_t nobj, size_t size) calloc liefert einen Zeiger auf einen Speicherbereich für einen Vektor von nobj Objekten, jedes mit der Größe size, oder NULL, wenn die Anforderung nicht erfüllt wer- den kann. Der Bereich wird mit Null-Bytes initialisiert. void *malloc(size_t size) malloc liefert einen Zeiger auf einen Speicherbereich für ein Objekt der Größe size oder NULL, wenn die Anforderung nicht erfüllt werden kann. Der Bereich ist nicht initialisiert. void *realloc(void *p, size_t size) realloc ändert die Größe des Objekts, auf das p zeigt, in size ab. Bis zur kleineren der alten und neuen Größe bleibt der Inhalt unverändert. Wird der Bereich für das Ob- jekt größer, so ist der zusätzliche Bereich uninitialisiert. realloc liefert einen Zeiger auf den neuen Bereich oder NULL, wenn die Anforderung nicht erfüllt werden kann; in die- sem Fall ist .p unverändert. void free(void *p) rree gibt den Bereich frei, auf den p zeigt; die Funktion hat keinen Effekt, wenn p den Wert NULL hat. p muß auf einen Bereich zeigen, der zuvor mit calloc, malloc oder realloc angelegt wurde. void abort(void) abort sorgt für eine anormale Beendigung des Programms im Stil von raise(SIGABRT) . void exit(int status) exit beendet das Programm normal. atexit-Funktionen werden in umgekehrter Reihenfolge ihrer Hinterlegung aufgerufen, die Puffer offener Dateien werden geschrie- ben, offene Ströme werden abgeschlossen, und die Kontrolle geht an die Umgebung des Programms zurück. Wie status an die Umgebung des Programms geliefert wird, hängt von der Implementierung ab, aber Null gilt als erfolgreiches Ende. Die Werte EXIT_SUCCESS und EXITJAlLURE können ebenfalls angegeben werden. int atexit(void (*fcn)(void» atexit hinterlegt die Funktion rco, damit sie aufgerufen wird, wenn das Programm normal endet, und liefert einen von Null verschiedenen Wert, wenn die Funktion nicht hinterlegt werden kann. int system(const char *s) system liefert die Zeichenkette s an die Umgebung zur Ausführung. Hat s den Wert NULL, so liefert system einen von Null verschiedenen Wert, wenn es einen Kom- mandoprozessor gibt. Wenn s von NULL verschieden ist, dann ist der Resultatwert im- plementierungsabhängig. char *getenv(const char *name) geteov liefert die zu name gehörende Zeichenkette aus der Umgebung oder NULL, wenn keine Zeichenkette existiert. Die Details hängen von der Implementierung ab. void *bsearch(const void *key. const void *base, size_t n, size_t size, int (*cmp)(const void *keyval, const void *datum» bsearch durchsucht base[0]...base[n-1] nach einem Eintrag, der gleich .key ist. Die Funktion cmp muß einen negativen Wert liefern, wenn ihr erstes Argument (der Suchschlüssel) kleiner als ihr zweites Argument (ein Tabelleneintrag) ist, Null, wenn bei- de gleich sind, und sonst einen positiven Wert. Die Elemente des Vektors base müssen aufsteigend sortiert sein. bsearch liefert einen Zeiger auf das gefundene Element oder NULL, wenn keines existiert. void qsort(void *base, size_t n, size_t size. int (*cmp)(const void *. const void *» qsort sortiert einen Vektor base [0] ...base[n.1] von Objekten der Größe size in aufsteigender Reihenfolge. Für die Vergleichsfunktion cmp gilt das gleiche wie bei bsearch. int abs(int n) abs liefert den absoluten Wert seines int Arguments. long labs(long n) labs liefert den absoluten Wert seines long Arguments. div_t div(int num. int denom) dJv berechnet Quotient und Rest von oum/denom. Die Resultate werden in den iot Komponenten quot und rem einer Struktur vom Typ div _tabgelegt. ldiv_t ldiv(long num, long denom) dJv berechnet Quotient und Rest von num/denom. Die Resultate werden in den loog Komponenten quot und rem einer Struktur vom Typ Idiv _t abgelegt. B.6 Fehlersuche: < assert.h > Mit dem assert-Makro fügt man Testpunkte zu Programmen hinzu: void assert(int ssion) Hat expression den Wert Null wenn assert (ssion ) ausgeführt wird, dann gibt der assert-Makro auf stderr etwa folgende Meldung aus: Aasertion failed: ssion. file filename. line nnn Anschließend wird die Ausführung durch Aufruf von abort abgebrochen. Der Dateina- me der Programmquelle sowie die Zeilennummer stammen von den Preprozessor-Ma- kros FILE und UNE . -- -- Wenn beim Einfügen von < assert.h > ein Makroname NDEBUG definiert ist, wird der assert-Makro ignoriert. B.7 Variable Argumentlisten: < stdarg.h > Die Defmitionsdatei < stdarg,h > bietet die Möglichkeit, eine Liste von Funktions- argumenten abzuarbeiten, deren Länge und Datentypen nicht bekannt sind. 
254 ,)ie Standard-Bibliothek Angenommen, lastarg ist der letzte benannte Parameter einer Funktion r, die mit einer variablen Anzahl von Argumenten aufgerufen wird. Dann vereinbart man in reine Variable ap vom Datentyp va_list, die der Reihe nach auf jedes Argument zeigen wird: va_list ap; ap muß einmal mit dem Makro va start initialisiert werden, bevor auf die unbenannten Argumente zugegriffen wird: - va_start(va_liat ap, lastar,g); Daran anschließend liefert jede Ausführung des Makros va arg einen Wert, der den Da- tentyp und den Wert des nächsten unbenannten Argumen-ts besitzt. Außerdem ändert der Makro den Wert von ap so, daß der nächste Aufruf von va arg das nächste Argu- ment liefert: - e va_arg(va_list ap, pe); Der Makro void va_end(va_list ap); muß einmal aufgerufen werden, nachdem die Argumente bearbeitet wurden, aber vor Schluß von r. B.8 Globale Sprünge: < setjmp.h > Mit den Vereinbarungen in < setjmp.h > kann man die normale Folge von Funkti- onsaufruf und -ende umgehen. Typischerweise erlaubt das einen direkten Rücksprung von einem tief verschachtelten Funktionsaufruf. int setjmp(jmp_buf env) Der Makro setjmp speichert Zustandsinformation in env zur Auswertung durch longjmp. Der Resultatwert ist Null, wenn setjmp direkt aufgerufen wird, und von Null verschieden bei einem späteren Aufruf von longjmp. Der Makro setjmp darf nur in be- stimmten Kontexten aufgerufen werden, im wesentlichen innerhalb der Bedingung bei ir, switch und Schleifen, und nur innerhalb von einfachen Vergleichen. if (setjmp(env) s. 0) /* hierher beim direkten Aufruf */ else /* hierher durch Aufruf von longjmp */ void longjmp(jmp_buf env, int val) longjmp stellt den Zustand wieder her, der beim letzten Aufruf von setjmp gespei- chert wurde. Dazu wird die Information in env verwendet. Die Ausführung geht so wei- ter, als ob die Funktion setjmp gerade ausgeführt wurde und den von Null verschiedenen Resultatwert val geliefert hat. Die Funktion, von der aus setjmp aufgerufen wurde, darf noch nicht beendet worden sein. Die erreichbaren Objekte haben dann die Werte, die sie beim Aufruf von longjmp hatten. In der Funktion, von der setjmp aufgerufen wurde, sind jedoch die Werte der automatischen Variablen undefmiert, die ohne das Attribut volatile defmiert wurden und die seit dem Aufruf von setjmp verändert wurden. B.9 Signale: < signal.h > Die Defmitionsdatei < signal.h > stellt Hilfsmittel zur Behandlung von Ausnahme- bedingungen zur Verfügung, die während der Ausführung eines Programms eintreten ... I B.1O Funktione .r Datum und Uhrzeit: <time.h> 255 können, wie zum Beispiel ein "Interrupt-Signal" von einer externen Quelle oder ein Feh- ler während der Ausführung. void (*signal(int sig, void (*handler)(int»)(int) signal legt fest, wie nachfolgende Signale handelt eren. Wir SIG ßFL als handler angegeben, so wird nach einer implemntIeunbhangtgen Voretellg ver- fahren' wird SIG IGN angegeben, so wird das Slgnallgnonert; andernfalls Wlfd die unk- tion agerufen, äuf die handler verweist. Die Funktion erhält als Argument den SIgnal- typ. Unter anderem gelten folgende Signale SIGABRT anomaler Abbruch, zum Beispiel durch abort SIGFPE Arithmetikfehler, zum Beispiel Division durch Null oder Overflow SIGILL illegaler Funktionstext, zum Beispiel illegaler Maschinenbefehl SI GI NT Dialogsignal, zum Beispiel interrupt . SIGSEGV illegaler Speicherzugriff, zum Beispiel außerhalb von Speichergrenzen SIGTERM Aufforderung an das Programm, sich zu beenden signal liefert den vorhergehenden Wert von handler für das angegebene Signal, oder SIG ERR, wenn ein Fehler passiert. - Trifft später ein Signal sig ein, wird für dieses Signal wieder die. Behdlung nach Voreinstellung festgelegt; anschließend wird die vereinbarte Funktion  de Form (*handler)(sig) aufgerufen. Wenn dieser Funktionsaufruf zu Ende geht, Wlfd die Aus- führung an dem Punkt fortgesetzt, an dem das Signal eintraf. Der anfängliche Zustand der Signale ist implementierungsabhängig. int raise(int sig) raise sendet das Signal sig an das Programm und liefert einen von Null verschiede- nen Wert bei Fehler. B.10 Funktionen rur Datum und Uhrzeit: < time.h > Die Defmitionsdatei < tlme.h > vereinbart Typen und Funktionen zum Umgang mit Datum und Uhrzeit. Manche Funktionen verarbeiten die Ortszeit, die von de KaIeder- zeit zum Beispiel wegen einer Zeitzone abweicht. dock _ t und time _ t sind arthmet1Sche Typen, die Zeiten repräsentieren, und struct tm enthält die Komponenten emer KaIen- derzeit: int tm_sec; Sekunden nach der vollen Minute (0, 61)- int tm_min; Minuten nach der vollen Stunde (0,59) int tm_hour; Stunden seit Mitternacht (0, 23) int tm_mday; Tage im Monat (1,31) int tm_mon; Monate seit Januar (0, 11) int tm-year; Jahre seit 1900 int tm_wday; Tage seit Sonntag (0, 6) int tm_yday; Tage seit dem 1. Januar (0,365) int tm isdst' Kennzeichen für Sommerzeit tm Isdst ist positiv, w:nn Smmerzeit gilt, Null, wenn Sommerzeit nicht gilt, und negativ, wenn die Information nicht zur Verfügung steht. . Die zusätzlich möglichen Sekunden sind Schaltsckunden. 
256 B r }'tandard-Bibliothek B.U Grenzwerte e' Implementierung: <limits.h> und <float.h> 257 Xc lokale Darstellung von Datum und Zeit. XcI Tag im Monat(01-31). XII Stunde (00-23). XI Stunde (01-12). Xj Tag im Jahr (001-366). XIII Monat (01-12). »t Minute (00-59). Xp lokales Äquivalent von AM oder PM. XS Sekunde (00-61). ».J Woche im Jahr (Sonntag ist erster Wochentag) (00-53). XN Wochentag (0-6, Sonntag ist 0). XW Woche im Jahr (Montag ist erster Wochentag) (00-53).  lokale Darstellung des Datums. XX lokale Darstellung der Zeit. Xy Jahr ohne Jahrhundert (00-99). XV Jahr mit Jahrhundert. Xl Name der Zeitzone, falls existent. XX %. R.11 Grenzwerte einer Implementierung: < limits.h > und < float.h > Die Dermitionsdatei < limits.h > definiert Konstanten für den Wertumfang der g a n77J1 hlig en Typen. Die nachfolgenden. Werte sind zuelassene minimale Größen; größere Werte können in einer Implementierung benutzt sem. CHAR_BIT 8 Bits in einem ehar CHAR_HAX UCHAR_HAX oder SCHAR_HAX CHAR_MIM 0  SCHAR_MIM IMT_HAX +32767 IMT_MIM -32767 lONG_HAX +2147483647 lONG_MIM -2147483647 SCHAR_HAX +127 SCHAR_MIM -127 SHRT_HAX +32767 SHRT_MIM -32767 UCHAR_HAX 255 UIMT_HAX 65535 UlONG_HAX 4294967295 USHRT_HAX 65535 maximaler Wert für unsigned short Die Namen in der nächsten Tabelle, einer Teilmenge von < Ooat.h >, sind Konstan- ten, die sich auf Gleitpunktarithmetik beziehen, Wenn ein Wert angege?en ist, hanelt es sich um ein Minimum für die entsprechende Größe, Jede Implementierung definiert entsprechende Werte. clock_t clock(void) dock liefert die Rechnerkern-Zeit, die das Programm seit Beginn seiner Aus- führung verbraucht hat, oder -1, wenn diese Information nicht zur Verfügung steht. dockO/CLOCKS_PER_SEC ist eine Zeit in Sekunden. time_t time(time_t *tp) time liefert die aktuelle Kalenderzeit oder -I, wenn diese nicht zur Verfügung steht. Wenn tp von NULL verschieden ist, wird der Resultatwert auch bei *tp abgelegt. double difftime(time_t time2, time_t timel) diD'time liefert time2 - time! ausgedrückt in Sekunden. time_t mktime(struct tm *tp) mktime wandelt die Ortszeit in der Struktur *tp in Kalenderzeit um, die so darge- stellt wird wie bei time. Die Komponenten erhalten Werte in den angegebenen Berei- chen. mktime liefert die Kalenderzeit oder -I, wenn sie nicht dargestellt werden kann. Die folgenden vier Funktionen liefern Zeiger auf statische Objekte, die von ande- ren Aufrufen überschrieben werden können. char *asctime(const struct tm *tp) asctime konstruiert aus der Zeit in der Struktur *tp eine Zeichenkette der Form Sun Jan 3 15:14:13 1988\n\0 char *ctime(const time_t *tp) ctime verwandelt die Kalenderzeit *tp in Ortszeit; dies ist äquivalent zu asctime<localtime<tp» struct tm *gmtime(const time_t *tp) gmtime verwandelt die Kalenderzeit *tp in Coordinated Universal Time (lITc). Die Funktion liefert NULL, wenn lITC nicht zur Verfügung steht. Der Name gmtime hat hi- storische Bedeutung. struct tm *localtime(const time_t *tp) localtime verwandelt die Kalenderzeit *tp in Ortszeit. size_t strftime(char *s, size_t smax, const char *fmt, const struct tm *tp) strnime formatiert Datum und Zeit aus *tp in s unter Kontrolle von fmt, analog zu einem printr-Format. Gewöhnliche Zeichen (insbesondere auch das abschließende '\0') werden nach s kopiert. Jedes o/c:c wird so wie unten beschrieben ersetzt, wobei Werte verwendet werden, die der lokalen Umgebung entsprechen. Höchstens smax Zeichen werden in s abgelegt. strftime liefert die Anzahl der resultierenden Zeichen, mit Aus- nahme von '\0'. Wenn mehr als smax Zeichen erzeugt wurden, liefert strftime den Wert Null. Xe abgekürzter Name des Wochentags. XA voller Name des Wochentags. Xb abgekürzter Name des Monats. XII voller Name des Monats. maximaler Wert für ehar minimaler Wert für ehar maximaler Wert für Int minimaler Wert für int maximaler Wert für long minimaler Wert für long maximaler Wert für signed ehar minimaler Wert für signed ehar maximaler Wert für short minimaler Wert für short maximaler Wert für unsigned char maximaler Wert für unsigned int maximaler Wert für unsigned long 
258 B D:andard-Bibliothek 259 FLT_RADIX FLT_ROUNDS FLT_DIG FL T _EPSI LON FLT_MANT_DIG FLT_MAX FLT_MAX_EXP FLT_HIN FLT_HIN_EXP DBL_DIG DBL_EPSILON DBL_MANT_DIG DBL_MAX DBL_MAX_EXP DBL_HIN DBL_HIN_EXP 2 Basis der Exponentendarstellung, zum Beispiel 2, 16 Art der Rundung bei Gleitpunktaddition 6 Genauigkeit in Dezimalziffern (für Ooat) 1E-S kleinster Wert x für den 1.0 + x  1.0 gilt Länge der Mantisse in Basis-Ziffern 1E+37 maximaler Gleitpunktwert maximales n, für das FLT_RADIXn-l darstellbar ist 1E-37 minimaler, normalisierter Gleitpunktwert minimales n, für das HY' normalisiert werden kann 10 Genauigkeit in Dezimalziffern (für double) 1E-9 kleinster Wert x für den 1.0 + x  1.0 gilt Länge der Mantisse in Basis-Ziffern 1E+37 maximaler Gleitpunktwert maximales n, für das FLT RADJX n -1 darstellbar ist 1E-37 minimaler, normalisierte double Gleitpunktwert minimales n, für das 1<Y' normalisiert werden kann c Änderungen in Kürze Seit die Erste Ausgabe dieses Buches erschien, hat sich die DefInition der Sprache C geändert. Die meisten Änderungen erweiterten nur die ursprüngliche Sprache und sie waren sorgfältig so entworfen, daß sie sich mit dem existierenden Sprachgebrauch vertru- gen. Manche reparierten Mehrdeutigkeiten in der ursprünglichen Beschreibung und ei- nige sind auch ModifIkationen, die den existierenden Sprachgebrauch abändern. Viele der neuen Eigenschaften wurden in den Dokumenten angekündigt, die mit den Überset - zern von AT&T geliefert wurden, und sie wurden später von anderen Herstellern von C- Übersetzern übernommen. In neuerer Zeit hat die ANSI-Kommission, die einen Stan- dard für C entwickelt, die meisten Änderungen übernommen, und sie hat auch andere wesentliche ModifIkationen eingeführt. Ihr Bericht wurde teilweise schon von einigen kommerziellen Übersetzern berücksichtigt, noch bevor der C-Standard formal erschien. In diesem Anhang werden die Unterschiede kurz beschrieben zwischen der Spra- che, die in der Ersten Ausgabe defIniert wurde, und der, die sicherlich der endgültige Standard defIniert. Der Anhang behandelt nur die Sprache selbst, nicht ihre Umgebung und Bibliothek; letztere sind zwar ein wichtiger Bestandteil des Standards, aber man kann sie mit wenig vergleichen, denn die Erste Ausgabe unternahm keinen Versuch, eine Umgebung oder eine Bibliothek vorzuschreiben. . Der Preprozessor wurde im Standard sorgfältiger definiert als in der Ersten Ausgabe, und er wurde erweitert: er beruht explizit auf Eingabesymbolen (tokens); es gibt neue Operatoren zur Verkettung von Symbolen (##) und zur Erzeugung von Zei- chenketten (#); es gibt neue Preprozessor-Anweisungen wie #eUf und #pragma; die wiederholte Definition eines Makros mit der gleichen Folge von Symbolen ist aus- drücklich erlaubt; Parameter werden innerhalb von Zeichenketten nicht mehr ersetzt. Zeilen können jetzt überall mit \ aneinandergehängt werden, nicht nur in DefInitio- nen von Zeichenketten und Makros. Siehe  A.12. . Alle internen Namen sind jetzt wenigstens auf 31 Zeichen signifIkant; minimal werden für extern sichtbaren Namen immer noch nur sechs signifikante Buchstaben (entwe- der alle groß oder alle klein) gefordert. (Viele Implementierungen erlauben mehr.) . Drei-Zeichen-Folgen (trigraphs), die mit ?? beginnen, erlauben die Darstellung von Zeichen, die in manchen Zeichensätzen fehlen. Für #\"[]{}I- sind.Ersatzdarstel- lungen definiert, siehe A.12.1. Man beachte, daß durch die Drei-Zeichen-Folgen die Bedeutung von Zeichenketten verändert werden kann, die ?? enthalten. . Neue reservierte Worte (void, const, volatile, signed, enum) wurden eingeführt. Das totgeborene Wort entry ist nicht mehr reserviert. . Neue Ersatzdarstellungen wurden definiert, die in Zeichenkonstanten und konstanten Zeichenketten verwendet werden. Wenn nach \ ein Zeichen folgt, das nicht Teil einer erlaubten Ersatzdarstellung ist, ist der Effekt undeflniert. Siehe  A.2.5.2. . Die allen liebste triviale Änderung: 8 und 9 sind keine oktalen Ziffern. 
260 -, Änderungen in Kürze C Änderungen in }<' :e 261 · Der Standard führt eine größere Anzahl von SuffIXen ein, mit denen der Typ von Kon- stanten explizit festgelegt wird: U oder L für g an77"hlig e Werte, F oder L für Gleit- punktwerte. Auch die Regeln zur Bestimmung des Typs von Konstanten ohne solche Suffixe sind verfeinert worden (A.25). · Aufeinanderfolgende konstante Zeichenketten werden aneinandergehängt. · Es gibt eine Schreibweise für konstante Zeichenketten und Zeichenkonstanten mit ei- nem erweiterten Zeichensatz; siehe  A.2.6. · Man kann explizit vereinbaren, ob Zeichen, wie auch andere Typen, ein Vorzeichen haben oder nicht. Dazu dienen die Schlüsselworte slgned oder unslgned. Die Um- schreibung long noat als Synonym für double existiert nicht mehr, aber long double kann verwendet werden, um Gleitpunktwerte mit zusätzlicher Genauigkeit zu verein- baren. · Der Typ unslgned char war schon seit einiger Zeit verfügbar. Der Standard führt das Wort slgned ein, um die Existenz eines Vorzeichens bei char und anderen Integer- Objekten explizit auszudrücken. · Den Datentyp vold gab es in den meisten Implementierungen schon seit einigen Jah- ren. Der Standard führt vold * als allgemeinen Zeigertyp ein; diese Rolle spielte früher cbar *. Gleichzeitig wurden explizite Regeln festgelegt, die sich gegen ein Mi- schen von Zeigern und Integer-Werten richten oder Mischen von Zeigern, die auf ver- schiedene 'JYpen zeigen, ohne daß explizite Umwandlungsoperationen verwendet wer- den. · Der Standard schreibt minimale Bereichsgrenzen für die arithmetischen 'JYpen expli- zit vor, und fordert DefInitionsdateien ( < limlts.b > und < noat.b > ), die die Charakte- ristika jeder speziellen Implementierung angeben. · Seit der Ersten Ausgabe wurden Aufzählungen neu eingeführt.. · Von C+ + übernimmt der Standard die Idee eines Attributs für Typen (type qua/ifier), zum Beispiel const (A.8.2). · Zeichenketten können nicht mehr modiftziert werden und können deshalb im schreib- geschützten Speicher angelegt werden. · Die ..üblichen arithmetischen Umwandlungen" wurden geändert, im wesentlichen von ..bei ganzzahligen Werten gewinnt immer unslgned; bei Gleitpunkt wird immer double verwendet" in "wandle in den kleinsten Typ um, der genügend Fassungsver- mögen hat." Siehe A.6.5. · Die alten Zuweisungsoperatoren wie = + sind wirklich hinüber. Zuweisungsoperato- ren sind jetzt auch einzelne Symbole; in der Ersten Ausgabe waren sie aus Symbol- paaren zusammengesetzt, die durch Zwischenraum getrennt sein konnten. · Der Freibrief für einen Übersetzer, mathematisch assoziative Operatoren auch für Computer als assoziativ behandeln zu dürfen, gilt nicht mehr. · Ein unärer Operator + wurde aus SymmetriegrÜDden zu - eingeführt. · Ein Zeiger auf eine Funktion kann zum Aufruf einer Funktion verwendet werden, oh- ne daß der Inhaltsoperator * explizit angegeben werden muß. Siehe A.7.3.2. · Strukturen können zugewiesen, als Argumente an Funktionen übergeben und als Re- sultat von Funktionen geliefert werden. · Der Adreß-Operator & darf auf Vektoren angewendet werden und liefert dann einen Zeiger auf den Vektor. · In der Ersten Ausgabe lieferte der Operator slzeor ein Resultat vom Typ Int; später sind viele Implementierungen zu unsigned übergegangen. Der Standard macht den Resultattyp explizit implementierungsabhängig, verlangt aber, daß dieser Typ, nämlich slze t, in einer Standard-Deflnitionsdatei « stddef.b » vereinbart wird. Eine ähnli- che -Änderung erfolgt für den Typ der Differenz zweier Zeiger (ptrdUf t). Siehe A.7.4.8 und A.7.7. · Der Adreß-Operator & darf nicht auf ein Objekt angewendet werden, das als register vereinbart wurde, auch dann nicht, wenn die Implementierung das Objekt gar nicht in einem Register ablegt. · Der Resultattyp bei den Shift-Operatoren « und » ist der Typ des linken Operan- den; der rechte Operand kann das Resultat nicht erweitern. Siehe  A.7.8. · Der Standard erlaubt, daß ein Zeigerwert direkt hinter das Ende eines Vektors er- zeugt wird, und erlaubt dafür Arithmetik und Vergleiche; siehe A.7.7. · Der Standard führt (angelehnt an C+ +) die Idee der Deklaration eines Funktions- prototyps ein, der die Typen der Parameter enthält. Dabei werden auch Funktionen mit variablen Argumentlisten explizit erkannt, und es gibt eine offtzielle Technik, sie zu verarbeiten. Siehe  A.7.3.2, A.8.6.3 und B.7. Mit Einschränkungen ist der alte Stil noch erlaubt. · Leere Deklarationen, die keine Deklaratoren enthalten und nicht wenigstens eine Struktur, eine Union oder Aufzählung vereinbaren, werden im Standard verboten. Andrerseits vereinbart eine Deklaration, die nur den Namen einer Struktur oder Union enthält, diesen Namen auch dann neu, wenn der Name in einem äußeren Gül- tigkeitsbereich schon vereinbart war. · Externe Datendeklarationen ohne Typ oder Attribut (die also nur aus Deklaratoren bestehen) sind verboten. · Manche Implementierungen, wenn konfrontiert mit einer extern Deklaration in einem inneren Block, haben diese Deklaration in den Rest der Datei exportiert. Der Stan- dard besteht darauf, daß der Gültigkeitsbereich einer solchen Deklaration nur der Block selbst ist. · Parameternamen haben als Gültigkeitsbereich den Funktionsblock, damit Vereinba- rungen im äußersten Block der Funktion die Parameter nicht mehr verbergen können. · Die Namensräume sind leicht abgeändert. Der Standard faßt alle Etiketten (tags) in einem einzigen Namensraum zusammen und führt außerdem einen eigenen Namens- raum für Marken ein; siehe A.l1.1. Außerdem werden Komponentennamen mit der Struktur oder Union verknüpft, in der sie definiert sind. (Dies war schon seit einiger Zeit gebräuchlich.) . Sie wurden in der ersten deutschen Ausgabe bereits berücbichtigt. Ad.Ü. 
262 , Änderungen in Kürze . Eine Union darf initialisiert werden; die Initialisierung bezieht sich auf die erste Al- ternative, . Auch im automatischen Speicherbereich dürfen Strukturen, Unionen und Vektoren initialisiert werden, allerdings mit Einschränkungen. . Zeichenvektoren, die explizit dimensioniert sind, dürfen mit einer konstanten Zei- chenkette initialisiert werden, die genau so viele Zeichen hat (\0 wird dann still- schweigend unterdrückt). . Der Auswahlausdruck und die case-Marken bei einer switch-Anweisung können einen beliebigen Integer-iyp besitzen. r 263 Sachverzeichnis I Negation 42, 198 I = ungleich 16, 41, 201 .. Doppelanführungszeichen 7, 19, 38, 186 tI Zeichenkette, Preprozessor 88, 228 tItI verketten. Preprozessor 88, 228 f#define 14,87, '227 tlelif,tlelse 88,230 tlendif 88 tlerror 231 tlif 88,130,230 tlifelef 89,231 tli fndef 89, 231 tlinclude 32,86,146,229 tll ine 231 tlpragma 231 tlundef 88, 165, '227 X Rest nach Division 4Of, 199 Xld bei prlntf, Umwandlung 18 & Adreßoperator ix, 91, 197 & UND, Bit-Operator 48,201 && UND 21,41,48,202 · Anführungszeichen 19, 37f, 185 () Klammern 194f * Inhaltsoperator ix, 91, 197 * Multiplikation 40, 199 + Addition 40, 199 + unäres Plus 197 ++ Inkrement 18, 46, 102f, 196f += Zuweisung 49 . Komma-Operator 62, 203 - Subtraktion 40, 199f - unäres Minus 198 -- Dekrement 18, 46, l02f, 196f -> Strukturverweis 127, 194 . Strukturauswahl 124, 194 . .. Deklaration 149, 196 I Division 10, 4Of, 199 ; 9, 15, 18, 55f < kleiner 41, 200 « links-shift, Bit-Operator 48,200 <= kleiner-gleich 41, 200 = Zuweisung 16,41,203 == gleich 19, 41, 201 > größer 41, 200 >= größer-gleich 41,200 » rechts-shift, Bit-Operator 48,200 1: bedingter Ausdruck 50f, 202 [] Klammern -+ Vektor 22, 194 \ Gegenschrägstrich 8, 38 A exklusiv-ODER, Bit-Operator 48,201 _ Unterstrich, Buchstabe 35, 184, 239 {} Klammern 7,10,55,82 I inklusiV-ODER, Bit-Operator 48, 201 11 ODER 21,41,48,202 - Eins-Komplement, Bit-Operator 48, 198 0... oktale Konstante 37,184 Ox... hexadezimale Konstante 37,184 \0 Nullzeichen 29, 38, 185 A \a Klingelzeichen 38, 185 Abbruch, Programm 156f abgeleitete Typen 1, 9, 189 abhängig -+ Portabilität abort Bibliotheksfunktion 252 abs Bibliotheksfunktion 253 Abschluß, Anweisung 9, 55 Abschneiden bei Division 10, 4Of, 199 Abschneiden von Gleitpunkt 44, 190 abstrakter Deklarator 217 acos Bibliotheksfunktion 250 Addition + 40, 199 addpoint Funktion 126 addtree Funktion 136 Adreßoperator & ix, 91, 197 Adresse einer Variablen 27,91,197 und register 205 afree Funktion 99 Aktualparameter ix, 25 alert -+ Klingelzeichen alloc Funktion 98 
264 Sachverzeichnis alphabetische Sortierung 114 alter Stil, Funktion 26, 32, 72, 195 Alternative ix, 142, 11)7 American National Standards Institute, ANS! v, ix, 2, 183 anfügen an Datei 154, 168, 240 Anführungszeichen I 19, 37r, 185 Anweisung 218ff Abschluß 9, 55 Ausdruck 55r,219 Auswahl 211) Block 55,82, 219, 222r break 59, 63r, 221 continue 64,221 do-whi le 62r, 221 for 13,18,59,221 goto 64r, 221 if-else 19,21,55,211) leer 18, 219 Marke 65, 219 return 25, 29, 69, 72, 222 sequentielle Ausführung 218 Sprung 221 swi tch 58, 74r, 211) while 10,59,221 Wiederholung 211)r zusammengesetzt 55, 82, 219, 222r Zuweisung, geschachtelt 17, 11), 50 a.out 6,70 Äquivalenz, Typen 218 Äquivalenzvergleich, Operatoren 41, 11)1 Argument ix, 25, 195 Anzahl, argc 110 #def i ne 87 Erweiterung 45, 195 Funktion 25, 195 Kommandozeile 1l0ff Makro 87 Teilvektor 97 Umwandlung 45 \'ektor,argv 110,157 \'ektorname 27,97,108 Zeiger 97 Argumentliste variabel 149, 167, 196, 213, 223, 253r void 32,72,213,223 Arithmetik Operatoren 40 Typen 188 übliche Umwandlung 42, 190r Zeiger 92, 96ff, 113, 132r, 199r Zeiger, illegale 99r, 133, 199r Zeiger, Skalierung 100, 191 a"ay -+ Vektor ASO! Zeichensatz 19,37,43,227,248 asct ime Bibliotheksfunktion 256 asin Bibliotheksfunktion 250 asm reserviertes Wort 184 <assert.h> 253 Assoziativität von Operatoren 51f, 193 atan, atanZ Bibliotheksfunktionen 161, 250 atex i t Bibliotheksfunktion 252 atof Bibliotheksfunktion 251 Funktion 70r atoi Bibliotheksfunktion 251 Funktion 42, 61, 72 atol Bibliotheksfunktion 251 Attribut eines Typs ix, 11)2, 206 const 40, 189, 206 volatile 189,206 Aufruf einer Funktion 195 Aufzählungstyp, enun 39, 188r, 210 Etikett 210 Konstante 39, 88, 184, 186, 210 Ausdruck 193ff Anweisung 55r, 219 bedingt, 1: 50 r, 11)2 geklammert 194 konstant 38, 58, 88, 204 logischer Wert 43 Reihenfolge der Bewertung 52, 193 Wert eines Vergleichs 42r Zuweisung 17,11),50,11)3 Ausführung, sequentiell 218 Ausgabe 146, 155, 163 Bildschirm 15,146, 157, 163 Um lenkung 146 Ausnahmen 193, 255 Ausrichtung 133,137,141,161,178,192 Bit-Feld 144,208r mit union 178 auto Speicherklasse 11)5 ,. Sachverzeichnis 265 automatische Variable 30, 73, 187 Gültigkeitsbereich 78, 225 lnitialisierung 30, 40, 83, 215 B B 1 \b backs pace 7, 38, 185 backslash -+ Gegenschrägstrich \ 8, 38 basic type -+ elementarer Datentyp Baum, binär 134 BCPL 1 bedingte Übersetzung 88, 230 bedingter Ausdruck, 1: 50r,11)2 Beispiel-+ Funktionsbeispiel, Programm Bewertungsreihenfolge 21, 48, 52r, 62, 75, 87,92,193 Bibliotheksfunktion 7,67, 78 abort 252 abs 253 acos 250 asctime 256 asin 250 atan, atanZ 250 atex i t 252 atof, atoi, atol 251 bsearch 253 calloc 161,252 cei I 250 clearerr 247 clock 256 cos 161, 250 cosh 250 ctime 256 di fftime 256 div 253 exi t 157,252 exp 161,250 fabs 161, 250 fclose 156,240 feof, ferror 158,247 ff I ush 240 fgetc 244 fgetpos 247 fgets 158, 244 floor 250 fmod 250 Bibliotheksfunktion +-- fopen 154, 240 fprintf 155,241f fputc 244 fputs 158, 244 fread 246 free 161,252 f reopen 156, 240 frexp 250 fscanf 155, 243 fseek 246 fsetpos 247 ftell 246 fwrite 246 getc 155, 245 getchar 15, 145, 155, 245 getenv 252 gets 158, 245 gmt i me 256 isalnun 131,160,247 isalpha 131, 160,247 iscntrl 247 isdigit 160,247 i sgraph 248 islower 160,248 isprint 248 i spunct 248 isspece 131, 160,248 i supper 160, 248 isxdigit 248 labs 253 Idexp 250 Idiv 253 localtime 256 'log, log10 161,250 longj 254 malloc 137, 160,252 memchr,memc,memcpy,memmove,memset 249 mktime 256 modf 250 perror 247 pow 24,161,250 printf 7,10,18, 147r, 242 putc 155, 246 putchar 15,146,155,246 
266 Sachverzeichnis Bibliotheksfunktion +- puts 158, 246 qsort 253 raise 255 rand 161, 251 realloc 252 remove 240 rename 240 rewind247 scanf 94, 1501f, 244 setbuf, setvbuf 241 setj 254 signal 255 sin 161,250 sinh 250 sprintf 149,242 sqrt 161,250 srand 162, 251 sscanf 244 strcat, strchr, str, strcpy 159,248 strcspn 249 strerror 249 strftime 256 strlen 159, 249 strncat, strnc, strncpy 159, 248 strpbrk 249 strrchr 159, 249 strspn 249 strstr 249 strtod 251 strtok 249 strtol,strtoul251 system 160, 252 tan 250 tanh 250 time 256 tfi le 241 tam 241 tolower 146,160,248 toupper 160,248 ungetc 160, 246 vfprintf, vprintf, vsprintf 167,242 Bildschirmausgabe 15, 146, 157, 163 binär, Baum 134 binär, Strom 154, 239r Bindung 187, 225r extern 72, 184, 187, 205, 226 intern 187, 226 binsearch Funktion 57,129,132 Bit-Feld ix, 144,2071f Bit-Operator 48,143,198,201 bi tCot.rlt Funktion 50 Block 55, 82, 219, 222r Initialisierung 83, 220 Blockstruktur 55, 82r, 219 break Anweisung 59, 63r, 221 bsearch Bibliotheksfunktion 253 Bücherei -+ Bibliothek BUFSIZ 241 c C-Programm übersetzen 6,24 ca// by reference 26 ca// by va/ue -+ Wertübergabe calloc Bibliotheksfunktion 161, 252 canonrect Funktion 126 carriage return -+ Wagenrücklauf case Marke 58, 219 cast -+ Typumwandlung, Umwandlungsoperation ca! Programm 153, 156r ce Kommando 6, 70 cei I Bibliotheksfunktion 250 charTyp 9,36,188,206 signed 43, 188 unsigned 36,43,165,188 clearerr Bibliotheksfunktion 247 CLIC_TCIC 256 clock Bibliotheksfunktion 256 clock_t Typname 255 close Systemaufruf 167 closedir Funktion 176 Compiler -+ übersetzen const Attribut ix, 40, 189, 206 continue Anweisung 64,221 copy Funktion 29,31 cos Bibliotheksfunktion 161,250 cosh Bibliotheksfunktion 250 creat Systemaufruf 165r ct i me Bibliotheksfunktion 256 <ctype.h> 43, 146, 160,247 r Sachverzeichnis D 267 Datei aneinanderhängen, Programm 153 anfügen 154, 168, 240 einfügen 86, 129 eröffnen 154, 163, 166 erzeugen 154,163 Kennung .h 32 kopieren, Programm 16r, 164, 166r übersetzen, mehrere 70 Zugriff 153,163, 170r, 240 Zugriffsart 154, 170r, 240 Zugriffsschutz 166 Dateisystem, UNIX 163,172 Datum umrechnen 107 day_of_year Funktion 107 dc I Funktion 119 dc/ Programm 119r default Marke 58,219 defensive Programmierung 57, 59 #define 14,87,227 mehrzellig 87 mit Argumenten 87 und enun 39, 143 def i ned Preprozessor-Operator 88, 230 DefInition -+ Vereinbarung ix, 9 externe Variable 32, 224 Funktion 24, 69, 222 Makro 1I7 Speicherplatz 205 und Deklaration 32, 79, 204 vorläufIg 224 DefInitionsdatei ix, 32, 80 <assert.h> 253 <ctype.h> 43,146,160,247 dir.h 175 <errno.h> 247 <fcnt I. h> 166 dloat.h> 36,258 <I imits.h> 36,257 <Iocale.h> 239 <math.h> 44,161,249r <setj.h> 254 <signal.h> 254r stat.h 173 <stdarg.h> 149, 167,253r DefInitionsdatei +- <stddef .h> 100, 130, 198,239 <stdio.h> 6,16, 86r, 99, 145r, 239 <stdl ib.h> 70, 137,251 <string.h> 39, 102r, 159, 248 sysca//s.h 165 <time.h> 255 types.h 173,175 Deklaration -+ Vereinbarung ix, 9 ... 149,196 und DefInition 32, 79, 204 Deklarator 2111f abstrakter 217 für Funktion 213 für Vektor 212 Dekrement -- 18, 46, 103, 196r dereferencing -+ Inhaltsoperator Diagnose-Ausgabe 155, 163 di fftime Bibliotheksfunktion 256 DIR Struktur 173 dirdcl Funktion 119 D i rent Struktur 173 dir.h DefInitionsdatei 175 dirwalk Funktion 175 di v Bibliotheksfunktion 253 Division / 10, 4Or, 199 abschneiden 10, 4Or, 199 ganzzahlig, Integer 10, 40r Rest X 4Or, 199 div_t, Idiv_t Typnamen 253 do-whi le Anweisung 62r, 221 domain -+ Argumentbereich 249r Doppelanführungszeichen 10 7, 19, 38, 186 double Typ 9,18,36,188,206 Konstante 37, 186 Umwandlung in f loat 44, 190 Drei-Zeichen-Folge 227 druckbares Zeichen 248 E E Schreibweise 37, 72, 186 EBCDlC Zeichensatz 43 echo Programm 111 EDOM 249r Effucienz 50,81,86,136,179 einfügen, Datei 86, 229 
268 Sachverzeichnis Eingabe 145, 155, 163 Fehler 158, 247 gepuffert 164 Tastatur, Terminal 15, 145, 163 Umlenkung .146, 155, 164 ungepuffert 164 zurückstellen 77 Zeichen 15, 145 Eingabesymbol 183, 227 Verkettung 88, 228 einrücken, Programmtext 10, 18, 23, 56 Einschränkung bei Ausrichtung 133,137, 141,161,178,192 Eins-Komplement, Bit-Operator - , 198 Element eines Vektors ix, 22 elementare Typen 9,36,188 #elif,#else 88,230 else-i f 23,56r #endi f 88 endlose Schleife, for(;;) 60,87 enun Aufzählungstyp 39, 210 und #define 39,143 enumeration -+ Aufzählung enumerator -+ Aufzählungskonstante environment -+ Umgebung EOF 16, 145, 240 ERANGE 249r eröffnen, Datei 154, 163, 166 errno 247,250 <errno.h> 247 #error 231 error Funktion 167 Ersatzdarstellung 7, 19, 37r, 185,227 hexadezimal \x 37, 185 Tabelle 38, 185 erweitertc konstante Zeichenkette 186 crwcitcrte Zeichenkonstante 185 Erwciterung bei Argument 45, 195 Erweiterung bei Intcger 44, 189r erzeugcn, Datei 154, 163 erzeugen, Zeiger 193 Etikett ix Aufzählung 210 Struktur 123, 2JJ7 Union 2JJ7 . ex i t Bibliotheksfunktion 157, 252 EXIT_FAILURE,EXIT_SUCCESS 252 exklusiv-ODER, Bit-Operator A 48, 201 exp Bibliotheksfunktion 161, 250 Expansion, Makro 228 explizite Typumwandlung 45, 198 Exponentenschreibweise 37, 72, 186 exponenzieren 24, 250 expression -+ Ausdruck 193ff extern Speicher klasse 30, 32, 79, 205 externe Bindung 72, 184, 187, 205, 226 externe Variable 30, 72, 187 Definition 32, 224 Gültigkeitsbereich 78, 225 Initialisierung 40, 79, 83, 215 statie 81 Vereinbarung 30,222,224 externer Name, Länge 35, 184 F \ f Seitenvorschub 38, 185 fabs Bibliotheksfunktion 161,250 fe lose Bibliotheksfunktion 156,240 <fent! . h> 166 fchlende Speicherklasse 205 fehlendcr Typ 206 Fehler bei Eingabe/Ausgabe 158,247 feof Bibliotheksfunktion 158, 247 Makro 169 ferror Bibliotheksfunktion 158, 247 Makro 169 fflush Bibliotheksfunktion 240 fgete Bibliotheksfunktion 244 fgetpos Bibliotheksfunktion 247 fgets Bibliotheksfunktion 158, 244 Funktion 158 file -+ Datei FILE-Zeiger 154, 169, 239 Filc-Deskriptor 163 _JILE__ Makro, Prcprozessor 253 fi leeopy Funktion 156 FILENAME_MAX 240 _ f ill buf Funktion 171 float Typ 9,36,188,206 Konstante 37, 186 Umwandlung in double 44,190 <float.h> 36,258 ,. Sachverzeichnis 269 ftoor Bibliotheksfunktion 250 fmod Bibliotheksfunktion 250 fopen Bibliotheksfunktion 154, 240 Funktion 170 FOPEN_MAX 240 for Anweisung 13, 18, 59, 221 endlose Schleife for(; ;) 60, 87 und whi te 14,59r Formalparameter ix, 25 Format, Programm 10, 18, 23, 40, 133, 183 Formatelement, printf Tabelle 13 formfeed -+ Seitenvorschub Forth 73 Fortran 19, 23r, 26, 30, 60, 72 fortran reserviertes Wort 184 fpos_t Typname 247 fprintf Bibliotheksfunktion 155,241r fpute Bibliotheksfunktion 244 fputs Bibliotheksfunktion 158, 244 Funktion 159 frand Funktion 162 fread Bibliotheksfunktion 246 free Bibliotheksfunktion 161,252 Funktion 180 f reopen Bibliotheksfunktion 156, 240 frexp Bibliotheksfunktion 250 fseanf Bibliotheksfunktion 155, 243 fseek Bibliotheksfunktion 246 hetpos Bibliotheksfunktion 247 fsize Funktion 174 fsize Programm 174 htat System aufruf 176 ftet I Bibliotheksfunktion 246 function designator -+ Funktionsaufruf, Funktionsbezeichner Funktion alter Stil 26, 32, 72, 195 Argument 25, 195 Aufruf 195 Definition 24, 69, 222 Deklarator 213 implizite Vereinbarung 26,72,195 Länge des Namens 35,184 leere 69 mathematische 161 neuer Stil 195 Funktion  Prototyp 25r, 29, 45, 71, 115r, 195 statie vereinbart 81 Typ nach Voreinstellung 29,195 Umwandlung 193 Vereinbarung 213r Verweis 195 Zeichen testen 160, 247 Zeichenketten 159 Zeiger 114, 116, 141, 195 Funktionsbeispiele addpoint 126 addtree 136 afree 99 al toe 98 atof 70r atoi 42, 61, 72 binseareh 57, 129, 132 bi teount 50 eanonrect 126 closedir 176 eopy 29, 31 day_of_year 107 dcl 119 dirdct 119 dirwatk 175 error 167 fgets 158 fi teeopy 156 _fillbuf 171 fopen 170 fputs 159 frand 162 free 180 fsize 174 get 168 getbits 49 geteh 77 getint 94 getl ine 28,31,69,159 getop 76 gettoken 12JJ getword 131 hash 139 install 140 itoa 63 
270 Funktionsbeispiele +- lookup139 lower 43 main 6 malcepoint 125 malloc 178r minprintf 150 month_day 107 month_name 109 moreeore 180 nunc 116 opendir 176 pop 76 power 24, 27 printd 84r ptinreet 126 push 76 qsort 85, 106, 115 rand 45 readdir 176 reacH i nes 105 reverse 62 shellsort 61 squeeze 46 srand 46 streat 47 stre 103 strepy 102 strdup 137 strindex 69 strlen 39,96, 100 swap 85,93, 106,16 talloc 137, 141 treeprint 136 trim 64 ungeteh 77 writel ines 105r fwri te Bibliotheksfunktion 246 G Gegenschrägstrich \ 8, 38 ganze Zahl -+ Integer generischer Zeiger 91, 100, 115, 192r, 260 gepufferte Eingabe 164r geschweifte Klammem 7, 10, 55, 82 get Funktion 168 Sachverzeichnis getbi ts Funktion 49 gete Bibliotheksfunktion 155, 245 Makro 169 geteh Funktion 77 getehar Bibliotheksfunktion 15, 145, 155, 245 Funktion 165 getenv Bibliotheksfunktion 252 get i nt Funktion 94 geH ine Funktion 28,31,69,159 getop Funktion 76 getrennte Übersetzung 67, 78, 225 gets Bibliotheksfunktion 158, 245 gettolcen Funktion 120 getword Funktion 131 gleich == 19, 41, 201 Gleitpunkttypen 189 abschneiden 44, 190 Konstante 12, 37, 186 Umwandlung in Integer 44, 190 Zufallszahlen 162 gmt i me Bibliotheksfunktion 256 goto Anweisung 64r, 221 Grenzbedingung 19, 64 Größe von Struktur 133, 198 Größe von Zahlen 9, 18, 36, 257 größer> 41,200 größer-gleich >= 41, 200 Gültigkeitsbereich 78, 187, 225 automatische Variable 78, 225 externe Variable 78, 225 lexikalisch 225 Marke 65, 219, 225 H .h Kennung bei Dateiname 32 hash Funktion 138r Header-Datei -+ Def1nitionsdatei hexadezimal Ersatzdarstellung \x 37, 185 Konstante Ox... 37, 184 Hoare, C. A. R. 85 HUGE_VAL 250 I identifier -+ Name r , Sachverzeichnis 271 #i f 88, 130,230 i f-else Anweisung 19,21,55,220 Mehrdeutigkeit 55r, 220,232 #i fdef 89,231 #i fndef 89, 231 illegale Zeigerarithmetik 99r, 133, 199r implementierungsabhängig -+ Portabilität implizite Vereinbarung einer Funktion 26, 72,195 #include 32, 86,146,229 include file -+ Def1nitionsdatei Index für Vektor 22, 95, 194, 213 negativ 97 und Zeiger 95r, 213 indirection -+ Inhaltsoperator infonnation hiding 67r, 74, 76 Inhaltsoperator * ix, 91,197 lnitialisierung 40, 83, 215, 224 auto Variable 30,40,83,215 Block 83, 220 externe Variable 40, 79, 83, 215 Form 83r, 204 konstante Zeichenkette 84, 215 statie 40,83,215 Struktur 124, 215 Union 216 Vektor 84, 109, 215 Vektor von Strukturen 128 Voreinstellung 84, 215 Zeiger 99, 132 zweidimensionaler Vektor 108, 216 inklusiv-ODER, Bit-Operator I 48, 201 inkonsistente Typenvereinbarung 71 Inkrement++ 18,46,103, 196r lnode 172 insull Funktion 140 int Typ 9,36,206 Integer-Typen 189 Division 10, 40r Erweiterung 44, 189r Konstante 12, 37, 184 Umwandlung in Gleitpunkt 12, 190 Umwandlung in Zeichen 44 Umwandlung in Zeiger 192,199r interne Bindung 187, 226 interne statie Variable 81 interner Name, Länge 35, 184 _IOFBF,_IOLBF,_IONBF 241 isalnun Bibliotheksfunktion 131,160,247 isalpha Bibliotheksfunktion 131,160,247 i sent r 1 Bibliotheksfunktion 247 isdigit Bibliotheksfunktion 160,247 i sgraph Bibliotheksfunktion 248 i s lower Bibliotheksfunktion 160, 248 ISO Zeichensatz 227 i spri nt Bibliotheksfunktion 248 i spunct Bibliotheksfunktion 248 isspaee Bibliotheksfunktion 131,160,248 i supper Bibliotheksfunktion 160, 248 i sxdi gi t Bibliotheksfunktion 248 i toa Funktion 63 K Katalog ausgeben, Programm 172 Kernighan, B. W. x Klammem eckige -+ Vektor 22, 194 geschweilte 7,10,55,82 runde 194r kleiner< 41,200 kleiner-gleich <= 41, 200 Kleinschreibung, Programm 146 Klingelzeichen \a 38, 185 Komma als Operator 62, 203 Kommandozeilen-Argumente 1l0ff Kommentar 9, 183, 227 Komponente einer Struktur ix, 123, 208 Konstante ix, 37, 184 Aufzählung enun 39, 88, 184, 186, 210 Ausdruck 38, 58, 88, 204 double 37, 186 erweitert, Zeichen 185 erweitert, Zeichenkette 186 float 37,186 ganzzahlig 12, 37, 184 Gleitpunkt 12, 37, 186 hexadezimal Ox... 37, 184 hexadezimal, Zeichen 37,185 leng 37, 184 long double 37,186 Länge im Preprozessor 35 oktal 0... 37, 184 
272 Sachverzeichnis Konstante +- oktal, Zeichen 37 Suffix 37, 185 Typ 37, 185 LnS \gned 37, 184 LnSigned long 37, 185 Zeichen 19, 37, 185 Zeichenkette 7, 19, 29, 38, 97, 101, 186 Zeichenkette, lnitialisierung 84, 215 Zeichenkette, Länge 29, 38, 101 kopieren, Programm 16r, 164, 166r L L-Wert 189 labs Bibliotheksfunktion 253 Länge konstante Zeichenkette 29, 38, 101 Name 35, 184 symbolische Konstante 35 längste Zeile, Programm 28, 31 Idexp Bibliotheksfunktion 250 I d iv Bibliotheksfunktion 253 ldiv_t, div_t Typnamen 253 leer Anweisung 18, 219 Funktion 69 Zeichenkette 38 Lesbarkeit, Programm 10, 50, 63, 84, 141 lexikalische Konventionen 183 lexikalischer Gültigkeitsbereich 225 <Iimits.h> 36,257 #l ine 231 __LlNE_ Makro, Preprozessor 253 /inkage.... Bindung links-shift, Bit-Operator 48,200 Literai.... Konstante <locale.h> 239 1 ocal time Bibliotheksfunktion 256 log, lO910 Bibliotheksfunktionen 161,250 logische Negation, 42, 198 logischer Ausdruck, Wert 43 logisches ODER 11 21,41, 48, 202 logisches UND && 21,41,48, 202 lokale Erwägungen 239 lang Typ 9,18,36,188,206 Konstante 37, 184f long double Typ 36,188 Konstante 37, 186 longj Bibliotheksfunktion 254 LONG_MAX, LONG_MIN 251 lOOkup Funktion 139 lower Funktion 43 /s Kommando 172 I seek System aufruf 168 M magische Zahlen 14 main Funktion 6 return 25, 157 makepo i nt Funktion 125 Makro 227r feof, ferror,getc,putc 169 mit Argumenten 87 Preprozessor 86, 226 malloc Bibliotheksfunktion 137, 160,252 Funktion 178r Marke 65, 219 case,default 58,219 Gültigkeitsbereich 65, 219, 225 maschinenabhängig .... Portabilität <math.h> 44, 161, 249r mathematische Funktionen 161, 250 Mehrdeutigkeit, if-else 55r, 220, 232 mehrdimensionaler Vektor 107, 213 mehrere Dateien übersetzen 70 mehrfache Zuweisung 20 mehrzelliges #define 87 member.... Alternative, Komponente memchr,memc,memcpy,menmove,memset Bibliotheksfunktionen 249 Methode des rekursiven Abstiegs 118r minprintf Funktion 150 Minus, unär - 198 mktime Bibliotheksfunktion 256 modf Bibliotheksfunktion 250 Modularisierung 24, 27r, 32, 67, 73r, 104 month_day Funktion 107 month_name Funktion 109 morecore Funktion 180 MS-DOS 163, 172 multi-dimensionaler Vektor 107, 213 Multiplikation * 40, 199 Muster suchen, Programm 67r, 112r , Sachverzeichnis 273 N \n Zeilentrenner 7, 15, 19, 37r, 185, 239 Name 184 Länge 35, 184 Strukturkomponente 123, 208 verbergen 83 vordefmiert, Preprozessor 232 Namensraum 225 nationale Zeichen 43,146 Nebenwirkung 52r, 87, 193, 196 Negation, 42, 198 negative Vektorindizes 97 neuer Stil, Funktion 195 new/ine .... Zeilentrenner NULL 99 Null, Test weglassen 55, 102 Nullzeichen \0 29, 38, 185 Nullzeiger 99, 191 nunc Funktion 116 numerisch sortieren 114 o Objekt 187, 189 ODER 11 21,41,48,202 ODER, Bit-Operatoren A 1 48,201 oktale Konstante 37, 184 open Systemaufruf 166 opendir Funktion 176 Operationen bei U'li on 142f Operationen bei Zeigern 100 Operator 52 Addition + 40, 199 Adresse & 91, 197 Äquivalenzvergleich 41,201 Arithmetik 40 Assoziativität 51r, 193 Dekrement -- 18, 46, 103, 196r Division I 10, 4Or, 199 gleich == 19,41, 201 größer> 41, 200 größer-gleich >= 41,200 Inhalt * ix, 91 Inkrement++ 18,46,103, 196f kleiner< 41, 200 kleiner-gleich <= 41,200 Komma 62, 203 Operator +- Minus, unär - 198 Multiplikation * 40, 199 Negation! 42, 198 ODER 11 21,41,48,202 Plus, unär + 197 Preprozessor 88, 228, 230 Rest nach Division x 4Or, 199 sizeof 88,100,130,198,246 Strukturauswahl . 124, 194 Strukturverweis -> 127,194 Subtraktion - 40, 199r Tabelle 52 UND && 21, 41, 48, 202 ungleich 1= 16, 41, 201 Vergleich 16, 41, 200 Vorrang 17,41,52,92,127,193 Zuweisung = 16, 41, 49, 203 Zuweisung += 49 Operator, Bit-Manipulation 48, 198,200r Eins-Komplement - 48,198 ODER A 1 48, 201 shift « » 48, 200 UND & 48, 201 o RDONLY,O_RDWR,O_WRONLY 166 oeow 41,193,250,255 p Parameter ix, 25, 82, 97, 195 Parameterliste,void 213 Pascal 6,19,23,26,30,55,60,63,72,82, 123, 141 perror Bibliotheksfunktion 247 Pipe 145, 164 Plus unär + 197 Polnische Schreibweise, Taschenrechner 73 pop Funktion 76 Portabilität 3,37,43,48,141,144,147,177 Position, geschweifte Klammern 10 PostfIX ++ und -- 46, 102 Postscript 73 pow Bibliotheksfunktion 24, 161, 250 power Funktion 24, 27 PräfIX ++ und -- 46, 103 #pragma 231 
274 Sachverzeichnis Sachverzeichnis 275 precision -+ Genauigkeit Preprozessor 86ff, 226ff _FILE-, _LI NE_ 253 Namen, vordefIniert 232 Operatoren 88, 228, 230 Primärausdruck 194 printd Funktion 84r printf Bibliotheksfunktion 7,10,18, 147r, 242 Beispiele, Tabelle 148 Umwandlungen, Tabelle 13,148,243 Programm Abbruch 156r cat 153, 156r Datei kopieren 16r, 164, 166f Dateien aneinanderhängen 153 dc/ 119r echo 111 Format 10, 18, 23, 40, 133, 183 fsize 174 Katalog ausgeben 172 Kleinschreibung 146 längste Zeile 28, 31 Lesbarkeit 10,50,63,84,141 Muster suchen 67r, 112r reservierte Worte zählen 129 sortieren 105, 115 Tabellensuche 138 Taschenrechner 71, 73ff, 152 Temperaturumwandlung 8, 12r, 15 übersetzen 6, 24 undc/ 121 Wörter zählen 20, 134 Zeichen zählen 17 Zeilen zählen 19 Zwischenraum zählen 22 58 Propagierung, Vorzeichen 43r: 170,185 Prototyp einer Funktion 25r, 29, 45, 71, 115r, 195 ptinreet Funktion 126 ptrdiff_t Typname 100,141,200 push Funktion 76 pute Bibliotheksfunktion 155 246 Makro 169 ' putehar Bibliotheksfunktion 15, 146, 155,246 puts Bibliotheksfunktion 158, 246 Q qua/ifier -+ Attribut qsort Bibliotheksfunktion 253 Funktion 85, 106, 115 Quicksort 85, 106 R seanf Bibliotheksfunktion 94, 150ff, 244 Umwandlungen, Tabelle 152, 245 Zuweisung unterdrücken 151, 243r Schaltjahr, Berechnung 40, 107 scope -+ Gültigkeitsbereich 78, 187, 225 SEEK_CUR,SEEK_ENO,SEEK_SET 246 Seiten effekt -+ Nebenwirkung 52r, 87, 193, 196 Seitenvorschub \ f 38, 185 selbst-verweisende Struktur 135,208 Semikolon 9,15,18, 55r sequentiell, Anweisungen 218 setbuf, setvbuf Bibliotheksfunktionen 241 setj"" Bibliotheksfunktion 254 <setj"".h> 254 Shell, D. L. 61 shellsort Funktion 61 shift, Bit-Operatoren «» 48,200 short Typ 9, 36, 188, 206 SIG_DFL, SIG_ERR, SIG_IGN 255 sign extension -+ Vorzeichen-Propagierung signa l Bibliotheksfunktion 255 <signaloh> 254r signed -+ vorzeichenbehaftet si gned eher 43, 188 si gned Typ 36, 206 si n Bibliotheksfunktion 161, 250 si nh Bibliotheksfunktion 250 si zeof Operator 88, 100, 130, 198, 246 size_t Typname 100,130,141,198,240 Skalierung bei Zeigerarithmetik 100, 191 sortieren, Programm 104r,114r Speicher reservieren 205 Speicherklasse 187, 205 euto, automatisch 30, 187, 205 extern 30,32, 79, 205 keine Angabe 205 register 81C,205 statie 30,81,187,205 Speicherverwaltung 137,177ff sprintf Bibliotheksfunktion 149,242 Sprunganweisung 221 sqrt Bibliotheksfunktion 161, 250 squeeze Funktion 46 srand Bibliotheksfunktion 162, 251 Funktion 46 \r Wagenrücklauf 38, 145, 185, 239 ra i se Bibliotheksfunktion 255 rand Bibliotheksfunktion 161, 251 Funktion 45 RAND_MAX 251 range -+ Resultatbereich 249r read Systemaufruf 164 readdi r Funktion 176 readl ines Funktion 105 realtoc Bibliotheksfunktion 252 rechts-shift, Bit-Operator» 48, 200 recursive-descent parser 118r reference -+ Adreßoperator register Speicherklasse 81r, 205 Adresse 205 Reihenfolge Bewertung 21,48, 52r, 62, 75, 87, 92, 193 Übersetzung 226r Vektor im Speicher 108, 213 Rekursion 84, 135r, 175, 196,274 rekursive Struktur 135, 208 rekursiver Abstieg 118r remove Bibliotheksfunktion 240 reneme Bibliotheksfunktion 240 reservierte Worte 35, 184 Tabelle 184 zählen, Programm 129 Rest nach Division x 4Or, 199 return Anweisung 25 29 69 72 222 inmain 25,157' , , , Typumwandlung 72, 222 reverse Funktion 62 rewind Bibliotheksfunktion 247 Richards, M. 1 Ritchie, D. M. vii s sbrlc Systemaufruf 180 sseenf Bibliotheksfunktion 244 Standard-Ausgabe 146, 155, 163 Standard-Defmitionsdateien, Tabelle 239 Standard-Eingabe 145, 155, 163 standard error -+ Diagnose-Ausgabe stat Struktur, Systemaufruf 173 stat.h Defmitionsdatei 173 statie Speicherklasse 30,81,187,205 Initialisierung 40, 83, 215 <stdarg.h> 149,167,253r <stdclef. h> 100, 130, 198, 239 stderr 155,157,240 stdin 155,240 <stdio.h> 6, 16, 86r, 99, 145r, 239 Inhalt 169 <stdl ib.h> 70,137,251 stdout 155, 240 Steuerzeichen 248 streat Bibliotheksfunktion 159,248 Funktion 47 strehr Bibliotheksfunktion 159,248 stre"" Bibliotheksfunktion 159, 248 Funktion 103 strepy Bibliotheksfunktion 159,248 Funktion 102 strespn Bibliotheksfunktion 249 strclup Funktion 137 stream -+ Datenstrom, Strom strerror Bibliotheksfunktion 249 strftime Bibliotheksfunktion 256 strindex Funktion 69 String -+ Zeichenkette <string.h> 39, 102r, 159,248 strlen Bibliotheksfunktion 159,249 Funktion 39, 96, 100 strncat Bibliotheksfunktion 159, 248 strne"" Bibliotheksfunktion 159,248 strncpy Bibliotheksfunktion 159,248 Strom, binär 154, 239r Strom, Text 15, 145, 239 strpbrlc Bibliotheksfunktion 249 strrehr Bibliotheksfunktion 159,249 strspn Bibliotheksfunktion 249 strstr Bibliotheksfunktion 249 strtodBibliotheksfunktion 251 
276 Sachverzeichnis strtok Bibliotheksfunktion 249 strtol, strtoul Bibliotheksfunktion 251 struct Struktur ix, 123, W7r Auswahloperator . 124, 194 DIR, Dirent 173 Etikett ix, 123, W7 geschachtelt 124 Größe 133, 198 Initialisierung 124, 215 Komponente ix, 123, 208 rekursiv 135, 208 selbst -verweisend 135, 208 stat 173 Vektor 127r Vereinbarung 123, W7 Verweisoperator -> 127, 194 Zeiger 131 Subtraktion - 40, 199r Zeiger 100, 133, 191 Suffix bei Konstante 37, 185 swap Funktion 85, 93, 106, 116 swi teh Anweisung 58, 74r, 2W symbolische Konstante, Länge 35 Syntax Funktionsaufruf 195 Schreibweise 187 Strukturverweis 196 Variablenname 35, 184 Syntaxbaum 118 sysca/ls.h Defmitionsdatei 165 system Bibliotheksfunktion 160, 252 Systemaufruf 163 elose 167 ereat 165r fstat 176 l seek 168 open 166 read 164 sbrk 180 stat 173 unI ink 167 write 164 T \ t Tabulatorzeichen 7, 11, 38, 185 Tabellen Ersatzdarstellungen 38, 185 Operatoren 52 printf Beispiele 148 printf Umwandlungen 13,148,243 reservierte Worte 184 seanf Umwandlungen 152,245 Standard- DefInitionsdateien 239 Tabellensuche, Programm 138 lag -+ Etikett talloc Funktion 137, 141 tan, taM Bibliotheksfunktionen 250 Taschenrechner, Programm 71, 73ff, 152 Tastatur-Eingabe 15, 145, 163 Teilvektor als Argument 97 Temperaturumwandlung, Programm 8, 12r, 15 Terminal, Eingabe und Ausgabe 15 Textstrom 15, 145, 239 Textzeilen sortieren 104, 114 Thompson, K. L. 1 time Bibliotheksfunktion 256 <time.h> 255 time_t Typname 255 tfi le Bibliotheksfunktion 241 THP _HAX 241 tam Bibliotheksfunktion 241 loken -+ Eingabesymbol tolower Bibliotheksfunktion 146, 160,248 toupper Bibliotheksfunktion 160, 248 treeprint Funktion 136 trigraph -+ Drei-Zeichen-Folge trim Funktion 64 Typ 206,213 abgeleitet 1, 9, 189 äquivalent 218 arithmetisch 188 Attribut ix, W2, 206 Aufzählung enun 188r eh ar 9, 36, 188, 206 double 9,18,36,188,206 elementar 9, 36, 188 float 9,36,188,206 ganzzahlig 189 Gleitpunkt 189 r I Sachverzeichnis 277 Typ+- inkonsistente Vereinbarung 71 int 9,36,206 Integer 189 keine Angabe 206 Konstante 37, 185 long 9, 18, 36, 188, 206 long double 36, 188 short 9, 36, 188, 206 si gned 36, 206 Umwandlungsregeln 42,44, 190 Umwandlung explizit 45 Umwandlung bei return 72, 222 unsigned 36,50,188,206 uns i gned ehar 36, 165 unvollständig W7r Vereinbarung 213 void 29, 189, 192,206 Zeichenkette 194 type qualifier -+ Typattribut typedef Vereinbarung 14Or, W5, 217f types.h DefInitionsdatei 173, 175 Typname 217 clock_t 255 div_t, ldiv_t 253 FILE 154 fpos_t 247 ptrdiff_t 100,141,200 size_t 100,130,141,198,240 time_t 255 wehar_t 185 u übersetzen, C Programm ix, 6, 24 bedingt 88, 230 getrennt 67, 78, 225 mehrere Dateien 70 Phasen 183, 226 Reihenfolge 226r Übersetzungseinheit 183, 222, 225 übliche arithmetische Umwandlung 42, 190r ULONG-'''AX 251 umgekehrte Polnische Schreibweise 73 Umlaut 43, 146 Umlenkung, Ein-/Ausgabe 146,155,164 Umwandlung 189ff Argument 45 arithmetisch 42, 190r Datum 107 double-float 44, 190 float-double 44, 190 Funktion 193 Gleitpunkt-Integer 44, 190 Integer-Gleitpunkt 12, 190 IntegerZeichen 44 Integer-Zeiger 192, 199r Regeln 42, 44, 190 return 72, 222 Tabelle für printf 13,148,243 Tabelle für seanf 152, 245 Vektorname 96, 193 Zeichen-Integer 22,42, 189r Zeiger 137, 191, 199r Zeiger-Integer 191, 199r Zuweisung 44, 203 Umwandlungsoperation 45,137,161,191, 198,217 unäres Minus - 198 unäres Plus + 197 UND && 21,41,48,202 UND, Bit-Operator & 48,201 undcl Programm 121 #undef 88, 165, 227 undery70w 41,250,255 ungepufferte Eingabe, getehar 164r ungete Bibliotheksfunktion 160, 246 ungeteh Funktion 77 ungleich, != 16,41, W1 union, Union, -+ Struktur Ausrichtung 178 Etikett W7 Initialisierung 216 Operationen 142r Vereinbarung 141f, 207 UNIX Dateisystem 163, 172 lM"Il ink Systemaufruf 167 unsigned -+ vorzeichenlos uns i gned Typ 36, 50, 188, 206 Konstante 37, 184 uns i gned ehar Typ 36,43,165,188 unsigned long Konstante 37,185 unspezifischer Zeiger -+ void * 
278 Sachverzeichnis Sachverzeichnis 279 Unterstrich _ 35, 184, 239 unvollständiger Typ W7r V \v Vertikal-Tabulator 38, 185 va_arg, va_end, va_l ist, va_start 149,167, 242,254 Variable 187 J\dresse 27,91,197 intern, statie 81 Variable, automatisch 30, 73, 187 Gilltigkeitsbereich 78, 225 Initialisierung 30, 40, 83, 215 Variable, extern 30, 32, 72, 187, 222, 224 Gilltigkeitsbereich 78, 225 Initialisierung 40, 79, 83, 215 statie 81 variable Argumentliste 149, 167, 196, 213, 223,253r Variablenname, Syntax 35, 184 Länge 184 Variante -+ Union Vektor ix, 22, 107, 212 Argument argv 110, 157 Deklarator 212 Index 22, 95, 194, 213 multi-dimensional 107,213 Reihenfolge im Speicher 108, 213 und Zeiger 951f, 101, l09r Verweis 194 zweidimensional 107f,216 Vektor, Initialisierung 84, 109,215 Struktur 128 zweidimensional 108, 216 Vektor von Strukturen 127r Zeichen 19,27,101 Zeigern 104 Vektorgröße, Voreinstellung 84, 109, 129 Vektorname als Argument 27, 97, 108 Vektorname, Umwandlung 96,193 Vereinbarung ix, 9, 32, 39, 79, 2041f -+ Definition, Deklaration Bit-Feld 144, W7 extern 222, 224 externe Variable 30, 222 Funktion 213f Vereinbarung  Funktion, implizit 26, 72, 195 inkonsistente Typen 71 Speicherklasse W5 Struktur 123, W7 Typ 213 typedef 14Of, W5, 217f Union 141f, W7 Vektor 22, 107, 212 Zeiger 92,97, 212 Vereinigung -+ uni on Vergleich, Zeiger 99, 133, 180, 200r Vergleichsausdruck, Wert 42f Vergleichsoperatoren 16, 41, 200 verketten, Eingabesymbol 88, 228 verketten, Zeichenkette 38, 88, 186 Vertikal-Tabulator \v 38,185 Verweisoperator -+ Inhaltsoperator verzweigte Entscheidung 23, 57 vfprintf Bibliotheksfunktion 167,242 void Typ 29, 189, 192, 206 void Argumentliste 32,72,213,223 void * Zeiger 91, 100, 115, 192r, 260 volatile ix, 189,206 vordefinierte Preprozessor-Namen 232 Voreinstellung Funktionstyp 29,195 Initialisierung 84, 215 Vektorgröße 84, 109, 129 vorläufige Defmition 224 Vorrang von Operatoren 17,41,52,92, 127, 193 Vorzeichen -+ unärer Operator Vorzeichen-Propagierung 43r, 170, 185 vprintf,vfprintf,vsprintf Bibliotheksfunktionen 167,242 wide character constant -+ erweiterte Zeichenkonstante Wiederholungsanweisungen 220r wissenschaftliche Schreibweise 37, 72 Wörter zählen, Programm W, 134 Worte, reservierte 35, 184 zählen, Programm 129 wr i te System aufruf 164 writelines Funktion 105r X \x hexadezimale Ersatzdarstellung 37, 185 W Wagenrücklauf \r 38, 145, 185, 239 wehar _ t Typname 185 Wert, logischer Ausdruck 43 Wert, Vergleich 42f Wertebereich verlassen 41 -+ overflow, underflow Wertübergabe 26, 93, 195 whi le Anweisung 10,59,221 Z Zahlengröße 9,18,36,257 Zeichen \ Gegenschrägstrich 8, 38 \0 Null 29, 38, 185 \a Klingel 38, 185 \b backspace 7,38,185 druckbar 248 Eingabe/Ausgabe 15,145 \ f Seitenvorschub 38, 185 Konstante 19, 37, 185 Konstante, erweitert 185 Konstante, oktal 37 \n Zeilentrenner 7, 15, 19, 37r, 185, 239 nationale 43, 146 \r Wagenrücklauf 38, 145, 185, 239 Steuerzeichen 248 \t Tabulator 7, 11,38, 185 Testfunktionen 160, 247 Umwandlung in Integer 22, 42, 189r \ v Vertikal-Tabulator 38, 185 Vektor 19, 27, 101 Zwischenraum 151,160,244,248 Zeichenkette Funktionen 159 in lnitialisierung 84, 215 Konstante 7, 19,29,38,97, 101, 186 Konstante, erweitert 186 Länge 29, 38, 101 leer 38 Typ 194 verketten 38, 88, 186 Zeichensatz 227 I 19,37,43,227,248 EBCDIC 43 ISO 227 Zeiger 92, 97, 212 Argument 97 Arithmetik 92, 961f, 113, 132r, 199f Arithmetik, illegal 99r, 133, 199r Arithmetik, Skalierung 100, 191 auf Funktion 114, 116, 141, 195 auf Struktur 131 erlaubte Operationen 100 erzeugen 193 fiLE 154,169,239 lnitialisierung 99, 132 Nullzeiger 99, 191 Subtraktion 100, 133, 191 Umwandlung 137, 191, 199r und Vektor 951f, 101, l09r, 213 unspeziftsch, void * 91, 100, 115, 192r, 260 Vergleich 99, 133, 180, 200r Zeigervektor 104 Zeilen zählen, Programm 19 Zeilen zusammenfügen 227 Zeilentrenner 145,183,227 \n 7, 15, 19, 37r, 185,239 Zufallszahlen 161f,251 Gleitpunkt 162 Zugriff auf Datei 153,163, 170r, 240 Zugriffsart 154, 170r, 240 Zugriffsschutz 166 zurückstellen, Eingabe 77 zusammengesetzte Anweisung 55, 82, 219, 222r Zuweisung 16f, 41, 49, W3 Ausdruck 17, W, 50, W3 mehrfach W Umwandlung 44, W3 unterdrücken bei seanf 151, 243r zweidimensionaler Vektor 107r, 216 lnitialisierung 108, 216 Zschenraum 151,160,183,244,248 zählen, Programm 22, 58 
t Häufig verwendete Zeichentabellen 
Zeichen Tabelle 2-1. Vorrang und Assoziativität der Operatoren + Assoziativität von links her von rechts her von links her von links her von links her von links her von links her von links her von links her von links her von links her von links her von rechts her von rechts her von links her Unär haben +, - und * mehr Vorrang als binär. Operatoren ( ) [] -> . ! - ++ -- + - * & (rype) sizeof * 1 " « » < <= > >= 1= & I && 11 ?: = += -= *= 1= X= &= A= 1= «= »= d, i Tabelle B-l. printC Umwandlungen Argument; Umwandlung in int; dezimal mit Vorzeichen. int; oktal ohne Vorzeichen (ohne führende Null). int; hexa dezimal ohne Vorzeichen (ohne führendes Ox oder OX), mit abcdeC bei Ox oder ABCDEF bei OX. int; dezimal ohne Vorzeichen int; einzelnes Zeichen, nach Umwandlung in unsigned char. char *; aus der Zeichenkette werden Zeichen ausgegeben bis vor '\0' oder so viele Zeichen, wie die Genauigkeit verlangt. double; dezimal als [- }mmm.ddd, wobei die Genauigkeit die Anzahl der d festlegt. Voreinstellung ist 6; bei 0 entfällt der Dezimalpunkt. double; dezimal als [- }m.dddddde;;xx oder [- }m.ddddddE;;xx, wobei die Genauigkeit die Anzahl der d festlegt. Vor einstellung ist 6; bei 0 entfällt der Dezimalpunkt. double; 'Tee oder %E wird verwendet, wenn der Exponent kleiner als - 4 oder nicht kleiner als die Genauigkeit ist; sonst wird %C benutzt. Null und Dezimalpunkt am Schluß werden nicht ausgegeben. void *; als Zeiger (Darstellung hängt von Implementierung ab). int *; die Anzahl der bisher von diesem Aufruf von printC ausgegebenen Zeichen wird im Argument abgelegt. Ein Argument wird nicht umgewandelt. kein Argument wird umgewandelt; ein % wird ausgegeben. o x,x u c s f e,E g,G p n " r Zeichen d e, f, 9 o Tabelle B-2. scanC Umwandlungen Eingabedaten; Argumenttyp dezimal, ganzzahlig; int *. ganzzahlig; int *. Der Wert kann oktal (mit 0 am Anfang) oder hexadezimal (mit Ox oder OX am Anfang) angegeben sein. oktal ganzzahlig (mit oder ohne 0 am Anfang); int *. dezimal ohne Vorzeichen; unsigned int *. hexadezimal ganzzahlig (mit oder ohne Ox oder OX am Anfang); int *. ein oder mehrere Zeichen; char *. Die nachfolgenden Eingabezeichen werden im angegebenen Vektor abgelegt, bis die Feldbreite erreicht ist; Voreinstellung is 1. '\0' wird nicht angefügt. In diesem Fall wird Zwischenraum nicht überlesen; das nächste Zeichen nach Zwischenraum liest man mit %15. Folge von Nicht-Zwischenraum-Zeichen (ohne Anführungszeichen); char *, der auf einen Vektor zeigen muß, der die Zeichenkette und das abschließende ,\0' aufnehmen kann, das dazukommt. Gleitpunkt; noat *. Das Eingabeformat erlaubt für noat ein optionales Vorzeichen, eine Ziffernfo\ge, die auch einen Dezimalpunkt enthalten kann, und einen optionalen Exponenten, bestehend aus E oder e und einer ganzen Zahl, optional mit Vorzeichen. Zeiger, wie ihn printC("%p") ausgibt; void *. legt im Argument die Anzahl Zeichen ab, die bei diesem Aufruf bisher gelesen wurden; int ... Vom Eingabestrom wird nicht gelesen, die Zählung der Umwandlungen bleibt unbeeinflußt. erkennt die längste nicht-leere Zeichenkette aus den Eingabezeichen in der angegebenen Klasse; char *. Dazu kommt '\0'. Die Klasse [)...) enthält auch ). erkennt die längste nicht-leere Zeichenkette aus den Eingabezeichen nicht in der angegebenen Klasse; char *. Dazu kommt '\0'. Die Klasse ["]...) enthält auch) . erkennt %; eine Zuweisung fmdet nicht statt. u x c s p n [...] [An.) "