¿El desbordamiento de enteros con signo sigue siendo un comportamiento indefinido en C ++?

82

Como sabemos, el desbordamiento de enteros con signo es un comportamiento indefinido . Pero hay algo interesante en la cstdintdocumentación de C ++ 11 :

tipo entero con signo con un ancho de exactamente 8, 16, 32 y 64 bits respectivamente sin bits de relleno y usando el complemento a 2 para valores negativos (proporcionado solo si la implementación admite directamente el tipo)

Ver enlace

Y aquí es mi pregunta: ya que la norma dice explícitamente que para int8_t, int16_t, int32_ty int64_tlos números negativos son complemento a 2, sigue siendo desbordamiento de estos tipos un comportamiento indefinido?

Editar Revisé los estándares C ++ 11 y C11 y esto es lo que encontré:

C ++ 11, §18.4.1:

El encabezado define todas las funciones, tipos y macros al igual que 7.20 en el estándar C.

C11, §7.20.1.1:

El nombre typedef intN_tdesigna un tipo entero con signo con ancho N, sin bits de relleno y una representación en complemento a dos. Por lo tanto, int8_tdenota un tipo de entero con signo con un ancho de exactamente 8 bits.

Archie
fuente
14
Nunca olvide que la única documentación principal para C ++ es el estándar. Todo lo demás, incluso una wiki como CppReference, es una fuente secundaria. Eso no significa que esté mal; simplemente no es del todo confiable.
Nicol Bolas
Esperaría que fuera UB, no hay exenciones para estos tipos en C, no veo por qué C ++ agregaría uno.
Daniel Fischer
3
Estoy un poco confundido: ¿dónde está el verbo en la oración "tipo entero con signo con un ancho de exactamente 8, 16, 32 y 64 bits respectivamente sin bits de relleno y usando el complemento de 2 para valores negativos (proporcionado solo si la implementación admite directamente el tipo)?" ¿Falta un poco? Qué significa eso?
YSC
C ++ 11 se basa en C99, no en C11. Pero esto no es importante de todos modos
LF

Respuestas:

80

¿Sigue siendo el desbordamiento de estos tipos un comportamiento indefinido?

Si. Según el párrafo 5/4 del estándar C ++ 11 (con respecto a cualquier expresión en general):

Si durante la evaluación de una expresión, el resultado no está definido matemáticamente o no está en el rango de valores representables para su tipo, el comportamiento es indefinido . [...]

El hecho de que se utilice una representación en complemento a dos para esos tipos con signo no significa que se utilice módulo aritmético 2 ^ n al evaluar expresiones de esos tipos.

Con respecto a la aritmética sin signo , por otro lado, el Estándar especifica explícitamente que (Párrafo 3.9.1 / 4):

Los enteros sin signo, declarados unsigned, obedecerán las leyes de la aritmética módulo 2 ^ n donde n es el número de bits en la representación del valor de ese tamaño particular de entero

Esto significa que el resultado de una operación aritmética sin signo siempre está " definido matemáticamente ", y el resultado siempre está dentro del rango representable; por lo tanto, 5/4 no se aplica. La nota al pie 46 explica esto:

46) Esto implica que la aritmética sin signo no se desborda porque un resultado que no puede ser representado por el tipo entero sin signo resultante se reduce módulo al número que es uno mayor que el valor más grande que puede ser representado por el tipo entero sin signo resultante.

Andy Prowl
fuente
1
Este párrafo también implicaría que el desbordamiento sin firmar no está definido, lo que no es así.
Archie
8
@Archie: En realidad no, ya que los valores sin signo se definen en el módulo del rango sin signo.
Lightness Races in Orbit
3
@Archie: Traté de aclarar, pero básicamente obtuviste la respuesta de LightnessRacesinOrbit
Andy Prowl
1
En realidad, no importa si se define el desbordamiento sin firmar o no si no puede ocurrir debido al cálculo del módulo ...
Aconcagua
1
Hay operaciones sin firmar cuyo resultado no está "definido matemáticamente", en particular la división por cero, por lo que quizás su redacción no sea exactamente lo que quería decir en esa oración. ITYM que cuando el resultado se define matemáticamente , también se define en C ++.
Toby Speight
22

El hecho de que un tipo esté definido para usar la representación del complemento a 2, no implica que el desbordamiento aritmético en ese tipo se defina.

El comportamiento indefinido del desbordamiento aritmético firmado se utiliza para habilitar optimizaciones; por ejemplo, el compilador puede asumir que si a > bentonces a + 1 > btambién; esto no se cumple en la aritmética sin firmar, donde la segunda verificación debería realizarse debido a la posibilidad de que a + 1pueda llegar a 0. Además, algunas plataformas pueden generar una señal de captura en caso de desbordamiento aritmético (ver, por ejemplo, http://www.gnu.org/software/libc/manual/html_node/Program-Error-Signals.html ); el estándar continúa permitiendo que esto ocurra.

ecatmur
fuente
5
Puede valer la pena señalar que muchas personas "se preocupan" más por las posibilidades de las trampas, pero las suposiciones del compilador son en realidad más insidiosas (una de las razones por las que desearía que hubiera una categoría entre el comportamiento definido por la implementación y el comportamiento indefinido, a diferencia del comportamiento definido por la implementación). que requiere implementaciones particulares para hacer algo de manera coherente y documentada, me gustaría un comportamiento "restringido por la implementación" que requeriría implementaciones para especificar todo lo que podría suceder como consecuencia de algo (las especificaciones podrían incluir explícitamente un comportamiento indefinido, pero ... .
supercat
3
... se alentaría a las implementaciones a ser más específicas cuando sea práctico). En un hardware donde los números en complemento a dos se "encajarían" naturalmente, no hay una razón sensata para que el código que quiera un resultado entero encapsulado ejecute muchas instrucciones tratando de realizar sin un desbordamiento de enteros, un cálculo que el hardware podría hacer en solo una o dos instrucciones .
supercat
1
@supercat De hecho, el código que quiere un resultado envuelto puede (en las CPU de complemento a 2) simplemente lanzar operandos a los tipos sin signo correspondientes y realizar la operación (y luego devolver, obteniendo el valor definido por la implementación): esto funciona para la suma, resta y multiplicación . El único problema es con la división, el módulo y funciones como abs. Para esas operaciones cuando funciona no requiere más instrucciones que con artmética firmada.
Ruslan
@Ruslan: en los casos en que el código necesita un resultado ajustado con precisión, una conversión a unsigned sería fea pero no necesariamente generaría código adicional. Un problema mayor sería el código que necesita identificar rápidamente a los candidatos "potencialmente interesantes", que pasarán la mayor parte de su tiempo rechazando candidatos poco interesantes. Si uno le da a un compilador la libertad de mantener o descartar arbitrariamente precisión adicional con valores enteros firmados, pero requiere que una conversión a un tipo entero trunque dicha precisión, eso habilitará la mayoría de las optimizaciones útiles que se lograrían haciendo un desbordamiento UB , ...
supercat
... pero permitiría que el código que necesita un ajuste preciso utilice una conversión en lugar de dos (por ejemplo (int)(x+y)>z, compararía un resultado ajustado), y también permitiría a los programadores escribir x+y>zen los casos en que sería aceptable que el código arrojara 0 o 1 en caso de de desbordamiento siempre que no tenga otros efectos secundarios . Si 0 o 1 fuera un resultado igualmente aceptable, dejar que el programador escriba eso en lugar de cualquiera de los dos (long)x+y>zo (int)((unsigned)x+y)>zpermitiría a los compiladores seleccionar cualquiera de las últimas funciones que fuera más barata en cualquier contexto dado [cada una sería más barata en algunos casos].
supercat
1

Apuesto a que sí.

De la documentación estándar (páginas 4 y 5):

1.3.24 comportamiento indefinido

comportamiento para el que esta norma internacional no impone requisitos

[Nota: Se puede esperar un comportamiento indefinido cuando esta Norma Internacional omite cualquier definición explícita de comportamiento o cuando un programa utiliza una construcción o datos erróneos. El comportamiento indefinido admisible va desde ignorar la situación por completo con resultados impredecibles, a comportarse durante la traducción o ejecución del programa de manera documentada característica del entorno (con o sin la emisión de un mensaje de diagnóstico), hasta terminar una traducción o ejecución (con la emisión de un mensaje de diagnóstico). Muchas construcciones de programas erróneas no generan un comportamiento indefinido; deben ser diagnosticados. Nota final]

EnzoR
fuente