variable tipos leer imprimir funciones datos caracteres cadenas cadena arreglos arreglo c integer-promotion

leer - ¿Por qué se promueven los tipos enteros durante la suma en C?



scanf en c (4)

Así que tuvimos un problema de campo, y después de días de depuración, redujimos el problema a este bit particular de código, donde el procesamiento en un bucle while no estaba sucediendo:

// heavily redacted code // numberA and numberB are both of uint16_t // Important stuff happens in that while loop while ( numberA + 1 == numberB ) { // some processing }

Esto funcionó bien, hasta que alcanzamos el límite de uint16 de 65535. Otro grupo de declaraciones impresas más tarde, descubrimos que el numberA + 1 tenía un valor de 65536 , mientras que el número numberB regresaba a 0 . Esto falló la comprobación y no se realizó ningún procesamiento.

Esto me dio curiosidad, así que armé un programa rápido de C (compilado con GCC 4.9.2) para verificar esto:

#include <stdio.h> #include <stdint.h> int main() { uint16_t numberA, numberB; numberA = 65535; numberB = numberA + 1; uint32_t numberC, numberD; numberC = 4294967295; numberD = numberC + 1; printf("numberA = %d/n", numberA + 1); printf("numberB = %d/n", numberB); printf("numberC = %d/n", numberC + 1); printf("numberD = %d/n", numberD); return 0; }

Y el resultado fue:

numberA = 65536 numberB = 0 numberC = 0 numberD = 0

Así que parece que el resultado de numberA + 1 se promovió a uint32_t. ¿Está previsto esto por el lenguaje C? ¿O es esto alguna rareza de compilador / hardware?


Así que parece que el resultado de numberA + 1 se promovió a uint32_t

Los operandos de la adición se promovieron a int antes de que la adición tuviera lugar, y el resultado de la adición es del mismo tipo que los operandos efectivos ( int ).

De hecho, si int es de 32 bits de ancho en su plataforma de compilación (lo que significa que el tipo que representa uint16_t tiene un "rango de conversión" más bajo que int ), el numberA + 1 se calcula como una adición int entre 1 y un número promovido numberA como parte de Las reglas de promoción de enteros, 6.3.1.1:2 en el estándar C11:

Se puede usar lo siguiente en una expresión dondequiera que se pueda usar un int o un unsigned int: [...] Un objeto o expresión con un tipo entero (distinto de int o unsigned int) cuyo rango de conversión de entero es menor o igual que el rango de int y unsigned int.

[…]

Si un int puede representar todos los valores del tipo original [...], el valor se convierte en un int

En su caso, el unsigned short que es, con toda probabilidad, lo que uint16_t se define como en su plataforma, tiene todos sus valores representables como elementos de int , por lo que el número A del valor unsigned short se promueve a int cuando ocurre en una operación aritmética.


Cuando se estaba desarrollando el lenguaje C, era deseable minimizar el número de tipos de compiladores aritméticos con los que tenía que lidiar. Por lo tanto, la mayoría de los operadores matemáticos (por ejemplo, la suma) solo admiten int + int, long + long y double + double. Si bien el lenguaje podría haberse simplificado omitiendo int + int (promoviendo todo a long lugar), la aritmética en valores long generalmente toma de 2 a 4 veces más código que la aritmética en valores int ; ya que la mayoría de los programas están dominados por la aritmética en los tipos int , eso habría sido muy costoso. Por el contrario, la promoción de float para double ahorrará código, ya que significa que solo se necesitan dos funciones para admitir float : convertir al double y convertir del double . Todas las demás operaciones aritméticas de punto flotante solo necesitan ser compatibles con un tipo de punto flotante, y dado que las matemáticas de punto flotante se realizan a menudo llamando a las rutinas de la biblioteca, el costo de llamar a una rutina para agregar dos valores double es a menudo el mismo que el costo de llamar a una rutina Rutina para sumar dos valores float .

Desafortunadamente, el lenguaje C se extendió en una variedad de plataformas antes de que alguien realmente descubriera lo que debería significar 0xFFFF + 1, y en ese momento ya había algunos compiladores donde la expresión daba 65536 y algunos donde daba cero. En consecuencia, los escritores de estándares se han esforzado por escribirlos de una manera que les permita a los compiladores seguir haciendo lo que estaban haciendo, pero que fue bastante inútil desde el punto de vista de cualquiera que desee escribir código portátil. Por lo tanto, en plataformas donde int es de 32 bits, 0xFFFF + 1 producirá 65536, y en plataformas donde int es de 16 bits, producirá cero. Si en alguna plataforma se int de 17 bits, 0xFFFF + 1 autorizaría al compilador a negar las leyes del tiempo y la causalidad [por cierto, no sé si hay plataformas de 17 bits, pero hay algunas plataformas de 32 bits donde uint16_t x=0xFFFF; uint16_t y=x*x; uint16_t x=0xFFFF; uint16_t y=x*x; hará que el compilador confunda el comportamiento del código que lo precede ].


Literal 1 en int , es decir, en su caso int32 tipo, por lo que las operaciones con int32 y int16 dan resultados de int32.

Para obtener el resultado de la numberA + 1 como uint16_t intente el tipo explícito de uint16_t para 1 , por ejemplo: numberA + (uint16_t)1


Para operadores aritméticos como + , se aplican las conversiones aritméticas habituales .

Para los enteros, el primer paso de esas conversiones se llama las promociones de enteros , y esto promueve que cualquier valor de tipo más pequeño que int sea ​​un int .

Los otros pasos no se aplican a su ejemplo, así que los omitiré por concisión.

En la expresión numberA + 1 , se aplican las promociones de enteros. 1 ya es un int por lo que permanece sin cambios. numberA tiene el tipo uint16_t que es más estrecho que int en su sistema, por lo que numberA pasa a ser int .

El resultado de agregar dos int s es otro int , y 65535 + 1 da 65536 ya que tiene int s de 32 bits.

Así que su primera printf produce este resultado.

En la linea:

numberB = numberA + 1;

La lógica anterior todavía se aplica al operador + , esto es equivalente a:

numberB = 65536;

Dado que numberB tiene un tipo sin signo, uint16_t específicamente, 65536 se reduce (mod 65536), lo que da 0 .

Tenga en cuenta que sus dos últimas declaraciones printf causan un comportamiento indefinido; debe utilizar %u para imprimir unsigned int . Para hacer frente a diferentes tamaños de int , puede usar "%" PRIu32 para obtener el especificador de formato para uint32_t .