2009-03-24 14 views
7

Tengo el siguiente código:MSVC++: Extrañeza con enteros sin signo y desbordamiento

#include <iostream> 

using namespace std; 

int main(int argc, char *argv[]) 
{ 
    string a = "a"; 
    for(unsigned int i=a.length()-1; i+1 >= 1; --i) 
    { 
     if(i >= a.length()) 
     { 
      cerr << (signed int)i << "?" << endl; 
      return 0; 
     } 
    } 
} 

Si puedo compilar el MSVC con optimizaciones completo, la salida que se ve es "-1?". Si compilo en modo Debug (sin optimizaciones), no obtengo salida (esperado).

Pensé que el estándar garantizaba que los enteros sin signo se desbordaran de manera predecible, de modo que cuando i = (unsigned int) (- 1) , i + 1 = 0, y la condición de bucle i + 1> = 1 falla. En cambio, la prueba de alguna manera está pasando. ¿Es esto un error del compilador, o estoy haciendo algo indefinido en alguna parte?

Respuesta

8

Recuerdo haber tenido este problema en 2001. Me sorprende que todavía esté allí. Sí, esto es un error del compilador.

El optimizador está viendo

i + 1 >= 1; 

En teoría, podemos optimizar esta poniendo todas las constantes en el mismo lado:

i >= (1-1); 

Debido i no tiene signo, siempre será mayor que o igual a cero.

Consulte esta discusión del grupo de noticias here.

1

No estoy seguro, pero creo que probablemente estés cometiendo un error.

Sospecho que el problema está en cómo el compilador está tratando el control for. Me podía imaginar el optimizador haciendo:

for(unsigned int i=a.length()-1; i+1 >= 1; --i) // As written 

for (unsigned int i = a.length()-1; i >= 0; --i) // Noting 1 appears twice 

for (unsigned int i = a.length()-1; ; --i) // Because i >= 0 at all times 

Si eso es lo que está sucediendo es otro asunto, pero podría ser suficiente como para confundir al optimizador.

Probablemente sería mejor usar una formulación de bucle más estándar:

for (unsigned i = a.length()-1; i-- > 0;) 
4

ISO14882: 2003, sección 5, párrafo 5:

Si durante la evaluación de una expresión, el resultado no está matemáticamente definido o no en el rango de valores representables para su tipo, el comportamiento no está definido, a menos que dicha expresión sea una expresión constante (5.19), en cuyo caso el programa está mal formado.

(Énfasis mío). Así que, sí, el comportamiento no está definido. La norma no garantiza el comportamiento en el caso de un entero por encima/por debajo del flujo.

Editar: El estándar parece un poco conflictivo sobre el asunto en otro lugar.

Sección 3.9.1.4 dice:

Los enteros sin signo, declararon sin firmar, deberá obedecer las leyes de la aritmética módulo 2, donde n es el número de bits en la representación de valores de ese tamaño particular de número entero.

Pero sección 4.7.2 y 0.3 dice:

2) Si el tipo de destino es sin signo, el valor resultante es el número entero sin signo menos congruente con el número entero fuente (módulo 2 n donde n es la cantidad de bits utilizados para representar el tipo sin signo). [Nota: en una representación de complemento de dos, esta conversión es conceptual y no hay cambios en el patrón de bits (si no hay truncamiento). ]

3) Si el tipo de destino está firmado, el valor no se modifica si se puede representar en el tipo de destino (y el ancho del campo de bits); de lo contrario, el valor está definido por la implementación.

(El subrayado es mío.)

+0

Hmm. Otros (en diferentes sitios) están citando la sección 4.7 de la norma: http://dev.feuvan.net/docs/isocpp/conv.html Están usando esto para argumentar que está definido. –

+0

De 3.9.1 4, y su nota al pie, tuve la impresión de que los enteros sin signo son una excepción: dado que 1 se agrega en la aritmética del mod 2^n, el resultado no puede estar fuera del rango de valores, ¿verdad? –

+0

En la conversión a un tipo firmado (como en el caso aquí), el resultado está definido por la implementación. Debido a que el valor cae fuera del rango de * ambos * tipos en una operación firmada, tomo esto para caer bajo la sección 5. El compilador está mal aquí, de cualquier manera. – greyfade

0

Sí, acabo de probar esto en Visual Studio 2005, que sin duda se comporta de manera diferente en depuración y de lanzamiento. Me pregunto si 2008 lo soluciona.

Curiosamente, se quejó de tu conversión implícita de size_t (resultado de .length) a unsigned int, pero no tiene problemas para generar código incorrecto.

Cuestiones relacionadas