¿Por qué se define el comportamiento de desbordamiento de enteros sin signo pero no de desbordamiento de enteros con signo?
El desbordamiento de enteros sin signo está bien definido por los estándares C y C++. Por ejemplo, la norma C99 ( §6.2.5/9
) establece
Un cálculo que involucra operandos sin signo nunca puede desbordarse, porque un resultado que no puede representarse mediante el tipo entero sin signo resultante se reduce en módulo, el número que es uno mayor que el valor más grande que puede representarse mediante el tipo resultante.
Sin embargo, ambos estándares establecen que el desbordamiento de enteros con signo es un comportamiento indefinido. Nuevamente, del estándar C99 ( §3.4.3/1
)
Un ejemplo de comportamiento indefinido es el comportamiento en caso de desbordamiento de enteros.
¿Existe una razón histórica o (¡mejor aún!) técnica para esta discrepancia?
La razón histórica es que la mayoría de las implementaciones de C (compiladores) simplemente usaban cualquier comportamiento de desbordamiento que fuera más fácil de implementar con la representación entera que usaba. Las implementaciones de C generalmente usaban la misma representación utilizada por la CPU, por lo que el comportamiento de desbordamiento seguía a la representación entera utilizada por la CPU.
En la práctica, sólo las representaciones de valores con signo pueden diferir según la implementación: complemento a uno, complemento a dos, signo-magnitud. Para un tipo sin signo, no hay razón para que el estándar permita la variación porque solo hay una representación binaria obvia (el estándar solo permite la representación binaria).
Citas relevantes:
C99 6.2.6.1:3 :
Los valores almacenados en campos de bits sin signo y los objetos de tipo char sin signo se representarán utilizando una notación binaria pura.
C99 6.2.6.2:2 :
Si el bit de signo es uno, el valor se modificará de una de las siguientes maneras:
— se niega el valor correspondiente con el bit de signo 0 ( signo y magnitud );
— el bit de signo tiene el valor −(2 N ) ( complemento a dos );
— el bit de signo tiene el valor −(2 N − 1) ( complemento a uno ).
Hoy en día, todos los procesadores utilizan la representación en complemento a dos, pero el desbordamiento aritmético con signo sigue sin estar definido y los fabricantes de compiladores quieren que permanezca sin definir porque utilizan esta falta de definición para ayudar con la optimización. Véase, por ejemplo, esta publicación de blog de Ian Lance Taylor o esta queja de Agner Fog y las respuestas a su informe de error.
Aparte de la buena respuesta de Pascal (que estoy seguro es la motivación principal), también es posible que algunos procesadores provoquen una excepción en el desbordamiento de enteros con signo, lo que por supuesto causaría problemas si el compilador tuviera que "preparar otro comportamiento" ( por ejemplo, utilice instrucciones adicionales para comprobar si hay un posible desbordamiento y calcular de forma diferente en ese caso).
También vale la pena señalar que "comportamiento indefinido" no significa "no funciona". Significa que la implementación puede hacer lo que quiera en esa situación. Esto incluye hacer "lo correcto", así como "llamar a la policía" o "chocar". La mayoría de los compiladores, cuando sea posible, elegirán "hacer lo correcto", suponiendo que sea relativamente fácil de definir (en este caso, lo es). Sin embargo, si tiene desbordamientos en los cálculos, es importante comprender en qué resulta eso realmente y que el compilador PUEDE hacer algo distinto de lo esperado (y que esto puede depender mucho de la versión del compilador, la configuración de optimización, etc.) .
En primer lugar, tenga en cuenta que C11 3.4.3, como todos los ejemplos y notas a pie de página, no es un texto normativo y, por lo tanto, no es relevante citarlo.
El texto relevante que establece que el desbordamiento de números enteros y flotantes es un comportamiento indefinido es el siguiente:
C11 6.5/5
Si ocurre una condición excepcional durante la evaluación de una expresión (es decir, si el resultado no está definido matemáticamente o no está en el rango de valores representables para su tipo), el comportamiento no está definido.
Aquí se puede encontrar una aclaración específica sobre el comportamiento de los tipos de enteros sin signo:
C11 6.2.5/9
El rango de valores no negativos de un tipo entero con signo es un subrango del tipo entero sin signo correspondiente y la representación del mismo valor en cada tipo es la misma. Un cálculo que involucra operandos sin signo nunca puede desbordarse, porque un resultado que no puede representarse mediante el tipo entero sin signo resultante se reduce en módulo al número que es uno mayor que el valor más grande que puede representarse mediante el tipo resultante.
Esto hace que los tipos de enteros sin signo sean un caso especial.
Tenga en cuenta también que existe una excepción si cualquier tipo se convierte a un tipo con signo y el valor anterior ya no se puede representar. El comportamiento entonces está simplemente definido por la implementación, aunque puede generarse una señal.
C11 6.3.1.3
6.3.1.3 Enteros con y sin signo
Cuando un valor con tipo entero se convierte a otro tipo entero distinto de _Bool, si el valor puede representarse mediante el nuevo tipo, no se modifica.
De lo contrario, si el nuevo tipo no está firmado, el valor se convierte sumando o restando repetidamente uno más que el valor máximo que se puede representar en el nuevo tipo hasta que el valor esté en el rango del nuevo tipo.
De lo contrario, el nuevo tipo está firmado y el valor no se puede representar en él; o el resultado está definido por la implementación o se genera una señal definida por la implementación.