2009-06-12 18 views
20

Estaba leyendo C Standard el otro día, y noté que a diferencia del desbordamiento de entero con signo (que no está definido), el desbordamiento de entero sin signo está bien definido. Lo he visto usado en una gran cantidad de códigos para máximos, etc. pero dado el vudú sobre el desbordamiento, ¿se considera esto una buena práctica de programación? ¿Está de alguna manera inseguro? Sé que muchos lenguajes modernos como Python no lo admiten, sino que continúan ampliando el tamaño de los números grandes.¿Está utilizando una buena práctica de desbordamiento de enteros sin signo?

Respuesta

20

Desbordamiento de entero sin signo (en forma de envolvente) se aprovecha de forma rutinaria en las funciones de hash, y lo ha sido desde el año dot.

+1

año punto: ¿Es eso 1970? –

+0

Como los orígenes de la informática están en la criptografía, sospecho que es en algún momento en la década de 1940. –

+0

Aunque en realidad no entiendo por qué insisto en * signed * integer overflow. Usar un entero sin signo es igual de bueno y se puede solucionar para detectar desbordamiento. –

3

Una forma en la que podría pensar en un desbordamiento de enteros sin signo que causa un problema es cuando se resta de un pequeño valor sin signo, lo que hace que se ajuste a un gran valor positivo.

Consejos prácticos para los desbordamientos de enteros:
http://www.gnu.org/software/hello/manual/autoconf/Integer-Overflow-Basics.html#Integer-Overflow-Basics

+1

Estoy preguntando sobre el desbordamiento sin firmar – user122147

+0

Esto no tiene firma. Usando la aritmética sin signo, reste 200 de 100. Obtendrá un valor grande (qué tan grande depende del rango de valores). La aritmética sin signo en C se define como modular, o se podría decir wrap-around. –

+0

@DavidThornley: Creo que la pregunta era si es una buena práctica confiar en tal comportamiento. Mi opinión personal es que está bien confiar en dicho comportamiento si y solo si uno usa tipos rígidos cada vez que uno desea aplicarlo. Si 'a' y' b' son, p. Ej.'UInt32', la expresión' (UInt32) (a-b) 'dará como resultado el comportamiento de ajuste, y quedará claro que se espera ese comportamiento. Sin embargo, la expectativa de que (a-b) se suponga que produzca un comportamiento de envolvimiento es mucho menos obvia, y en máquinas donde 'int' es mayor que 32 bits, de hecho * no * podría generar tal comportamiento. – supercat

1

no confiaría en él sólo por razones de legibilidad. Va a depurar su código durante horas antes de averiguar dónde está restableciendo esa variable a 0.

+0

No realmente, siempre y cuando el comportamiento esté documentado y comentado. Además, existen motivos legítimos para el desbordamiento de enteros (no), por ejemplo, en sellos de tiempo de ancho fijo, donde la precisión es más importante y el desbordamiento se puede detectar razonablemente. –

0

Dado que los números con signo en las CPU se pueden representar de diferentes formas, el 99,999% de todas las CPU actuales usan la anotación de dos en complemento. Dado que esta es la mayoría de las máquinas que existen, es difícil encontrar un comportamiento diferente, aunque el compilador podría comprobarlo (posibilidad de grasa). Sin embargo, las especificaciones C deben representar el 100% de los compiladores, por lo que no han definido su comportamiento.

Por lo que haría las cosas más confusas, que es una buena razón para evitarlo. Sin embargo, si tiene una buena razón (por ejemplo, un aumento de rendimiento del factor de 3 para una parte crítica del código), documente bien y úselo.

+2

Se refiere a enteros con signo. El estándar C documenta el comportamiento de los enteros sin signo, y de eso se trata la pregunta. –

2

Lo uso todo el tiempo para saber si es el momento de hacer algo.

UInt32 now = GetCurrentTime() 

if(now - then > 100) 
{ 
    // do something 
} 

Mientras se comprueba el valor antes de 'ahora' vueltas 'entonces', que están muy bien para todos los valores de 'ahora' y 'después'.

EDIT: Creo que esto es realmente un underflow.

+0

Creo que el código anterior fallará en los sistemas donde 'int 'es de 64 bits, ya que los operandos de la resta se extenderían a enteros de 64 bits firmados, entonces' ahora 'es 0 y' entonces' es 4294967295u (0xFFFFFFFF) el resultado de la resta no sería 1 sino -4294967295. Lanzar el resultado de la resta a un UInt32 antes de la comparación evitaría ese problema ya que (UInt32) (- 4294967295) sería 1. – supercat

1

Otro lugar donde desbordamiento sin firmar se puede utilizar de manera útil es cuando se tiene que recorrer hacia atrás a partir de un determinado tipo sin signo:

void DownFrom(unsigned n) 
{ 
    unsigned m; 

    for(m = n; m != (unsigned)-1; --m) 
    { 
     DoSomething(m); 
    } 
} 

Otras alternativas no son tan claras. Intentar hacer m >= 0 no funciona a menos que cambie m a signed, pero entonces podría estar truncando el valor de n - o peor - convirtiéndolo en un número negativo en la inicialización.

De lo contrario, tiene que hacer! = 0 o> 0 y luego hacer manualmente el caso 0 después del ciclo.

+1

para (m = n; m

+2

O "do {DoSomething (n);} while (n--! = 0); ". Con "if (n! = (Unsigned) -1)" al inicio, si es realmente deseable que -1 sea un valor mágico de entrada que significa "no hacer nada", como está en el código anterior. –

+0

@Niki: no funciona, su ciclo no hace nada ya que m

3

Para decirlo brevemente:

es perfectamente legal/OK/seguro de usar desbordamiento de entero sin signo como mejor le parezca, siempre y cuando se presta atención y se adhieren a la definición (para cualquier propósito - optimización, super algoritmos inteligentes, etc.)

0

Está bien confiar en el desbordamiento, siempre que sepa CUÁNDO ocurrirá ...

Yo, por ejemplo, tuve problemas con la implementación C de MD5 al migrar a un compilador más reciente ... El código esperaba desbordamiento pero también esperaba 32 bits de entrada.

¡Con 64 bits los resultados fueron incorrectos!

Afortunadamente para eso son las pruebas automatizadas: Detecté el problema temprano pero podría haber sido una verdadera historia de terror si hubiera pasado desapercibido.

Se podría argumentar "pero esto no suele suceder": sí, pero eso es lo que lo hace aún más peligroso! Cuando hay un error, todos sospechan del código escrito en los últimos días. Nadie sospecha que el código "solo funcionó durante años" y, por lo general, nadie sabe cómo funciona ...

+0

Este es un caso en el que una aserción en tiempo de compilación para verificar la suposición de que sizeof (int) == 4 era válido sería una buena idea. Eso habría arrojado un error incluso antes de que se ejecutaran las pruebas. – RBerteig

3

El hecho de que usted conozca las minucias de la norma no significa que la persona que mantiene su código sí lo hace. Esa persona puede perder el tiempo preocupándose por esto mientras realiza la depuración más tarde, o tiene que buscar el estándar para verificar este comportamiento más adelante.

Claro, esperamos competencia con las características razonables de un lenguaje en un programador en funcionamiento, y diferentes compañías/grupos tienen una expectativa diferente sobre dónde se encuentra esa competencia razonable. Pero para la mayoría de los grupos esto parece ser demasiado esperar que la próxima persona lo sepa de la cabeza y no tenga que pensar en ello.

Si esto no fuera suficiente, es más probable encontrarse con errores de compilación cuando se trabaja alrededor de los bordes de la norma. O peor, la persona que transfiere este código a una nueva plataforma puede toparse con ellos.

En resumen, ¡voto no lo hagas!

0

Si lo usa con prudencia (bien comentado y legible), puede beneficiarse de ella por tener el código más pequeño y más rápido.

0

siukurnin hace un buen punto, que lo que necesita saber cuándo se producirán desbordamientos. La forma más fácil de evitar el problema de portabilidad que describió es usar los tipos de enteros de ancho fijo de stdint.h. uint32_t es un entero de 32 bits sin signo en todas las plataformas y sistemas operativos, y no se comportará de manera diferente cuando se compila para un sistema diferente.

0

Yo sugeriría tener siempre una conversión explícita cualquier momento se va a confiar en los números sin signo de envolver. De lo contrario, puede haber sorpresas. Por ejemplo, si "int" es de 64 bits, un código como:

UInt32 foo,bar; 
if ((foo-bar) < 100) // Report if foo is between bar and bar+99, inclusive) 
    ... do something 

puede fallar, ya que "foo" y "bar" se consiguen promovidos a enteros con signo de 64 bits. Agregar un typecast a UInt32 antes de verificar el resultado contra 100 evitaría problemas en ese caso.

Incidentalmente, creo que la única forma portátil de obtener directamente los 32 bits inferiores del producto de dos UInt32 es lanzar uno de los ints a un UInt64 antes de hacer el multiplicador. De lo contrario, el UInt32 podría convertirse a int64s firmados, con la multiplicación desbordante y produciendo resultados indefinidos.

Cuestiones relacionadas