¿Cuál es la regla para que C emita entre short e int?

¿Cuál es la regla para que C emita entre short e int?

Cada vez que un tipo entero se convierte en un tipo entero diferente, cae a través de una máquina pachinko determinista de reglas según lo dicta el estándar y, en una ocasión, la implementación.

La descripción general sobre la calificación de valor:

C99 6.3.1.1-p2

Dicho esto, echemos un vistazo a sus conversiones. El signed-short a unsigned int está cubierto por lo siguiente, ya que el valor ser convertido cae fuera del unsigned int dominio:

C99 6.3.1.3-p2

Lo que básicamente significa "agregar UINT_MAX+1". En su máquina, UINT_MAX es 4294967295, por lo tanto, se convierte en

-1 + 4294967295 + 1 = 4294967295

Con respecto a tu unsigned short a signed int conversión, que está cubierta por la promoción calificada de valor regular. Específicamente:

C99 6.3.1.3-p1

En otras palabras, porque el valor de su unsigned short cae dentro del dominio cubierto de signed int , no se hace nada especial y el valor simplemente se guarda.

Y finalmente, como se mencionó en el comentario general anterior, sucede algo especial con su declaración de b

signed short b = 0xFFFF;

El 0xFFFF en este caso es un entero con signo. El valor decimal es 65535. Sin embargo, ese valor no representable por un signed short entonces ocurre otra conversión, una de la que quizás no estabas al tanto:

C99 6.3.1.3-p3

En otras palabras, su implementación seleccionó para almacenarlo como (-1) , pero no puede confiar en eso en una implementación diferente.


Lo que sucede aquí es que el lado derecho del argumento es primero extendido de 16 a 32 bits, y la conversión al tipo del lado izquierdo solo ocurre en la asignación. Esto significa que si el lado derecho está firmado, se extenderá con signo cuando se convierta a 32 bits y, del mismo modo, si no está firmado, solo se rellenará con ceros.

Si tiene cuidado con sus conversiones, entonces no debería haber ningún problema, pero a menos que esté haciendo algo que requiera mucho rendimiento, el par adicional de operaciones bit a bit no debería dañar nada.

En otra nota, si está haciendo algo en lo que asume ciertos anchos de bits para diferentes tipos de enteros, realmente debería ser explícito y usar los tipos definidos en stdint.h. Hace poco me mordió esto al transferir el código (de otra persona) de * nix a Windows, ya que el compilador de Visual C ++ usa una convención diferente para tamaños enteros (LLP64) que la de cualquier otro compilador x64 o power-7 que he usado (LP64). En resumen, si desea 32 bits, es mejor que lo diga explícitamente con un tipo como uint32_t .

Sí, siempre debe aguantar. Citas relevantes (con enlaces) del estándar C99:"Las promociones enteras conservan el valor, incluido el signo". Cuando se manejan conversiones de tipos aritméticos usuales:"... las promociones de enteros se realizan en ambos operandos. Luego se aplican las siguientes reglas a los operandos promovidos..."


Como se indica en la pregunta, suponga short de 16 bits y int de 32 bits .

unsigned short a = 0xFFFF;

Esto inicializa a a 0xFFFF o 65535 . La expresión 0xFFFF es de tipo int; se convierte implícitamente a unsigned short y el valor se conserva.

signed short b = 0xFFFF;

Esto es un poco más complicado. De nuevo, 0xFFFF es de tipo int . Está implícitamente convertido a signed short -- pero dado que el valor está fuera del rango de signed short la conversión no puede conservar el valor.

La conversión de un entero a un tipo de entero con signo, cuando el valor no se puede representar, genera un valor definido por la implementación. En principio, el valor de b podría ser cualquier cosa entre -32768 y +32767 inclusivo. En la práctica, es casi seguro que será -1 . Asumiré por el resto de esto que el valor es -1 .

unsigned int u16tou32 = a;

El valor de a es 0xFFFF , que se convierte de unsigned short a unsigned int . La conversión conserva el valor.

unsigned int s16tou32 = b;

El valor de b es -1 . Se convierte a unsigned int , que claramente no puede almacenar un valor de -1 . La conversión de un entero a un tipo de entero sin signo (a diferencia de la conversión a un tipo con signo) está definida por el idioma; el resultado es módulo reducido MAX + 1 , donde MAX es el valor máximo del tipo sin firmar. En este caso, el valor almacenado en s16tou32 es UINT_MAX - 1 o 0xFFFFFFFF .

signed int u16tos32 = a;

El valor de a , 0xFFFF , se convierte a signed int . El valor se conserva.

signed int s16tos32 = b;

El valor de b , -1 , se convierte a signed int . El valor se conserva.

Así que los valores almacenados son:

a == 0xFFFF (65535)
b == -1     (not guaranteed, but very likely)
u16tou32 == 0xFFFF (65535)
s16tou32 == 0xFFFFFFFF (4294967295)
u16tos32 == 0xFFFF (65535)
s16tos32 == -1

Para resumir las reglas de conversión de enteros:

Si el tipo de destino puede representar el valor, el valor se conserva.

De lo contrario, si el tipo de destino no está firmado, el valor se reduce módulo MAX+1 , lo que equivale a descartar todos menos los N bits de orden inferior. Otra forma de describir esto es que el valor MAX+1 se suma o resta repetidamente del valor hasta que obtiene un resultado que está en el rango (así es como lo describe el estándar C). Los compiladores en realidad no generan código para hacer esta suma o resta repetida; solo tienen que obtener el resultado correcto.

De lo contrario, el tipo de destino está firmado y no puede representar el valor; la conversión produce un valor definido por la implementación. En casi todas las implementaciones, el resultado descarta todos menos los N bits de orden inferior utilizando una representación de complemento a dos. (C99 agregó una regla para este caso, que permite generar una señal definida por la implementación. No conozco ningún compilador que haga esto).