Frage

  

Mögliche Duplizieren:
   Pre & Post Inkrementoperator Verhalten in C, C ++, Java & C #

Hier ist ein Testfall:


void foo(int i, int j)
{
   printf("%d %d", i, j);
}
...
test = 0;
foo(test++, test);

Ich würde erwarten, ein „0 1“ -Ausgabe zu bekommen, aber ich erhalte „0 0“ Was gibt ??

War es hilfreich?

Lösung

Dies ist ein Beispiel eines nicht näher bezeichneten Verhalten. Der Standard hat nicht sagen, was um Argumente bewertet werden soll. Dies ist eine Compiler-Implementierung Entscheidung. Der Compiler ist frei, um die Argumente für die Funktion in beliebiger Reihenfolge zu bewerten.

In diesem Fall sieht es aus wie eigentlich die Argumente verarbeitet rechts nach links statt dem links nach rechts erwartet.

In der Regel tun Nebenwirkungen in Argumenten ist schlechter Programmier.

Anstelle von foo (Test ++ Test); Sie sollten schreiben foo (Test, Test + 1); Test ++;

Es wäre zu semantisch äquivalent sein, was Sie versuchen zu erreichen.

Edit: Wie Anthony richtig ausgeführt hat, ist es nicht definiert sowohl eine einzelne Variable, ohne eine dazwischenliegende Sequenz Punkt lesen und modifizieren. Also in diesem Fall, das Verhalten ist in der Tat undefined . So ist der Compiler ist frei zu erzeugen, was Code es will.

Andere Tipps

Dies ist nicht nur nicht spezifiziert Verhalten, es ist eigentlich nicht definiertes Verhalten .

Ja, die Reihenfolge der Argumentation Bewertung ist nicht spezifiziert , aber es ist undefined lesen und eine einzelne Variable ohne eine dazwischenliegende Sequenz Punkt ändern, wenn die Lese allein ist für der Zweck der neue Wert der Berechnung. Es gibt keinen Sequenzpunkt zwischen den Bewertungen der Funktionsargumente, so f(test,test++) ist nicht definiertes Verhalten : test für ein Argument gelesen wird und für die andere geändert. Wenn Sie die Änderung in eine Funktion zu bewegen, dann sind Sie fein:

int preincrement(int* p)
{
    return ++(*p);
}

int test;
printf("%d %d\n",preincrement(&test),test);

Das ist, weil es ein Sequenzpunkt bei der Ein- und Ausfahrt ist preincrement, so muss der Aufruf ausgewertet wird entweder vor oder nach dem einfachen Lesen. Jetzt ist die Reihenfolge nur nicht spezifiziert .

Beachten Sie auch, dass das Komma Operator stellt einen Sequenzpunkt, so

int dummy;
dummy=test++,test;

ist in Ordnung --- der Zuwachs vor dem Lesen geschieht, so dummy auf den neuen Wert gesetzt.

Alles, was ich ursprünglich gesagt, falsch! Der Zeitpunkt, an dem die Seiten beeinflussen wird berechnet wird, nicht spezifiziert. Visual C ++ den Zuwachs nach dem Aufruf durchführen werden foo (), wenn der Test eine lokale Variable ist, aber wenn der Test als statisches oder global deklariert wird, wird sie erhöht werden, bevor der Aufruf von foo () und zu unterschiedlichen Ergebnissen führen, obwohl die Endwert Test korrekt sein.

Der Zuwachs wirklich in einer gesonderten Erklärung nach dem Aufruf getan werden soll, um foo (). Auch wenn das Verhalten in der C / C ++ angegeben wurde Standard wäre es verwirrend sein. Sie würden denken, dass C ++ Compiler moniert dies als mögliche Fehler.

Hier ist eine gute Beschreibung von Sequenzpunkte und nicht näher bezeichnete Verhalten.

<---- BEGINN FALSCH FALSCH FALSCH ---->

Die „++“ bisschen „test ++“ nach dem Aufruf ausgeführt wird foo. So kommt man in (0,0) auf foo, nicht (1,0)

Hier ist die Assembler Ausgabe von Visual Studio 2002:

mov ecx, DWORD PTR _i$[ebp]
push    ecx
mov edx, DWORD PTR tv66[ebp]
push    edx
call    _foo
add esp, 8
mov eax, DWORD PTR _i$[ebp]
add eax, 1
mov DWORD PTR _i$[ebp], eax

Der Zuwachs wird nach dem Aufruf getan foo (). Während dieses Verhalten von Entwurf ist, ist es für den flüchtigen Leser sicherlich verwirrend und wahrscheinlich vermieden werden sollte. Der Zuwachs sollte wirklich in einer gesonderten Erklärung erfolgen, nachdem der Anruf () foo

<---- ENDE FALSCH FALSCH FALSCH ---->

Es ist „nicht spezifiziert Verhalten“, sondern mit der Art und Weise in der Praxis des C Call-Stack garantiert fast immer angegeben wird, dass Sie es als 0, 0 sehen und nie 1, 0.

Als jemand erwähnt, die Assembler Ausgabe von VC drückt die am weitesten rechts stehenden Parameter auf dem Stack. Dies ist, wie C-Funktionsaufrufe in Assembler implementiert sind. Dies ist C-Schema „endlose Parameterliste“ Funktion gerecht zu werden. Durch die Parameter in einem von rechts nach links, um drücken, der ersten Parameter ist garantiert das oberste Element auf dem Stapel sein.

Nehmen Sie printf Unterschrift:

int printf(const char *format, ...);

Diese Ellipsen bezeichnen eine unbekannte Anzahl von Parametern. Wenn die Parameter von links nach rechts geschoben wurden, am unteren Rand das Format eines Stapels sein würde, von denen wir nicht wissen, die Größe.

, dass in C Wissen (und C ++), die Parameter verarbeitet werden, von links nach rechts, können wir die einfachste Art der Analyse bestimmen und einen Funktionsaufruf zu interpretieren. Erhalten Sie bis zum Ende der Parameterliste, und starten Sie drängen, Auswertung keine komplexen Anweisungen, wie Sie gehen.

Aber auch dies kann man nicht speichern, da die meisten C-Compiler eine Option Funktionen „Pascal-Stil“ zu analysieren. Und all dies bedeutet, dass die Funktionsparameter auf dem Stapel in einer von links nach rechts Art und Weise gedrückt werden. Wenn zum Beispiel printf mit der Pascal-Option kompiliert wurde, dann würde der Ausgang am ehesten 1, 0 (da jedoch printf die Ellipse verwendet, ich glaube nicht, dass es Pascal-Stil erstellt werden).

C garantiert nicht die Reihenfolge der Auswertung von Parametern in einem Funktionsaufruf, so damit Sie die Ergebnisse bekommen könnten „0 1“ oder „0 0“. Die Bestellung kann vom Compiler Compiler ändern, und der gleiche Compiler verschiedene Aufträge auf Basis von Optimierungsparametern wählen könnte.

Es ist sicherer, foo (Test, Test + 1) zu schreiben und dann tut ++ Test in der nächsten Zeile. Auf jeden Fall sollte der Compiler es, wenn möglich, optimieren.

Der Compiler nicht die Argumente in der Reihenfolge könnte Auswertung man erwarten würde.

Die Reihenfolge der Auswertung für Argumente an eine Funktion ist nicht definiert. In diesem Fall scheint es, dass es sie von rechts nach links hat.

(Variablen zwischen Sequenzpunkten ändern kann grundsätzlich einen Compiler, etwas zu tun es will.)

Um, nun, da die OP für Konsistenz bearbeitet wurde, ist es nicht mehr synchron mit den Antworten. Die grundsätzliche Antwort über Reihenfolge der Auswertung ist richtig. Jedoch sind die spezifischen Werte möglich sind unterschiedlich für die foo (++ test, test); Fall.

++ Test wird vor vergangen erhöht werden wird, so dass das erste Argument wird immer 1 sein Das zweite Argument 0 sein oder 1 je nach Auswerteauftrag.

Nach dem C-Standard ist es nicht definiertes Verhalten mehr als eine Bezugnahme auf eine Variable zu haben, in einem einzigen Sequenz Punkt (hier kann man daran denken, wie eine Aussage zu sein, oder Parameter an eine Funktion), wo einer von mehrer diese Referenzen umfassen eine Pre / Post-Modifikation. Damit: foo (f ++, f) <- nicht definiert, wann Schritte f. Und ebenso (ich sehe das die ganze Zeit in Benutzer-Code): * P = p ++ + p;

Typischerweise wird ein Compiler nicht sein Verhalten für diese Art der Sache ändern (mit Ausnahme der größeren Revisionen).

es vermeiden, indem Sie auf Warnungen drehen und die Aufmerksamkeit auf sich.

Um es zu wiederholen, was andere gesagt haben, ist dies nicht nicht spezifiziert Verhalten, sondern undefiniert. Dieses Programm kann rechtlich Ausgang alles oder nichts, lassen Sie n auf einem beliebigen Wert, oder beleidigende E-Mail an Ihren Chef schicken.

Als eine Frage der Praxis Compiler Schriftsteller wird in der Regel nur das tun, was für sie am einfachsten zu schreiben, was bedeutet im Allgemeinen, dass das Programm holen wird n ein- oder zweimal, rufen Sie die Funktion, und irgendwann erhöhen. Dies, wie auch jede andere denkbare Verhalten ist ganz gut nach dem Standard. Es gibt keinen Grund, das gleiche Verhalten zwischen Compiler zu erwarten, oder Versionen oder mit anderen Compiler-Optionen. Es gibt keinen Grund, warum zwei verschiedene, aber ähnlich aussehenden Beispiele im selben Programm haben konsequent kompiliert werden, aber das ist die Art und Weise, das ich tippe.

Kurz gesagt, dies nicht tun. Testen Sie es unter anderen Umständen, wenn Sie neugierig sind, aber nicht behauptet, dass es nur ein richtiges oder sogar vorhersagbares Ergebnis ist.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top