FAQ:Structs, Enums und Unions

[ FAQ in de.comp.lang.c ]


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.


[ Inhalt ][ Index ][ ZurÜck ][ Weiter ]


Frage 9.1: Was ist der Unterschied zwischen einem Enum (Aufzählung) und einer Reihe Präprozessor #defines?

Antwort: Momentan ist da wenig Unterschied. Obwohl sich das sicher viele Leute anders gewünscht hätten, besagt der ANSI Standard, dass Enums ohne Casts mit integralen Typen gemischt werden dürfen, ohne dass der Compiler Fehler meldet. (Wenn solches Mischen ohne explizite Casts illegal wäre, könnte die grundsätzliche Verwendung von Enums viele Programmierfehler auffangen.)

Einige Vorteile von Enums sind, dass Zahlenwerte automatisch zugewiesen werden, dass ein Debugger Werte von Enumvariablen symbolisch darstellen kann und dass sie den Sichtbarkeitsregeln von C unterliegen. (Ein Compiler kann Warnungen erzeugen, wenn Enums und Ints gemischt verwendet werden, da so etwas immer noch als schlechter Stil angesehen werden kann, selbst wenn es nicht strikt illegal ist.) Ein Nachteil ist, dass der Programmierer nur wenig Kontrolle über die Größe von Enums hat (oder über diese Warnungen).

Referenzen: K&R II Sec. 2.3 p. 39, Sec. A4.2 p. 196; H&S Sec. 5.5 p. 100; ANSI Secs. 3.1.2.5, 3.5.2, 3.5.2.2 .


Frage 9.2: Ich habe gehört, Structs könnten Variablen zugewiesen werden und an oder von Funktionen übergeben werden, aber K&R I spricht dagegen.

Antwort: Was K&R I sagte, war dass die Beschränkungen der Structoperationen in einer nachfolgenden Version des Compilers behoben sein würden, und tatsächlich waren Structzuweisung und -übergabe in Ritchies Compiler bereits voll funktionstüchtig, als K&R I veröffentlicht wurde. Obwohl einige wenige C Compiler keine Zuweisung von Structs konnten, unterstützen es alle modernen Compiler, so dass keine Probleme bei der Verwendung entstehen sollten.

Referenzen: K&R I Sec. 6.2 p. 121; K&R II Sec. 6.2 p. 129; H&S Sec. 5.6.2 p. 103; ANSI Secs. 3.1.2.5, 3.2.2.1, 3.3.16 .


Frage 9.3: Wie funktioniert die Über- und Rückgabe von Structs?

Antwort: Wenn Structs als Argumente an Funktionen übergeben werden, wird typischerweise die gesamte Struct auf den Stack kopiert, dabei werden so viele (Maschinen-)wörter wie nötig verwendet. (Programmierer entscheiden sich häufig, Pointer auf Strukturen zu verwenden, um diesen Overhead zu vermeiden.)

Für die Rückgabe von Structs als Funktionsergebnis wird oft ein nicht sichtbarer Parameter an eine solche Funktion verwendet, der vom Compiler automatisch übergeben wird. Dieser Parameter ist ein Zeiger auf einen Speicherbereich, an dem das Funktionsergebnis abgelegt wird. Einige ältere Compiler haben auch einen statischen belegten Speicherplatz als Platz für das Rückgabeergebnis verwendet, dadurch wurden solche Funktionen nicht-reentrant (wiedereintrittsfähig), was ANSI verbietet.

Referenzen: ANSI Sec. 2.2.3 p. 13.


Frage 9.4: Das folgende Programm arbeitet korrekt, bricht jedoch nach dem beenden mit einem core-dump ab. Warum?

	struct list
		{
		char *item
		struct list *next;
		}

	/* Nun das Hauptprogramm */

	main(int argc, char *argv[])
	...

Antwort: Ein fehlendes Semikolon macht den Compiler glauben, main gebe eine Struct zurück. (Die Verbindung ist wegen des dazwischen liegenden Kommentars schwer zu erkennen.) Da Funktionen mit einer Struct als Funktionsergebnis gewöhnlich durch Hinzufügen eines versteckten Arguments implementiert werden, versucht der erzeugte Code für main drei Argumente zu akzeptieren, obwohl nur zwei übergeben wurden (in diesem Falle vom Startup-Code). Siehe auch Frage 17.21.

Referenzen: CT&P Sec. 2.3 pp. 21-2.


Frage 9.5: Warum kann man Structs nicht vergleichen?

Antwort: Es gibt keinen vernünftigen Weg für einen Compiler, Vergleiche von Structs zu implementieren, der konsistent zu C's low-level Konzept ist. Ein Byte für Byte Vergleich könnte durch zufällige Bits in den Löchern einer Struktur (wenn Padding verwendet wird, um die Ausrichtung der späteren Felder korrekt zu halten; siehe Fragen 9.10, 9.11) verfälscht werden. Ein Feld für Feld Vergleich würde bei großen Strukturen inakzeptable Mengen an wiederholtem Inlinecode benötigen.

Zum Vergleich von zwei Structs kommt man nicht umhin, eine Funktion zu schreiben, die das tut. Unter C++ kann dazu der == Operator überladen werden.

References: K&R II Sec. 6.2 p. 129; H&S Sec. 5.6.2 p. 103; ANSI Rationale Sec. 3.3.9 p. 47.


Frage 9.6: Wie kann ich Stucts aus/in Datendateien lesen/schreiben?

Antwort: Es ist relativ naheliegend, einen Struct mittels fwrite zu schreiben:

	fwrite((char*)&somestruct, sizeof(somestruct), 1, fp);

und ein passender Aufruf von fread kann es wieder einlesen. Wie auch immer, Datendateien, die so geschrieben wurden sind _nicht_ sehr portabel (Siehe auch Fragen 9.11 und 17.3). Bei vielen Systemen muß das "b" flag beim fopen verwendet werden.


Frage 9.7: Ich stolperte über Code, der einen Struct wie diesen hier deklarierte:

	struct name
		{
		int namelen;
		char name[1];
		};

Und dann mittels trickreicher Allokation das Array name so tun ließ als hätte es mehrere Elemente. Ist das legal und/oder portabel?

Antwort: Diese Technik ist verbreitet, obwohl Dennis Ritchie es "not warranted chumminess with the C implementation" (frei: Ausnutzen nicht garantierter Eigenschaften der C Implementation) nannte. Eine ANSI Interpretationsregel meinte, es (präziser: Zugriff über die deklarierte Feldgröße hinaus) sei nicht strikt konform; obwohl eine gründliche Behandlung der Argumente um die Legalität der Technik über den Rahmen dieser Liste hinausgeht. Wie auch immer scheint es auf alle bekannten Implementationen portabel zu sein. (Compiler, die Arraygrenzen sorgfältig überprüften könnten Warnungen ausgeben.)

Um auf der sicheren Seite zu sein, ist es vorzuziehen das Element variabler Größe sehr groß anstelle sehr klein zu deklarieren; für das obige Beispiel:

	...
	char name[MAXSIZE]
	...

wobei MAXSIZE größer als jedes zu speichernde Name ist. Dem so angepaßten Trick wird ANSI-Konformität nachgesagt.

References: ANSI Rationale Sec. 3.5.4.2 pp. 54-5.


Frage 9.8: Wie kann ich den Byteoffset eines Elements in einer Struct ermitteln?

Antwort: ANSI C definiert das offsetof() Makro, das, so vorhanden, verwendet werden sollte; siehe <stddef.h>. Wenn es nicht existiert, hier ist eine mögliche Implementation:

	#define offsetof(type, mem) ((size_t) \
		((char *)&((type *) 0)->mem - (char *)((type *) 0)))

Diese Implementation ist nicht 100% portabel; einige Compiler akzeptieren sie (legitimerweise) nicht.

Siehe die nächste Frage für einen Nutzungshinweis.

References: ANSI Sec. 4.1.5, Rationale Sec. 3.5.4.2 p. 55.


Frage 9.9: Wie kann ich auf Structfelder zur Laufzeit per Namen zugreifen?

Antwort: Baue eine Tabelle mit Namen und Offsets, das offsetof() Makro verwendend. Der Offset von Feld b in Struct a ist

	offsetb = offsetof(struct a, b)

Wenn structp ein Pointer auf ein Exemplar dieser Struktur und b ein Int-Feld mit offset, wie oben angegeben, kann b's Wert indirekt mittels

	*(int *)((char *)structp + offsetb) = value

gesetzt werden.


Frage 9.10: Warum ergibt sizeof für einen Strukturtyp, eine größere Größe als ich erwarte, so als ob da Padding am Ende wäre?

Antwort: Strukturen können dieses Padding haben (wie auch internes Padding; siehe auch Frage 9.5), so dass Alignmenteigenschaften erhalten bleiben, wenn ein Array von zusammenhängenden Strukturen alloziert wird.


Frage 9.11: Mein Compiler läßt Löcher in Strukturen, was Platz verschwendet und binäres I/O nach externen files verhindert. Kann ich das Padding ausstellen, oder das Alignment von Strukturen anderweitig kontrollieren?

Antwort: Der Compiler hat möglicherweise eine Erweiterung, die eine Kontrolle über das Alignment erlaubt (vielleicht ein #pragma), aber es gibt keine Standardmethode. Siehe auch Frage 17.3


Frage 9.12: Kann ich Unions initialisieren?

Antwort: Der ANSI C Standard erlaubt einen Initialisierer für das erste Element eines Union. Es gibt keinen Standardweg, die anderen Elemente zu initialisieren (unter einem prä-ANSI Compiler, gibt es gar keinen Weg überhaupt eines der Elemente zu initialisieren).


Frage 9.13: Wie kann ich konstante Werte an Routinen übergeben, die Struct-Parameter akzeptieren?

Antwort: C kennt keinen Weg, anonyme Struct-Werte zu erzeugen. Man muß temporäre Structvariablen verwenden.

[ Inhalt ][ Index ][ ZurÜck ][ Weiter ]


[ FAQ Logo ]   © 1997-2004 Jochen Schoof (joscho@bigfoot.de)
Diese Version wurde am 14. März 2004 erzeugt. Sie wird zukünftig nicht weiter gepflegt.