2012-02-20 20 views
10

Todas las operaciones en tipos enteros con signo "estándar" en C (short, int, long, etc.) muestran un comportamiento indefinido si arrojan un resultado fuera del intervalo [TYPE_MIN, TYPE_MAX] (donde TYPE_MIN, TYPE_MAX son el mínimo y el máximo .. valor entero respectivamente que puede ser almacenada por el tipo entero específico¿Los tipos enteros con signo C99 definidos en stdint.h presentan un comportamiento bien definido en caso de desbordamiento?

Según el estándar C99, sin embargo, todos los tipos intN_t están obligados a tener representación de complemento a dos:

7.8.11.1 exacta- ancho entero tipos
1. El typedef nombre intN_t designar s un tipo entero con signo con ancho N, sin relleno bits, y una representación complementaria de dos. Por lo tanto, int8_t denota un entero con signo con un ancho de exactamente 8 bits.

¿Esto significa que los tipos intN_t en C99 muestran un comportamiento bien definido en caso de un desbordamiento de número entero? Por ejemplo, ¿este código está bien definido?

#include <stdio.h> 
#include <stdint.h> 
#include <inttypes.h> 

int main(void) 
{ 
    printf("Minimum 32-bit representable number: %" PRId32 "\n", INT32_MAX + 1); 
    return 0; 
} 

Respuesta

11

No, no es así.

El requisito de una representación de complemento de 2 para valores dentro del rango del tipo no implica nada sobre el comportamiento en el desbordamiento.

Los tipos en <stdint.h> son simplemente typedefs (alias) para los tipos existentes. Agregar un typedef no cambia el comportamiento de un tipo.

Sección 6.5 párrafo 5 de la norma C (ambos C99 y C11) todavía se aplica:

Si un condiciones excepcionales se produce durante la evaluación de una expresión (es decir, si el resultado no es matemáticamente definido o no en el rango de valores representables para su tipo), el comportamiento no está definido.

Esto no afecta a los tipos sin signo porque las operaciones sin firmar no se desbordan; se definen para producir el resultado envuelto, módulo reducido TIPO _MAX + 1. Excepto que los tipos sin signo más estrechos que int se promueven a (firmado) int, y por lo tanto pueden encontrarse con los mismos problemas. Por ejemplo, esto:

unsigned short x = USHRT_MAX; 
unsigned short y = USHRT_MAX; 
unsigned short z = x * y; 

provoca un comportamiento indefinido si short es más estrecha que int. (Si short y int son 16 y 32 bits, respectivamente, entonces 65535 * 65535 produce 4294836225, que supera INT_MAX.)

+0

¿Se menciona esto implícita o explícitamente en algún lugar de la norma? – Alexandros

+1

6.5p5, que cité, lo menciona explícitamente. Nada más en la norma dice que implica que 6.5p5 no se aplica a los tipos 'intN_t'. Y nada en el estándar define el comportamiento envolvente común para los tipos de complementos de 2; para que el comportamiento se defina, el estándar debería definirlo en alguna parte. –

+1

"Esto no afecta a los tipos sin signo porque ..." ¿No debería calificarse esto como tipos sin signo más estrecho que 'int' se promueven a' int' (y no 'sin firmar') primero y luego sufren las mismas limitaciones de comportamiento? – chux

4

Aunque el almacenamiento de un valor fuera de rango a un tipo firmado almacenado en la memoria generalmente almacenar la parte inferior bits del valor, y volver a cargar el valor desde la memoria lo firmará, muchas optimizaciones de los compiladores pueden suponer que la aritmética firmada no se desbordará, y los efectos del desbordamiento pueden ser impredecibles en muchos escenarios reales. Como un ejemplo simple, en un DSP de 16 bits que usa su acumulador de 32 bits para los valores de retorno (p.TMS3205X), int16_t foo(int16_t bar) { return bar+1;} un compilador sería libre de cargar bar, con extensión de signo, en el acumulador, agregar uno a él y devolverlo. Si el código de llamada fue, por ejemplo, long z = foo(32767), el código bien podría establecer z en 32768 en lugar de -32768.

+1

@BenVoigt: ¿Se pudo haber corregido el ejemplo como 'long foo (int bar) {return bar + 1;}'? He utilizado un compilador para un DSP donde 'int' tenía 16 bits, pero el único acumulador de 32 bits del procesador se usaba para devolver los resultados de la función. Si tal función fuera llamada por 'long z = foo (32767) + 1'; el compilador probablemente asignaría 'z' un valor de +32769 en lugar de -32767 [el código de llamada agregaría uno al acumulador, y luego almacenaría ambas mitades]. No recuerdo los casos exactos que produjeron comportamientos "interesantes", pero hubo algunos. – supercat

+0

@BenVoigt: Además, el lenguaje que citas me parece curioso. ¿Se requeriría que cada posible "valor" de una expresión 'int16' (incluidos los definidos por la implementación) sea uno que pueda almacenarse sin modificación en una variable' int16'? Me parece interesante que la aritmética fuera de rango sea totalmente "comportamiento indefinido", pero las conversiones fuera de rango permiten "señales definidas por la implementación". – supercat

+0

Sí, ese ejemplo parece un caso razonable en el que el comportamiento indefinido podría hacer algo totalmente inesperado a la luz del sistema de tipos. Para el primer ejemplo, sí, creo que si no hay señal, el resultado debe ser un valor 'int16_t' válido. Los comportamientos reales más razonables son el enmascaramiento de bits (aritmética de módulo al igual que sin signo) y la saturación (que implementa algún hardware). –

Cuestiones relacionadas