Eine 'for'-Schleife, die praktisch unendlich zu sein scheint

82

Ich debugge gerade Code und bin auf diese Zeile gestoßen:

for (std::size_t j = M; j <= M; --j)

(Geschrieben von meinem Chef, der im Urlaub ist.)

Es sieht für mich wirklich seltsam aus.

Was tut es? Für mich sieht es aus wie eine Endlosschleife.

Michael Bullock
quelle
31
size_tist ohne Vorzeichen, wird also garantiert auf seinen Maximalwert umbrochen, wenn versucht wird, Null zu verwerfen, wodurch die Schleife beendet wird. Trotzdem schrecklicher Code.
BoBTFish
4
@Bathsheba Was ist, wenn Mzufällig der Maximalwert für ist size_t? Findest du es immer noch klug?
Thorsten Dittmar
8
@Barmar Kommt nicht viel vor ist nicht kommt nie auf , also gibt es eine Wahrscheinlichkeit> 0. Was bedeutet, dass diese Lösung dumm ist, wenn es Möglichkeiten gibt, es einfach auszudrücken .
Thorsten Dittmar
38
Ich verstehe das Downvoting nicht. Diese Frage ist klar - ich kann nicht sehen, wie sie verbessert werden könnte. Von einem neuen Benutzer ist diese Frage tatsächlich interessant !
Bathseba
17
#define TRUE FALSEund in den Urlaub fahren.
Leo Heinsaar

Antworten:

68

std::size_twird durch den C ++ - Standard als unsignedTyp garantiert . Und wenn Sie einen unsignedTyp von 0 dekrementieren , garantiert der Standard, dass das Ergebnis davon der größte Wert für diesen Typ ist.

Dieser umlaufende Wert ist immer größer oder gleich M1, sodass die Schleife endet.

Also , j <= Mwenn auf ein angelegtes unsignedTyp ist eine bequeme Art zu sagen , „die Schleife auf Null dann Anschlag laufen“.

Es gibt Alternativen wie das Ausführen jeiner größeren als der gewünschten und sogar die Verwendung des Folienoperators for (std::size_t j = M + 1; j --> 0; ){ , die wohl klarer sind, obwohl mehr Eingabe erforderlich ist. Ich denke jedoch, dass ein Nachteil (abgesehen von dem verwirrenden Effekt, den es bei der ersten Überprüfung hervorruft) darin besteht, dass es nicht gut auf Sprachen ohne vorzeichenlose Typen wie Java portiert werden kann.

Beachten Sie auch, dass das Schema, das Ihr Chef ausgewählt hat, einen möglichen Wert aus dem unsignedSatz "ausleiht" : In diesem Fall hat der MSatz auf std::numeric_limits<std::size_t>::max()nicht das richtige Verhalten. In diesem Fall ist die Schleife tatsächlich unendlich . (Beobachten Sie das?) Sie sollten einen entsprechenden Kommentar in den Code einfügen und möglicherweise sogar eine bestimmte Bedingung geltend machen.


1 Vorbehaltlich des MNicht-Seins std::numeric_limits<std::size_t>::max().

Bathseba
quelle
7
Ich beschuldige das Wetter.
Sombrero Chicken
3
Wenn M std::numeric_limits<std::size_t>::max()dann M + 1Null ist, wird die for (std::size_t j = M + 1; j --> 0; )Schleife überhaupt nicht wiederholt.
Pete Kirkham
51
Nennen Sie es nicht den "Folienoperator". In C ++ gibt es keinen solchen Operator, es ist nur eine clever aussehende Verschleierung.
Sie
26
@ DimitarMirchev: Weil es kein Operator ist. Es sind zwei Operatoren mit ungeraden Abständen. Nennen Sie es eine Redewendung, wenn Sie darauf bestehen, den Befragten irrelevante Fragen zu stellen (aber im Idealfall fragen Sie sie, wie sie die Funktionalität implementieren würden, anstatt nach der "cleveren" Syntax zu fragen).
Sie
1
Unter der Annahme, dass size_tes sich um 64 Bit handelt, dauert es mehrere hundert Jahre, bis das falsche Verhalten im Randfall festgestellt wird. (Außer wenn der Optimierer die Schleife loswerden kann.)
Carsten S
27

Was Ihr Chef wahrscheinlich versucht hat , war, von Mbis einschließlich Null herunterzuzählen und für jede Zahl eine Aktion auszuführen.

Leider gibt es einen Randfall, bei dem Sie tatsächlich eine Endlosschleife erhalten, bei der Mes sich um den Maximalwert handelt size_t, den Sie haben können. Und obwohl genau definiert ist, was ein vorzeichenloser Wert bewirkt, wenn Sie ihn von Null dekrementieren, behaupte ich, dass der Code selbst ein Beispiel für schlampiges Denken ist, zumal es eine absolut praktikable Lösung gibt, ohne dass die Mängel Ihres Chefs es versuchen.

Diese sicherere Variante (und meiner Meinung nach besser lesbar, während immer noch ein enges Geltungsbereich eingehalten wird) wäre:

{
    std::size_t j = M;
    do {
        doSomethingWith(j);
    } while (j-- != 0);
}

Siehe beispielsweise den folgenden Code:

#include <iostream>
#include <cstdint>
#include <climits>
int main (void) {
    uint32_t quant = 0;
    unsigned short us = USHRT_MAX;
    std::cout << "Starting at " << us;
    do {
        quant++;
    } while (us-- != 0);
    std::cout << ", we would loop " << quant << " times.\n";
    return 0;
}

Dies macht im Grunde dasselbe mit einem unsigned shortund Sie können sehen, dass es jeden einzelnen Wert verarbeitet:

Starting at 65535, we would loop 65536 times.

Das Ersetzen der do..whileSchleife im obigen Code durch das, was Ihr Chef im Grunde getan hat, führt zu einer Endlosschleife. Probieren Sie es aus und sehen Sie:

for (unsigned int us2 = us; us2 <= us; --us2) {
    quant++;
}
paxdiablo
quelle
7
Nun ist der Randfall 0. Warum nicht for(size_t j = M; j-- != 0; )?
LogicStuff
Ja, dies leidet darunter, dass der Eckfall möglicherweise häufiger auftritt als numeric_limits<size_t>::max().
Bathsheba
7
@Logic et al., Die von mir bereitgestellte Methode enthält keinen Randfall. Ich habe Code hinzugefügt, um dies zu zeigen. Mit Ihrer vorgeschlagenen Lösung wird ein Anfangswert von M == 0führt in das Element 0 nicht verarbeitet werden, so dass es tut eine Kante Fall haben. Durch die Verwendung meiner Post-Check- do..whileMethode wird der Randfall vollständig beseitigt. Wenn Sie es mit versuchen M == 1, werden Sie sehen, dass es sowohl 1 als auch 0 macht. Beginnen Sie es in ähnlicher Weise mit max_size_t(was auch immer das sein mag ) und es startet erfolgreich an diesem Punkt und dekrementiert dann auf und einschließlich Null.
Paxdiablo
Interessanterweise motiviert dieser Fall die Verwendung von "nicht strukturiertem Code", weil er ein " exitif" verwendet, nämlich { j =M; for(;;){ f(j); if( j == 0 )break; j -= 1; } }. Möglicherweise muss das sogar durch breaka ersetzt werden, gotowenn Dinge verschachtelt werden, da C keine benannten Schleifen hat. Wenn „strukturiert“ „anfällig für Überlegungen zu Chunks“ bedeutet, ist es strukturiert (Layout hilft!) Und auch, wenn es „anfällig für formale Validierung durch Überlegungen zu Vor- und Nachbedingungen“ bedeutet. Obwohl dies in diesem Fall j--funktioniert, kann der Exitif-Stil gerechtfertigt sein, wenn ein komplizierterer Übergang erforderlich ist.
PJTraill
@PJTraill Ich kann nicht sagen, was Sie sagen, ist strukturiert und was Sie sagen, ist nicht strukturiert. Das Nachdenken über Do-While ist unkompliziert. Es "benutzt" einen Exit nicht mehr als oder auf unstrukturierte Weise, während dies der Fall ist.
philipxy