Das ist UB; In ISO C ++ ist das gesamte Verhalten des gesamten Programms für eine Ausführung, die schließlich UB trifft, völlig unbestimmt . Das klassische Beispiel ist, was den C ++ - Standard betrifft, dass Dämonen aus der Nase fliegen können. (Ich empfehle, keine Implementierung zu verwenden, bei der Nasendämonen eine echte Möglichkeit sind). Weitere Antworten finden Sie in anderen Antworten.
Compiler können zur Kompilierungszeit "Probleme verursachen", wenn Ausführungspfade angezeigt werden, die zu einer zur Kompilierungszeit sichtbaren UB führen. Nehmen wir beispielsweise an, dass diese Basisblöcke niemals erreicht werden.
Siehe auch Was jeder C-Programmierer über undefiniertes Verhalten wissen sollte (LLVM-Blog). Wie dort erläutert, können Compiler mit UB mit signiertem Überlauf beweisen, dass for(... i <= n ...)
Schleifen auch für Unbekannte keine Endlosschleifen sind n
. Außerdem können sie int-Schleifenzähler auf Zeigerbreite "heraufstufen", anstatt die Vorzeichenerweiterung zu wiederholen. (Die Konsequenz von UB in diesem Fall könnte also der Zugriff außerhalb der Low-64k- oder 4G-Elemente eines Arrays sein, wenn Sie eine vorzeichenbehaftete Umhüllung i
in seinen Wertebereich erwarten .)
In einigen Fällen geben Compiler eine unzulässige Anweisung wie x86 ud2
für einen Block aus, der nachweislich UB verursacht, wenn er jemals ausgeführt wird. (Beachten Sie, dass eine Funktion möglicherweise nicht immer aufgerufen werden, so Compiler kann im Allgemeinen nicht Amok und andere Funktionen brechen oder sogar mögliche Pfade durch eine Funktion , die UB nicht betroffen. Dh der Maschinencode kompiliert es muss noch viel Arbeit für alle Eingänge, die nicht zu UB führen.)
Die wahrscheinlich effizienteste Lösung besteht darin, die letzte Iteration manuell zu schälen, damit unnötige Iterationen factor*=10
vermieden werden können.
int result = 0;
int factor = 1;
for (... i < n-1) { // stop 1 iteration early
result = ...
factor *= 10;
}
result = ... // another copy of the loop body, using the last factor
// factor *= 10; // and optimize away this dead operation.
return result;
Wenn der Schleifenkörper groß ist, sollten Sie einfach einen vorzeichenlosen Typ für verwenden factor
. Dann können Sie den vorzeichenlosen Multiplikationsüberlauf zulassen, und es wird nur ein genau definierter Umbruch mit einer Potenz von 2 (der Anzahl der Wertbits im vorzeichenlosen Typ) durchgeführt.
Dies ist auch dann in Ordnung, wenn Sie es mit signierten Typen verwenden, insbesondere wenn Ihre nicht signierte> signierte Konvertierung nie überläuft.
Die Konvertierung zwischen vorzeichenlosem und vorzeichenbehaftetem 2er-Komplement ist kostenlos (gleiches Bitmuster für alle Werte). Das im C ++ - Standard angegebene Modulo-Wrapping für int -> unsigned vereinfacht die Verwendung des gleichen Bitmusters, anders als für das eigene Komplement oder Vorzeichen / die eigene Größe.
Und unsigned-> signiert ist ähnlich trivial, obwohl es für Werte größer als implementiert ist INT_MAX
. Wenn Sie nicht mit dem riesigen unsigned Ergebnis der letzten Iteration, haben Sie nichts zu befürchten. Wenn ja, lesen Sie Ist die Konvertierung von nicht signiert zu signiert undefiniert? . Der Fall "Wert passt nicht" ist implementierungsdefiniert. Dies bedeutet, dass eine Implementierung ein bestimmtes Verhalten auswählen muss . Vernünftige schneiden das vorzeichenlose Bitmuster einfach ab (falls erforderlich) und verwenden es als vorzeichenbehaftet, da dies für Werte im Bereich auf die gleiche Weise ohne zusätzliche Arbeit funktioniert. Und es ist definitiv nicht UB. So können große Werte ohne Vorzeichen zu Ganzzahlen mit negativen Vorzeichen werden. zB nach int x = u;
gcc und clang nicht weg optimierenx>=0
wie immer wahr, auch ohne -fwrapv
, weil sie das Verhalten definiert haben.