|
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 4.1: Warum funktioniert dieser Schnippsel a[i] = i++; nicht? Antwort: Der Teilausdruck i++ besitzt einen Seiteneffekt - er ändert den Wert von i. Wird der Wert von i nun an anderer Stelle in diesem Ausdruck benutzt, dann führt dies zu undefiniertem Verhalten. (In K&R wird vorgeschlagen, dass das Verhalten für diesen Ausdruck nicht festgelegt sein soll. Der ANSI/ISO Standard geht allerdings noch einen Schritt weiter und erklärt: solche Ausdrücke führen zu undefiniertem Verhalten - siehe 5.23) Siehe hierzu: ANSI Anschnitt 3.3 Seite 39. Frage 4.2: Bei meinem Compiler liefert int i = 7; printf("%d\n", i++ * i++); 49. Sollte das Ergebnis nicht 56 lauten, egal in welcher Reihenfolge die Terme ausgewertet werden? Antwort: Die Postinkrement und -dekrement Operatoren ++ und -- führen ihren Seiteneffekt erst aus, nachdem sie den vorherigen Wert ausgegeben haben. Das "nachdem" in dieser Aussage wird dabei aber oft mißverstanden: Der Seiteneffekt muß sich nicht sofort auswirken, nachdem der ursprüngliche Wert der Variablen ausgegeben wurde; er kann sich erst auswirken, nachdem andere Teilausdrücke bearbeitet wurden. Das einzige, auf das man sich verlassen kann, ist, dass alle Seiteneffekte berücksichtigt wurden, nachdem der Ausdruck "vollständig" berechnet wurde (vor dem nächsten "sequence point" in ANSI C Terminologie). Im obigen Beispiel kann der Compiler zuerst die beiden ursprünglichen Werte miteinander multiplizieren und danach alle Seiteneffekte berücksichtigen. Von jeher war das Verhalten von Programmen, die vielfältige, zweideutige Seiteneffekte in Ausdrücken benutzen undefiniert ("vielfältige, zweideutige Seiteneffekte" steht hier für eine Kombination von ++, --, =, +=, usw. innerhalb eines Ausdrucks, bei dem ein Objekt mehrfach verändert oder verändert und benutzt wird. Dies ist nur eine sehr grobe Definition. Für die Definition von "undefiniert" siehe 5.23). Am besten versuchst man erst gar nicht herauszufinden, wie der Compiler solche Ausdrücke auswertet (im Gegensatz zu einigen schlecht gestellten Aufgaben in vielen C-Büchern); wie K&R in seiner Weisheit sagt: ``wenn Du nicht weißt, _wie_ dies auf verschiedenen Maschinen gemacht wird, kann Dich diese Unschuld vor Fehlern bewahren.'' Siehe hierzu: K&R I Abschnitt 2.12 Seite 50; K&R II Abschnitt 2.12 Seite 54; ANSI Abschnitt 3.3 Seite 39; CT&P Abschnitt 3.7 Seite 47; PCS Abschnitt 9.5 Seiten 120-121; (vergiß H&S Abschnitt 7.12 Seiten 190-191, das ist überholt.) Frage 4.3: Ich habe ein wenig mit int i = 2; i = i++; experimentiert. Bei manchen Compilern hatte i danach den Wert 2, bei anderen 3 und bei einigen 4. Ich verstehe ja, dass das Verhalten undefiniert ist, aber wie kann dabei 4 herauskommen? Antwort: Wenn ein Verhalten undefiniert ist, kann alles passieren [Anm. Uz: von einem korrekten Verhalten bis zur Auslösung des dritten Weltkriegs - um Thomas König aus dem Gedächtnis zu zitieren]. Siehe 5.23. Frage 4.4: Kann ich die Reihenfolge, in der Teilausdrücke ausgewertet werden, nicht durch Klammern festlegen? Selbst ohne Klammern sollte die Reihenfolge doch durch die Rangfolge der Operatoren vorgeschrieben sein. Antwort: Die Hierarchie der Operatoren und Klammern legen nur zum Teil fest, in welcher Reihenfolge die Teilausdrücke ausgewertet werden. Im Beispiel f() + g() * h() ist nur sicher, dass die Multiplikation vor der Addition ausgeführt wird. Welche der drei Funktionen zuerst aufgerufen wird, ist allerdings undefiniert. Wenn die Reihenfolge, in der Teilausdrücke ausgewertet werden, wichtig ist, führt an temporären Variablen kein Weg vorbei. Frage 4.5: Und wie steht es mit den &&-, ||- und Komma-Operatoren? Ich sehe häufiger Ausdrücke der Form if((c = getchar()) == EOF || c == '\n') ... Antwort: Für diese Operatoren (sowie für den ?:-Operator) gilt tatsächlich eine Ausnahme; Jeder einzelne dieser Operatoren stellt einen "sequence point" dar (d.h. der Ausdruck wird von links nach rechts ausgewertet). Dies sollte in jedem C-Buch klargestellt werden. Siehe hierzu: K&R I Abschn. 2.6 S. 38, Abschn. A7.11-12 S. 190-191; K&R II Abschn. 2.6 S. 41, Abschn. A7.14-15 S. 207-208; ANSI Abschn. 3.3.13 S. 52, 3.3.14 S. 52, 3.3.15 S. 53, 3.3.17 S.55; CT&P Abschnitt 3.7 Seiten 46-47 Frage 4.6: Wenn ich den Wert eines Ausdrucks nicht brauche, sollte ich i++ oder ++i verwenden, um den Wert von i zu erhöhen? Antwort: Die beiden Formen unterscheiden sich nur im Wert, den sie weitergeben. Wenn es nur um den Seiteneffekt geht, sind sie völlig äquivalent. (Weder i++ noch ++i bedeuten dasselbe wie i+1. Wenn Du i um 1 erhöhen willst, benutze i=i+1 oder i++ oder ++i, keine Kombination dieser Möglichkeiten.) Frage 4.7: Warum funktioniert int a = 1000, b = 1000; long int c = a * b; nicht? Antwort: Nach den C Regeln für die integrale Erweiterung wird die Multiplikation mit int-Arithmetik ausgeführt. Das Resultat kann dabei zu groß werden, um von einem int gehalten zu werden, und deshalb abgeschnitten werden, bevor es an die long-int-Variable auf der linken Seite übergeben wird. Eine explizite Typkonversion auf der rechten Seite sorgt dafür, dass long-int-Arithmetik benutzt wird: long int c = (long int)a * b; (long int)(a * b) führt im übrigen nicht zum gewünschten Resultat. Ein ähnliches Problem stellt sich, wenn zwei integrale Objekte in einer Division benutzt werden und das Ergebnis einer Gleitkomma-Variablen zugewiesen wird (ohne ausdrückliche Typkonversion wird die Ganzzahldivision ausgeführt).
© 1997-2004 Jochen Schoof (joscho@bigfoot.de) Diese Version wurde am 14. März 2004 erzeugt. Sie wird zukünftig nicht weiter gepflegt. |