Un bucle 'for' que parece ser prácticamente infinito

82

Estoy depurando un código en este momento y me encontré con esta línea:

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

(Escrito por mi jefe, que está de vacaciones).

Me parece realmente extraño.

¿Qué hace? Para mí, parece un bucle infinito.

Michael Bullock
fuente
31
size_tno está firmado, por lo que se garantiza que se ajustará a su valor máximo cuando se intente desaprobar el cero, finalizando el ciclo. Sin embargo, sigue siendo un código horrible.
BoBTFish
4
@Bathsheba ¿Qué Mpasa si es el valor máximo para size_t? ¿Sigues pensando que es inteligente?
Thorsten Dittmar
8
@Barmar No surge mucho, no Nunca surge , por lo que hay una probabilidad> 0. Lo que significa que esta solución es estúpida cuando hay formas de ponerlo simple.
Thorsten Dittmar
38
No entiendo las votaciones negativas. Esta pregunta es clara: no veo cómo se podría mejorar. De un nuevo usuario, ¡esta pregunta es realmente interesante !
Betsabé
17
#define TRUE FALSEy vete de vacaciones.
Leo Heinsaar

Respuestas:

68

std::size_testá garantizado por el estándar C ++ para ser un unsignedtipo. Y si disminuye un unsignedtipo desde 0, el estándar garantiza que el resultado de hacerlo es el valor más grande para ese tipo.

Ese valor envuelto siempre es mayor o igual a M1, por lo que el ciclo termina.

Entonces, j <= Mcuando se aplica a un unsignedtipo, es una forma conveniente de decir "ejecutar el ciclo a cero y luego detenerse".

Existen alternativas como ejecutar juno más grande de lo que desea, e incluso usar el operador de diapositiva for (std::size_t j = M + 1; j --> 0; ){ , que posiblemente son más claras, aunque requieren más escritura. Sin embargo, supongo que una desventaja (aparte del efecto desconcertante que produce en la primera inspección) es que no se adapta bien a lenguajes sin tipos sin firmar, como Java.

Tenga en cuenta también que el esquema que ha elegido su jefe "toma prestado" un valor posible del unsignedconjunto: en este caso sucede que el Mconjunto a std::numeric_limits<std::size_t>::max()no tendrá el comportamiento correcto. De hecho, en ese caso, el bucle es infinito . (¿Es eso lo que está observando?) Debería insertar un comentario en ese sentido en el código, y posiblemente incluso afirmar sobre esa condición en particular.


1 Sujeto a Mno ser std::numeric_limits<std::size_t>::max().

Betsabé
fuente
7
Culpo al clima.
Hatted Rooster
3
Si M es, std::numeric_limits<std::size_t>::max()entonces M + 1será cero y el for (std::size_t j = M + 1; j --> 0; )bucle no formará bucle.
Pete Kirkham
51
No lo llames el "operador de diapositivas". No existe tal operador en C ++, es solo una ofuscación de aspecto inteligente.
Usted
26
@DimitarMirchev: Porque no es un operador. Son dos operadores, con espacios impares. Llámelo un modismo si insiste en hacer preguntas irrelevantes a los entrevistados (pero, idealmente, pregúnteles cómo implementarían la funcionalidad en lugar de preguntar sobre la sintaxis "inteligente").
Usted
1
Suponiendo que size_tsea ​​de 64 bits, se necesitarán varios cientos de años para observar el comportamiento incorrecto en el caso de borde. (Excepto si el optimizador puede deshacerse del bucle).
Carsten S
27

Lo que su jefe probablemente estaba tratando de hacer era contar hacia atrás desde Mcero inclusive, realizando alguna acción en cada número.

Por desgracia hay un caso extremo en que habrá de hecho le dará un bucle infinito, aquella en la que Mes el máximo size_tvalor que puede tener. Y, aunque está bien definido lo que hará un valor sin firmar cuando lo disminuyas desde cero, mantengo que el código en sí es un ejemplo de pensamiento descuidado, especialmente porque hay una solución perfectamente viable sin las deficiencias del intento de tus jefes.

Esa variante más segura (y más legible, en mi opinión, sin dejar de mantener un límite de alcance ajustado), sería:

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

A modo de ejemplo, consulte el siguiente código:

#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;
}

Esto hace básicamente lo mismo con un unsigned shorty puede ver que procesa todos los valores:

Starting at 65535, we would loop 65536 times.

Reemplazar el do..whileciclo en el código anterior con lo que hizo básicamente su jefe resultará en un ciclo infinito. Pruébelo y vea:

for (unsigned int us2 = us; us2 <= us; --us2) {
    quant++;
}
paxdiablo
fuente
7
Ahora, el caso de borde es 0. ¿Por qué no for(size_t j = M; j-- != 0; )?
LogicStuff
Sí, esto sufre de que el caso de la esquina sea quizás más frecuente que numeric_limits<size_t>::max().
Betsabé
7
@Logic et al, no hay caso de borde en el método que proporcioné, he agregado código para mostrar esto. Con su solución propuesta , un valor inicial de M == 0dará como resultado que el elemento 0 no se procese, por lo que tiene un caso de borde. El uso de mi do..whilemétodo de verificación posterior elimina el caso de borde por completo. Si lo prueba con M == 1, verá que hace 1 y 0. De manera similar, comience con max_size_t(lo que sea que sea) y comenzará con éxito en ese punto y luego disminuirá hasta cero inclusive.
paxdiablo
Curiosamente, este caso motiva el uso de lo que algunos dirían que es “código no estructurado” porque usa un “ exitif”, es decir { j =M; for(;;){ f(j); if( j == 0 )break; j -= 1; } }. Incluso podría ser necesario reemplazar el breakcon a gotosi las cosas se anidan, ya que C no tiene bucles con nombre. Si "estructurado" significa "susceptible de razonar sobre fragmentos", está estructurado (¡el diseño ayuda!), Y también si significa "susceptible de validación formal mediante el razonamiento sobre condiciones previas y posteriores". Aunque en este caso j--funciona, el estilo exitif puede estar justificado cuando se requiere una transición más complicada.
PJTraill
@PJTraill No puedo decir que lo que estás diciendo está estructurado y lo que estás diciendo no está estructurado. Razonar sobre el do-while es sencillo. No "usa" una salida más de lo que lo hace while-do, o de una manera no estructurada.
philipxy