c++ - ¿Cómo funcionan las reglas de promoción cuando difiere la firma de ambos lados de un operador binario?
overflow arithmetic-expressions (3)
Esta pregunta ya tiene una respuesta aquí:
Considere los siguientes programas:
// http://ideone.com/4I0dT
#include <limits>
#include <iostream>
int main()
{
int max = std::numeric_limits<int>::max();
unsigned int one = 1;
unsigned int result = max + one;
std::cout << result;
}
y
// http://ideone.com/UBuFZ
#include <limits>
#include <iostream>
int main()
{
unsigned int us = 42;
int neg = -43;
int result = us + neg;
std::cout << result;
}
¿Cómo "sabe" el operador + cuál es el tipo correcto para devolver? La regla general es convertir todos los argumentos al tipo más amplio, pero aquí no hay un "ganador" claro entre int
y unsigned int
. En el primer caso, se debe elegir unsigned int
como resultado de operator+
, porque obtengo un resultado de 2147483648
. En el segundo caso, debe elegir int
, porque obtengo un resultado de -1
. Sin embargo, no veo en el caso general cómo esto es decidible. ¿Este comportamiento indefinido estoy viendo o algo más?
Antes de que C se estandarizara, había diferencias entre los compiladores: algunos seguían las reglas de "preservación del valor" y otras reglas de "preservación de los signos". La preservación de señal significaba que si ninguno de los operandos estaba sin firmar, el resultado no estaba firmado. Esto fue simple, pero a veces dio resultados bastante sorprendentes (especialmente cuando un número negativo se convirtió a unsigned).
C estandarizado en las reglas bastante más complejas de "preservación del valor". Bajo las reglas de preservación del valor, la promoción puede / depende de los rangos reales de los tipos, por lo que puede obtener diferentes resultados en diferentes compiladores. Por ejemplo, en la mayoría de los compiladores de MS-DOS, int
es del mismo tamaño que short
y long
es diferente de cualquiera. En muchos sistemas actuales, int
es del mismo tamaño que long
, y short
es diferente de cualquiera. Con reglas que preservan el valor, estas pueden llevar a que el tipo promocionado sea diferente entre los dos.
La idea básica de las reglas para preservar el valor es que promoverá un tipo firmado más grande si eso puede representar todos los valores del tipo más pequeño. Por ejemplo, un unsigned short
16 bits puede promocionarse a un signed int
32 bits, ya que cada valor posible del unsigned short
se puede representar como un signed int
. Los tipos se promocionarán a un tipo sin firmar si y solo si es necesario para preservar los valores del tipo más pequeño (por ejemplo, si unsigned short
y signed int
son ambos 16 bits, entonces un signed int
no puede representar todos los valores posibles de unsigned short
, por lo que un unsigned short
se promocionará a unsigned int
).
Cuando asigna el resultado como lo hizo, el resultado se convertirá al tipo de destino de todos modos, por lo que la mayoría de esto hace una diferencia relativamente pequeña, al menos en la mayoría de los casos típicos, donde solo copiará los bits en el resultado, y Depende de usted decidir si interpreta eso como firmado o sin firmar.
Cuando no se asigna el resultado, como en una comparación, las cosas pueden ponerse bastante feas. Por ejemplo:
unsigned int a = 5;
signed int b = -5;
if (a > b)
printf("Of course");
else
printf("What!");
Debajo de las reglas de preservación de signos, b
se promocionaría a unsigned, y en el proceso llegaría a ser igual a UINT_MAX - 4
, entonces el "¡Qué!" pierna del if
sería tomada. Con las reglas de preservación del valor, puede lograr algunos resultados extraños como este, pero 1) principalmente en los sistemas tipo DOS donde int
es del mismo tamaño que short
, y 2) en general es más difícil hacerlo de todos modos.
Es elegir cualquier tipo en el que pones tu resultado o al menos cout honra a ese tipo durante el resultado.
No lo recuerdo con certeza, pero creo que los compiladores de C ++ generan el mismo código aritmético para ambos, solo se compara y la salida se preocupa por el signo.
Esto se describe explícitamente en §5 / 9:
Muchos operadores binarios que esperan operandos de tipo aritmético o de enumeración causan conversiones y producen tipos de resultados de forma similar. El propósito es producir un tipo común, que también es el tipo del resultado. Este patrón se denomina conversiones aritméticas habituales , que se definen de la siguiente manera:
- Si cualquiera de los dos operandos es del tipo
long double
, el otro se convertirá along double
.- De lo contrario, si cualquiera de los dos operandos es
double
, el otro se convertirá endouble
.- De lo contrario, si alguno de los operandos es
float
, el otro se convertirá enfloat
.- De lo contrario, las promociones integrales se realizarán en ambos operandos.
- Entonces, si cualquiera de los operandos tiene un
unsigned long
el otro se convertirá aunsigned long
.- De lo contrario, si un operando es un
long int
y el otrounsigned int
, entonces si unlong int
puede representar todos los valores de ununsigned int
, elunsigned int
se convertirá en unlong int
; de lo contrario, ambos operandos se convertirán aunsigned long int
.- De lo contrario, si alguno de los operandos es
long
, el otro se convertirá enlong
.- De lo contrario, si ninguno de los operandos está
unsigned
, el otro se convertirá enunsigned
.[ Nota : de lo contrario, el único caso restante es que ambos operandos son
int
]
En ambos escenarios, el resultado de operator+
unsigned
está unsigned
. En consecuencia, el segundo escenario es efectivamente:
int result = static_cast<int>(us + static_cast<unsigned>(neg));
Como en este caso el valor de us + neg
no es representable por int
, el valor del result
está definido por la implementación - §4.7 / 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.