|
Diese FAQ bezieht sich in ihrer Gänze auf den inzwischen nicht mehr aktuellen ISO-C Standard 9899:1990, vielfach auch als C90 bezeichnet. Der seit Dezember 1999 existierende neue ISO 9899:1999 Standard (oder auch C99) wird nicht berücksichtigt.
Frage 10.1: Wie entscheide ich mich für einen der Integer-Typen? Antwort: Der Typ 'long' sollte verwendet werden, wenn Werte benötigt werden, die größer als 32767 oder kleiner als -32767 sind. Wenn Speicher sehr wichtig ist - große Arrays oder sehr viele Strukturen -, dann ist die Verwendung von 'short' sinnvoll. Trifft keiner dieser Gründe zu, dann sollte 'int' verwendet werden. Falls ein definiertes Verhalten bei einer Bereichsüberschreitung wichtig ist und/oder keine negative Werte auftreten, so sind die jeweiligen 'unsigned' Typen von Vorteil. Die beiden Varianten solten aber auf keinen Fall innerhalb eines Ausdrucks gemischt werden. Obwohl 'char' und 'unsigned char' als 'kleine' Integer-Typen verwendet werden können, handelt man sich mit dieser Vorgehensweise meist mehr Nach- als Vorteile ein. Dies liegt an undefiniertem Verhalten bei Vorzeichenbelegung und an einer Vergrößerung des Programmcodes bei manchen Compilern (falls diese die vom Standard vorgeschriebene Erweiterung nach int in allen Fällen durchführen). Obige Regeln lassen sich natürlich nicht anwenden, wenn man mit den Adressen von Variablen arbeitet und diese einen bestimmten Typ erfordern. Wenn aus irgendeinem Grund irgendetwas mit genauer Größe deklariert werden muß (die wohl einzig sinnvolle Situation dafür ist die Kompatibilität mit einem von außen aufgezwungen Speicherlayout), dann sollte das in passenden 'typedef's versteckt werden. Frage 10.2: Wie sollte der 64-bit Typ auf neuen, 64-bit-Rechnern aussehen? Antwort: Einige Hersteller von C-Produkten für 64-bit-Rechner unterstützen 64-bit große 'long int's. Andere fürchten, dass zuviele real existierende Quelltexte auf sizeof(int) == sizeof(long) == 32 bits vertrauen, und führen stattdessen einen neuen 64-bit 'long long' oder '__longlong' Typ ein. Programmierer, die daran interessiert sind, portierbaren Code zu schreiben, sollten deshalb ihre 64-bit-Erfordernisse hinter entsprechenden 'typedef's verstecken. Frage 10.3: Ich habe Probleme mit der Definition von verketteten Listen. Bei typedef struct { char *item; NODEPTR next; } *NODEPTR; gibt der Compiler eine Fehlermeldung aus. Kann eine C-Struktur keinen Zeiger auf sich enthalten? Antwort: C-Strukturen können selbstverständlich Zeiger auf sich enthalten, Kap. 6.5 in K&R beschäftigt sich damit. Im Beispiel liegt das Problem darin, dass die Definition für NODEPTR noch nicht vollständig war, als sie für das Feld 'next' benötigt wurde. Eine Verbesserung besteht darin, der Struktur sofort einen Namen (tag,Etikett) 'struct node' zu geben, und anschließend bei der Deklaration des Feldes 'next' 'struct node *next' anzugeben. Zuletzt wird der 'typedef'-Teil hinter die Strukturdeklaration geschoben (oder vor sie gezogen). Eine berichtigte Version wäre struct node { char *item; struct node *next; }; typedef struct node *NODEPTR; Es gibt außerdem mindestens nochmal drei gleichwertige Möglichkeiten. Ein ähnliches Problem - mit ähnlicher Lösung - kann bei dem Versuch auftreten, Typen zu definieren, die auf einander verweisen. References: K&R I Sec. 6.5 p. 101; K&R II Sec. 6.5 p. 139; H&S Sec. 5.6.1 p. 102; ANSI Sec. 3.5.2.3 . Frage 10.4: Wie kann ich ein Array von N Zeigern auf Funktionen deklarieren, die Zeiger auf Funktionen zurückgeben, die Zeiger auf char zurückgeben? Antwort: Es gibt mindestens 3 unterschiedliche Wege: 1. Man schreibt es einfach hin: char *(*(*a[N])())(); 2. Man baut die Deklaration unter Verwendung von 'typedef' in Etappen auf: typedef char *pc; /* Zeiger auf char */ typedef pc fpc(); /* Fun., die pc zurückgibt*/ typedef fpc *pfpc; /* Zeiger auf fpc */ typedef pfpc fpfpc(); /* Funk., die pfpc zurückgibt */ typedef fpfpc *pfpfpc; /* Zeiger auf .. */ pfpfpc a[N]; /* Array of ... */ 3. Man benutzt das Programm 'cdecl', das Englisch in C umwandelt und umgekehrt: cdecl> declare a as array of pointer to function returning pointer to function returning pointer to char char *(*(*a[])())() cdecl kann auch komplizierte Deklarationen erklären, bei casts helfen und aufzeigen, in welche Klammern die Argumente gehören (bei komplizierten Funktionsdeklarationen wie der obigen). cdecl-Versionen sind in Volume 14 von comp.sources.unix (siehe 17.12) und K&R II. Jedes gute Buch über C sollte erläutern, wie man komplizierte C-Deklarationen "von innen nach aussen" liest. References: K&R II Sec. 5.12 p. 122; H&S Sec. 5.10.1 p. 116. Frage 10.5: Ich programmiere gerade einen Automaten, bei dem jeder Zustand durch eine eigene Funktion realisiert ist. Ich möchte die einzelnen Zustandsübergänge dadurch implementieren, dass eine Funktion einen Zeiger auf die Funktion des Folgezustands übergibt. Dabei werde ich durch die Deklarationsmethode von C behindert. Es gibt keine Möglichkeit, eine Funktion zu deklarieren, die einen Zeiger auf eine Funktion zurückgibt, die einen Zeiger auf eine Funktion ... . Antwort: Das geht nur auf einem Umweg. Entweder muß die Funktion einen generischen Funktionszeiger zurückgeben, der vor Gebrauch durch Typumwandlung angepasst wird, oder eine Struktur, die lediglich einen Zeiger auf eine Funktion enthält, die diese Struktur zurückgibt. Frage 10.6: Mein Compiler beschwert sich über eine ungültige Redeklaration einer Funktion, dabei definiere ich die Funktion nur einmal und rufe sie einmal auf. Antwort: Funktionen, die aufgerufen werden, ohne dass ihre Deklaration im Sichtbarkeitsbereich ist (oder bevor sie deklariert wurden), werden vom Compiler so eingesetzt, als ob sie den Typ int zurückgeben. Dies führt zu Unstimmigkeiten, falls sie später anders deklariert werden. Funktionen, die andere Typen als int zurückgeben, müssen_ _vor ihrem Aufruf deklariert werden. Ansonsten kann man dies als Kompatibilitätsübung für C++ Projekte ansehen, in denen Prototypen für jegliche Art von Funktionen Pflicht sind. References: K&R I Sec. 4.2 pp. 70; K&R II Sec. 4.2 p. 72; ANSI Sec. 3.3.2.2 . Frage 10.7: Wie deklariert und definiert man globale Variablen am besten? Antwort: Zunächst sei festgestellt, dass es beliebig viele _Deklarationen_ einer 'globalen' (genauer gesagt: 'externen') Variablen geben kann, aber genau eine _Definition_. (Die Definition ist diejenige Deklaration, die letztendlich Speicher anfordert - und die Variable eventuell initialisiert). Am besten faßt man die Definitionen in einer zentralen C-Datei zusammen (relativ zum Programm oder Modul) mit externen Deklarationen in Header-Dateien, die per '#include' eingebunden werden, wo immer man die Deklarationen braucht. Die o.a. C-Datei, die die Definition enthält, sollte ebenfalls die Header-Datei einbinden, damit der Compiler sie mit der entsprechenden Deklaration vergleichen kann. Diese Vorgehensweise verspricht ein Höchstmaß an Portierbarkeit, und entspricht den Anforderungen von ANSI-C. Nebenbei bemerkt, benutzen Compiler und Linker unter Unix gewöhnlich ein 'Gemeinschaftsmodell', welches mehrfache (nichtinitialisierende) Definitionen unterstützt. Hin und wieder findet man Systeme, die ausdrückliche Initialisierung verlangen, um eine Definition von einer externen Deklaration zu unterscheiden. Man kann mit Präprozessortricks (geschickte '#define's) erreichen, dass eine Header-Datei mit Deklarationen genau einmal eingelesen wird, und die Deklarationen zu Definitionen werden. References: K&R I Sec. 4.5 pp. 76-7; K&R II Sec. 4.4 pp. 80-1; ANSI Sec. 3.1.2.2 (esp. Rationale), Secs. 3.7, 3.7.2, Sec. F.5.11; H&S Sec. 4.8 pp. 79-80; CT&P Sec. 4.2 pp. 54-56. Frage 10.8: Was bedeutet 'extern' in einer Funktionsdeklaration? Antwort: Es kann als Hinweis (für menschliche Leser) dienen, dass die Definition (wahrscheinlich) in einer anderen Quelldatei steht, aber der Compiler sieht keinen Unterschied zwischen extern int f(); und int f(); References: ANSI Sec. 3.1.2.2 . Frage 10.9: Ich habe herausgefunden, wie Zeiger auf Funktionen zu deklarieren sind, nun würde ich gerne wissen, wie ich sie initialisieren kann. Antwort: Z.B. extern int func(); int (*fp)() = func; Wenn der Name einer Funktion in einem Ausdruck erscheint ohne als Aufruf zu wirken (d.h. ohne folgende "(" ), dann wird die Funktion in einen Zeiger umgewandelt (ihre Adresse wird implizit ermittelt und eingesetzt), ähnlich wie es bei Arrays gehandhabt wird. Eine ausdrückliche (externe) Deklaration ist i.a. notwendig, da eine implizite (externe) Deklaration wegen der fehlenden Argumente sowie der Information über deren Typ nicht möglich ist. Frage 10.10: Ich habe unterschiedliche Wege gesehen, wie Routinen über Zeiger auf Funktionen aufgerufen werden. Was steckt dahinter? Antwort: Ursprünglich mußte ein Zeiger auf eine Funktion vor dem Aufruf durch Dereferenzieren ( *-Operator sowie ein zusätzliches Klammerpaar, um der Hierarchie der Operatoren zu genügen) in eine 'echte' Funktion umgewandelt werden: int r, func(), (*fp)() = func; r = (*fp)(); Man kann ebenso argumentieren, dass Funktionen immer über Zeiger aufgerufen werden, und dass 'echte' Funktionen implizit zu Zeigern umgewandelt werden, und sich folglich nicht abweichend verhalten. Diese Überlegung, die in den ANSI-Standard aufgenommen wurde, bedeutet, dass r = fp(); regelgerecht ist, und korrekt abgearbeitet wird, unabhängig davon, ob fp nun eine Funktion ist, oder ein Zeiger auf eine solche. (Diese Schreibweise war immer eindeutig, man konnte mit einem Zeiger auf eine Funktion nie etwas anderes machen, als eine Funktion aufzurufen.) Ein ausdrückliches * ist harmlos, und immer noch erlaubt (und empfohlen, falls Kompatibilität zu älteren Compilern wichtig ist). References: ANSI Sec. 3.3.2.2 p. 41, Rationale p. 41. Frage 10.11: Welchen Sinn hat das Schlüsselwort 'auto'? Antwort: Keinen. Es ist veraltet.
© 1997-2004 Jochen Schoof (joscho@bigfoot.de) Diese Version wurde am 14. März 2004 erzeugt. Sie wird zukünftig nicht weiter gepflegt. |