Ihre Frage war wahrscheinlich nicht "Warum sind diese Konstrukte undefiniertes Verhalten in C?". Ihre Frage war wahrscheinlich: "Warum hat mir dieser Code (mit ++
) nicht den erwarteten Wert gegeben?", Und jemand hat Ihre Frage als Duplikat markiert und Sie hierher geschickt.
Diese Antwort versucht, diese Frage zu beantworten: Warum hat Ihr Code Ihnen nicht die erwartete Antwort gegeben, und wie können Sie lernen, Ausdrücke zu erkennen (und zu vermeiden), die nicht wie erwartet funktionieren.
Ich gehe davon aus, dass Sie inzwischen die grundlegende Definition von Cs ++
und --
Operatoren gehört haben und wie ++x
sich das Präfixformular vom Postfixformular unterscheidet x++
. Aber diese Operatoren sind schwer zu überlegen. Um sicherzugehen, dass Sie verstanden haben, haben Sie vielleicht ein kleines Testprogramm geschrieben, das so etwas beinhaltet
int x = 5;
printf("%d %d %d\n", x, ++x, x++);
Zu Ihrer Überraschung hat Ihnen dieses Programm jedoch nicht geholfen, es zu verstehen - es druckte eine seltsame, unerwartete, unerklärliche Ausgabe, was darauf hindeutet, dass möglicherweise ++
etwas völlig anderes funktioniert, als Sie gedacht haben.
Oder vielleicht sehen Sie einen schwer verständlichen Ausdruck wie
int x = 5;
x = x++ + ++x;
printf("%d\n", x);
Vielleicht hat dir jemand diesen Code als Puzzle gegeben. Dieser Code macht auch keinen Sinn, besonders wenn Sie ihn ausführen - und wenn Sie ihn kompilieren und unter zwei verschiedenen Compilern ausführen, erhalten Sie wahrscheinlich zwei verschiedene Antworten! Was ist damit? Welche Antwort ist richtig? (Und die Antwort ist, dass beide oder keiner von ihnen sind.)
Wie Sie inzwischen gehört haben, sind alle diese Ausdrücke undefiniert , was bedeutet, dass die C-Sprache keine Garantie dafür gibt, was sie tun werden. Dies ist ein seltsames und überraschendes Ergebnis, da Sie wahrscheinlich dachten, dass jedes Programm, das Sie schreiben könnten, solange es kompiliert und ausgeführt wird, eine eindeutige, genau definierte Ausgabe erzeugen würde. Bei undefiniertem Verhalten ist dies jedoch nicht der Fall.
Was macht einen Ausdruck undefiniert? Sind Ausdrücke involviert ++
und --
immer undefiniert? Natürlich nicht: Dies sind nützliche Operatoren, und wenn Sie sie richtig verwenden, sind sie perfekt definiert.
Bei den Ausdrücken, über die wir sprechen, sind sie undefiniert, wenn zu viel auf einmal passiert, wenn wir nicht sicher sind, in welcher Reihenfolge die Dinge passieren werden, aber wenn die Reihenfolge für das Ergebnis von Bedeutung ist, erhalten wir sie.
Kehren wir zu den beiden Beispielen zurück, die ich in dieser Antwort verwendet habe. Als ich schrieb
printf("%d %d %d\n", x, ++x, x++);
Die Frage ist, printf
berechnet der Compiler vor dem Aufruf den Wert von x
first oder x++
oder oder ++x
? Aber es stellt sich heraus, dass wir es nicht wissen . In C gibt es keine Regel, die besagt, dass die Argumente für eine Funktion von links nach rechts, von rechts nach links oder in einer anderen Reihenfolge ausgewertet werden. So können wir nicht sagen , ob der Compiler tun wird x
zuerst, dann ++x
, dann x++
, oder x++
dann ++x
dann x
oder eine andere Reihenfolge. Die Reihenfolge ist jedoch eindeutig von Bedeutung, da je nach der vom Compiler verwendeten Reihenfolge eindeutig unterschiedliche Ergebnisse gedruckt werden printf
.
Was ist mit diesem verrückten Ausdruck?
x = x++ + ++x;
Das Problem mit diesem Ausdruck besteht darin, dass er drei verschiedene Versuche enthält, den Wert von x zu ändern: (1) Der x++
Teil versucht, x 1 hinzuzufügen, den neuen Wert in zu speichern x
und den alten Wert von zurückzugeben x
. (2) der ++x
Teil versucht, 1 zu x hinzuzufügen, den neuen Wert in zu speichern x
und den neuen Wert von zurückzugeben x
; und (3) der x =
Teil versucht, die Summe der beiden anderen zurück zu x zuzuweisen. Welche dieser drei versuchten Aufgaben wird "gewinnen"? Welchem der drei Werte wird tatsächlich zugewiesen x
? Wiederum und vielleicht überraschend, gibt es in C keine Regel, die es uns sagt.
Sie können sich vorstellen, dass Vorrang oder Assoziativität oder Bewertung von links nach rechts Ihnen sagen, in welcher Reihenfolge die Dinge passieren, aber nicht. Sie mögen mir nicht glauben, aber bitte nehmen Sie mein Wort dafür, und ich sage es noch einmal: Vorrang und Assoziativität bestimmen nicht jeden Aspekt der Bewertungsreihenfolge eines Ausdrucks in C. Insbesondere, wenn es innerhalb eines Ausdrucks mehrere gibt Verschiedene Stellen, an denen wir versuchen, etwas wie x
Vorrang und Assoziativität einen neuen Wert zuzuweisen , sagen uns nicht , welcher dieser Versuche zuerst oder zuletzt oder irgendetwas passiert.
Wenn Sie also sicherstellen möchten, dass alle Ihre Programme genau definiert sind, welche Ausdrücke können Sie schreiben und welche nicht?
Diese Ausdrücke sind alle in Ordnung:
y = x++;
z = x++ + y++;
x = x + 1;
x = a[i++];
x = a[i++] + b[j++];
x[i++] = a[j++] + b[k++];
x = *p++;
x = *p++ + *q++;
Diese Ausdrücke sind alle undefiniert:
x = x++;
x = x++ + ++x;
y = x + x++;
a[i] = i++;
a[i++] = i;
printf("%d %d %d\n", x, ++x, x++);
Und die letzte Frage ist, wie können Sie feststellen, welche Ausdrücke gut definiert und welche nicht definiert sind?
Wie ich bereits sagte, sind die undefinierten Ausdrücke diejenigen, bei denen zu viel auf einmal passiert, bei denen Sie nicht sicher sein können, in welcher Reihenfolge die Dinge passieren und wo die Reihenfolge wichtig ist:
- Wenn es eine Variable gibt, die an zwei oder mehr verschiedenen Stellen geändert (zugewiesen) wird, woher wissen Sie, welche Änderung zuerst erfolgt?
- Wenn es eine Variable gibt, die an einer Stelle geändert wird und deren Wert an einer anderen Stelle verwendet wird, woher wissen Sie, ob sie den alten oder den neuen Wert verwendet?
Als Beispiel für # 1 im Ausdruck
x = x++ + ++x;
Es gibt drei Versuche, `x zu ändern.
Als Beispiel für # 2 im Ausdruck
y = x + x++;
Wir beide verwenden den Wert von x
und ändern ihn.
Das ist also die Antwort: Stellen Sie sicher, dass in jedem Ausdruck, den Sie schreiben, jede Variable höchstens einmal geändert wird. Wenn eine Variable geändert wird, versuchen Sie nicht, den Wert dieser Variablen auch an einer anderen Stelle zu verwenden.