Este es UB; en términos de ISO C ++, el comportamiento completo de todo el programa no está especificado para una ejecución que finalmente llega a UB. El ejemplo clásico es que, en lo que respecta al estándar C ++, puede hacer que los demonios salgan volando de tu nariz. (Recomiendo no usar una implementación donde los demonios nasales sean una posibilidad real). Ver otras respuestas para más detalles.
Los compiladores pueden "causar problemas" en el momento de la compilación para las rutas de ejecución que pueden ver que conducen a una UB visible en tiempo de compilación, por ejemplo, supongamos que nunca se alcanzan esos bloques básicos.
Consulte también Lo que todo programador de C debe saber sobre el comportamiento indefinido (blog LLVM). Como se explica allí, UB de desbordamiento firmado permite a los compiladores probar que los for(... i <= n ...)
bucles no son infinitos, incluso para desconocidos n
. También les permite "promover" los contadores de bucle int al ancho del puntero en lugar de rehacer la extensión de signo. (Entonces, la consecuencia de UB en ese caso podría ser acceder fuera de los elementos bajos de 64k o 4G de una matriz, si esperaba un ajuste firmado i
en su rango de valores).
En algunos casos, los compiladores emitirán una instrucción ilegal como x86 ud2
para un bloque que probablemente cause UB si alguna vez se ejecuta. (Tenga en cuenta que una función puede no siempre ser llamado, por lo que los compiladores pueden no en los estribos General Ir y romper otras funciones, o incluso los posibles caminos a través de una función que no golpeó UB. Es decir, el código de máquina que compila al mosto todavía trabajo para todas las entradas que no conducen a UB.)
Probablemente la solución más eficiente es pelar manualmente la última iteración para factor*=10
evitar lo innecesario .
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;
O si el cuerpo del bucle es grande, considere simplemente usar un tipo sin signo para factor
. Luego, puede dejar que se multiplique el desbordamiento sin signo y simplemente se ajustará bien a una potencia de 2 (el número de bits de valor en el tipo sin signo).
Esto está bien incluso si lo usa con tipos con signo, especialmente si su conversión sin signo-> con signo nunca se desborda.
La conversión entre sin signo y el complemento de 2 con signo es libre (mismo patrón de bits para todos los valores); el ajuste del módulo para int -> unsigned especificado por el estándar C ++ se simplifica al uso del mismo patrón de bits, a diferencia del complemento o signo / magnitud.
Y unsigned- >igned es igualmente trivial, aunque está definido por la implementación para valores mayores que INT_MAX
. Si no está utilizando el enorme resultado sin firmar de la última iteración, no tiene nada de qué preocuparse. Pero si es así, vea ¿La conversión de sin firmar a firma no está definida? . El caso del valor no encaja está definido por la implementación , lo que significa que una implementación debe elegir algún comportamiento; los cuerdos simplemente truncan (si es necesario) el patrón de bits sin signo y lo usan como firmado, porque eso funciona para valores dentro del rango de la misma manera sin trabajo adicional. Y definitivamente no es UB. Por lo tanto, los grandes valores sin signo pueden convertirse en enteros con signo negativo. por ejemplo, después de que int x = u;
gcc y clang no se optimicenx>=0
como siempre es cierto, incluso sin ellos -fwrapv
, porque definieron el comportamiento.