c++ overflow arithmetic-expressions integer-promotion

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á a long double .
  • De lo contrario, si cualquiera de los dos operandos es double , el otro se convertirá en double .
  • De lo contrario, si alguno de los operandos es float , el otro se convertirá en float .
  • 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á a unsigned long .
  • De lo contrario, si un operando es un long int y el otro unsigned int , entonces si un long int puede representar todos los valores de un unsigned int , el unsigned int se convertirá en un long int ; de lo contrario, ambos operandos se convertirán a unsigned long int .
  • De lo contrario, si alguno de los operandos es long , el otro se convertirá en long .
  • De lo contrario, si ninguno de los operandos está unsigned , el otro se convertirá en unsigned .

[ 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.