Conversiones enteras (estrechamiento, ampliación), comportamiento indefinido

Fue bastante difícil para mí encontrar información sobre este tema de manera que pudiera entenderlo fácilmente, por lo que estoy pidiendo una revisión de lo que he encontrado. Solo se trata de la conversión y la conversión.


En ejemplos me referiré a:

(signed/unsigned) int bigger; (signed/unsigned) char smaller; 
  1. Truncando enteros (más grande-> más pequeño)

    • primero truncar bigger en el lado MSB para que coincida con el tamaño smaller .
    • segundo, convierta el resultado truncado a firmado / no firmado dependiendo del tipo más pequeño.

    Si el valor más grande es demasiado grande para caber en un tipo más pequeño, da como resultado un comportamiento indefinido (corríjame sobre eso). Sin embargo, mi regla debería estar funcionando en todas las máquinas (corríjame también sobre eso) y los resultados deberían ser predecibles.

  2. Entero creciente (más pequeño-> más grande)

    a) signed char -> signed int

    • preceder más pequeño con MSB (1 o 0) para que coincida con un tamaño más grande
    • convertir a firmado

    b) signed char -> unsigned int

    • preceder más pequeño con MSB (1 o 0) para que coincida con el tamaño más grande.
    • convertir a unsigned

    c) unsigned char -> signed int

    • preceder con 0 para que coincida con un tamaño más grande
    • convertir a firmado

    d) unsigned char -> unsigned int

    • preceder con 0 para que coincida con un tamaño más grande
    • convertir a unsigned

¿Dónde están los comportamientos indefinidos / no especificados que no mencioné que podrían aparecer?

Una conversión integral nunca produce un comportamiento indefinido (puede producir un comportamiento definido por la implementación).

Una conversión a un tipo que puede representar el valor que se está convirtiendo siempre está bien definida: el valor simplemente no cambia.

Una conversión a un tipo sin signo siempre está bien definida: el valor se toma módulo UINT_MAX + 1 (o el valor máximo que admite el tipo de destino).

Una conversión a un tipo firmado que no puede representar el valor que se está convirtiendo da como resultado un valor definido por la implementación o una señal definida por la implementación.

Tenga en cuenta que las reglas anteriores se definen en términos de valores enteros y no en términos de secuencias de bits.

De C documento estándar (p.50 borrador versión 201x creo y no cita exacta):

  • Ningún dos entero con signo tendrá el mismo rango

  • El rango del entero con signo debe ser mayor que el rango de cualquier entero con signo con menos precisión.

  • long long int es mayor que long int que es mayor que int, que es mayor que short int, que es mayor que char firmado.

  • firmado y sin firmar de la misma precisión tienen el mismo rango (por ejemplo, firmado int es el mismo rango que unsigned int)

  • El rango de cualquier tipo de entero estándar será mayor que el rango de cualquier tipo de entero extendido del mismo ancho.

  • El rango de char es igual a char sin signo es igual a char firmado.

(Me estoy yendo bool porque los excluiste de tu pregunta)

  • El rango de cualquier entero extendido con respecto a otro entero con signo extendido está definido por la implementación pero aún está sujeto a otras reglas de rango de conversión de enteros.

  • para todos los tipos enteros T1 T2 y T3, T1 tiene un rango mayor que T2 y T2 tiene un rango mayor que T3, que T1 tiene un rango mayor que T3.

Un objeto con un tipo entero (diferente de int y signed int) cuyo rango entero es MENOS que o igual al rango de int y unsigned int, un campo de bit de tipo _Bool, int, signed int o unsigned int; si un int puede representar todos los valores del tipo original, el valor se convierte en un int. De lo contrario, una int sin firmar Todos los otros tipos son cambiados por la promoción entera.

En términos simples:

Cualquier tipo “más pequeño” que int int sin firmar se promueve a int cuando se convierte a otro tipo de mayor rango. Este es el trabajo del comstackdor para garantizar que un código C comstackdo para una máquina determinada (architecture) sea compatible con ISO-C a ese respecto. char está definido por la implementación (está firmado o no). Todos los otros tipos (promoción o “degradación”) están definidos por la implementación.

¿Qué es la implementación definida? Significa que un comstackdor determinado se comportará sistemáticamente igual en una máquina determinada. En otras palabras, todo el comportamiento “definido por la implementación” depende tanto del comstackdor como de la máquina de destino.

Para hacer código portátil:

  • siempre promocione los valores a tipos de C estándar de mayor rango.
  • Nunca “degrade” valores a tipos menores.
  • Evite toda implementación “definida por la implementación” en su código.

¿Por qué existe esa locura definida por la implementación si arruina el esfuerzo de los progtwigdores? La progtwigción del sistema básicamente requiere este comportamiento definido por la implementación.

Así que más específicamente hacia su pregunta:

  • el truncamiento probablemente no sea protable. O requerirá mucho más esfuerzo en mantenimiento, seguimiento de errores, etc., que simplemente mantener el código utilizando tipos de mayor rango.
  • Si su implementación ejecuta valores más grandes que los tipos involucrados, su diseño es incorrecto (a menos que esté involucrado en la progtwigción del sistema).
  • Como regla general, pasar de unsigned a signed conserva los valores pero no al revés. Por lo tanto, cuando un valor sin signo va de la cabeza a los pies contra uno firmado, promueva el signo no firmado para que se firme en lugar de al revés.
  • Si utilizar una clase de enteros tan pequeña como sea posible es crítico para la memoria en su aplicación, probablemente debería volver a visitar la architecture de todo el progtwig.