tipo texto significado refranes que mensaje implícito implicito explícito ejemplos aprendizaje c type-conversion implicit-conversion

texto - Reglas de promoción de tipo implícito



que es explícito (2)

C fue diseñado para cambiar implícita y silenciosamente los tipos enteros de los operandos utilizados en las expresiones. Existen varios casos en los que el lenguaje obliga al compilador a cambiar los operandos a un tipo más grande o a cambiar su firma.

La razón detrás de esto es evitar desbordamientos accidentales durante la aritmética, pero también permitir que coexistan operandos con diferente firma en la misma expresión.

Desafortunadamente, las reglas para la promoción de tipo implícito causan mucho más daño que bien, hasta el punto de que podrían ser uno de los mayores defectos en el lenguaje C. Estas reglas a menudo ni siquiera son conocidas por el programador promedio de C y, por lo tanto, causan todo tipo de errores muy sutiles.

Por lo general, se ven escenarios en los que el programador dice "simplemente envía al tipo x y funciona", pero no sabe por qué. O tales errores se manifiestan como fenómenos raros e intermitentes que atacan desde dentro de un código aparentemente simple y directo. La promoción implícita es particularmente problemática en el código que realiza manipulaciones de bits, ya que la mayoría de los operadores de C en bits tienen un comportamiento mal definido cuando se les da un operando firmado.

Tipos enteros y rango de conversión

Los tipos enteros en C son char , short , int , long , long long y enum .
_Bool / bool también se trata como un tipo entero cuando se trata de promociones de tipo.

Todos los enteros tienen un rango de conversión especificado. C11 6.3.1.1, énfasis mío en las partes más importantes:

Cada tipo de entero tiene un rango de conversión de enteros definido de la siguiente manera:
- No hay dos tipos enteros con signo que tengan el mismo rango, incluso si tienen la misma representación.
- El rango de un tipo entero con signo será mayor que el rango de cualquier tipo entero con signo con menos precisión.
- El rango de long long int será mayor que el rango de long int , que será mayor que el rango de int , que será mayor que el rango de short int , que será mayor que el rango de caracteres signed char .
- El rango de cualquier tipo de entero sin signo será igual al rango del tipo de entero con signo correspondiente, si lo hay.
- El rango de cualquier tipo entero estándar será mayor que el rango de cualquier tipo entero extendido con el mismo ancho.
- El rango de char será igual al rango de char firmado y no firmado.
- El rango de _Bool será menor que el rango de todos los demás tipos enteros estándar.
- El rango de cualquier tipo enumerado será igual al rango del tipo entero compatible (véase 6.7.2.2).

Los tipos de stdint.h ordenan aquí, con el mismo rango que el tipo al que corresponden en el sistema dado. Por ejemplo, int32_t tiene el mismo rango que int en un sistema de 32 bits.

Además, C11 6.3.1.1 especifica qué tipos se consideran tipos enteros pequeños (no es un término formal):

Lo siguiente se puede usar en una expresión siempre que se pueda usar un int o unsigned int :

- Un objeto o expresión con un tipo entero (distinto de int o unsigned int ) cuyo rango de conversión de enteros es menor o igual que el rango de int y unsigned int .

Lo que este texto algo críptico significa en la práctica es que _Bool , char y short (y también int8_t , uint8_t , etc.) son los "tipos enteros pequeños". Estos se tratan de manera especial y están sujetos a una promoción implícita, como se explica a continuación.

Las promociones enteras

Cada vez que se usa un tipo entero pequeño en una expresión, se convierte implícitamente en int que siempre está firmado. Esto se conoce como las promociones de enteros o la regla de promoción de enteros .

Formalmente, la regla dice (C11 6.3.1.1):

Si un int puede representar todos los valores del tipo original (según lo restringido por el ancho, para un campo de bits), el valor se convierte en un int ; de lo contrario, se convierte en un unsigned int . Estas se llaman promociones enteras .

Esto significa que todos los tipos enteros pequeños, sin importar la firma, se convierten implícitamente a (firmado) int cuando se usan en la mayoría de las expresiones.

Este texto a menudo se malinterpreta como: "todos los tipos enteros pequeños con signo se convierten en int con signo y todos los tipos enteros pequeños sin signo se convierten en int sin signo". Esto es incorrecto. La parte sin signo aquí solo significa que si tenemos, por ejemplo, un operando unsigned short , y int tiene el mismo tamaño que short en el sistema dado, entonces el operando unsigned short se convierte en unsigned int . Como en, nada de nota realmente sucede. Pero en caso de que short sea ​​un tipo más pequeño que int , siempre se convierte en int (firmado), independientemente de si el short fue firmado o no .

La dura realidad causada por las promociones de enteros significa que casi ninguna operación en C se puede llevar a cabo en tipos pequeños como char o short . Las operaciones siempre se llevan a cabo en int o tipos más grandes.

Esto puede parecer una tontería, pero afortunadamente el compilador puede optimizar el código. Por ejemplo, una expresión que contenga dos operandos unsigned char conseguiría que los operandos se promocionen a int y la operación se lleve a cabo como int . Pero el compilador puede optimizar la expresión para que se realice como una operación de 8 bits, como era de esperar. Sin embargo, aquí viene el problema: al compilador no se le permite optimizar el cambio implícito de firma causado por la promoción de enteros. Porque no hay forma de que el compilador sepa si el programador confía intencionalmente en la promoción implícita o si no es intencional.

Es por eso que el ejemplo 1 en la pregunta falla. Ambos operandos de caracteres sin signo se promueven al tipo int , la operación se realiza en el tipo int y el resultado de x - y es del tipo int . Lo que significa que obtenemos -1 lugar de 255 que podría haberse esperado. El compilador puede generar código de máquina que ejecuta el código con instrucciones de 8 bits en lugar de int , pero puede no optimizar el cambio de firma. Lo que significa que terminamos con un resultado negativo, que a su vez da como resultado un número extraño cuando se invoca printf("%u Se invoca el ejemplo 1 volviendo el resultado de la operación a unsigned char .

Con la excepción de algunos casos especiales como los operadores ++ y sizeof , las promociones de enteros se aplican a casi todas las operaciones en C, sin importar si se utilizan operadores unarios, binarios (o ternarios).

Las conversiones aritméticas habituales

Siempre que una operación binaria (una operación con 2 operandos) se realiza en C, ambos operandos del operador deben ser del mismo tipo. Por lo tanto, en caso de que los operandos sean de diferentes tipos, C impone una conversión implícita de un operando al tipo del otro operando. Las reglas sobre cómo se hace esto se denominan las conversiones artihmetic habituales (a veces denominadas informalmente como "equilibrio"). Estos se especifican en C11 6.3.18:

(Piense en esta regla como una declaración if-else if larga y anidada y podría ser más fácil de leer :))

6.3.1.8 Conversiones aritméticas usuales

Muchos operadores que esperan operandos de tipo aritmético provocan conversiones y producen tipos de resultados de manera similar. El propósito es determinar un tipo real común para los operandos y el resultado. Para los operandos especificados, cada operando se convierte, sin cambio de dominio de tipo, a un tipo cuyo tipo real correspondiente es el tipo real común. A menos que se indique explícitamente lo contrario, el tipo real común es también el tipo real correspondiente del resultado, cuyo dominio de tipo es el dominio de tipo de los operandos si son iguales, y complejo de lo contrario. Este patrón se llama las conversiones aritméticas habituales :

  • Primero, si el tipo real correspondiente de cualquiera de los operandos es long double , el otro operando se convierte, sin cambio de dominio de tipo, a un tipo cuyo tipo real correspondiente es long double .
  • De lo contrario, si el tipo real correspondiente de cualquiera de los operandos es double , el otro operando se convierte, sin cambio de dominio de tipo, a un tipo cuyo tipo real correspondiente es double .
  • De lo contrario, si el tipo real correspondiente de cualquiera de los operandos es float , el otro operando se convierte, sin cambio de dominio de tipo, a un tipo cuyo tipo real correspondiente es flotante.
  • De lo contrario, las promociones de enteros se realizan en ambos operandos. Luego, se aplican las siguientes reglas a los operandos promocionados:

    • Si ambos operandos tienen el mismo tipo, no se necesita más conversión.
    • De lo contrario, si ambos operandos tienen tipos enteros con signo o ambos tienen tipos enteros sin signo, el operando con el tipo de rango de conversión de entero menor se convierte al tipo del operando con mayor rango.
    • De lo contrario, si el operando que tiene un tipo entero sin signo tiene un rango mayor o igual al rango del tipo del otro operando, entonces el operando con tipo entero con signo se convierte al tipo del operando con tipo entero sin signo.
    • De lo contrario, si el tipo de operando con tipo entero con signo puede representar todos los valores del tipo de operando con tipo entero sin signo, entonces el operando con tipo entero sin signo se convierte en el tipo de operando con tipo entero con signo.
    • De lo contrario, ambos operandos se convierten al tipo entero sin signo correspondiente al tipo del operando con tipo entero con signo.

Es notable aquí que las conversiones aritméticas habituales se aplican tanto a las variables de punto flotante como a las enteras. En el caso de los enteros, también podemos observar que las promociones de enteros se invocan desde las conversiones aritméticas habituales. Y después de eso, cuando ambos operandos tienen al menos el rango de int , los operadores se equilibran con el mismo tipo, con la misma firma.

Esta es la razón por la cual a + b en el ejemplo 2 da un resultado extraño. Ambos operandos son enteros y son al menos de rango int , por lo que las promociones de enteros no se aplican. Los operandos no son del mismo tipo: a unsigned int tiene unsigned int y b tiene signed int . Por lo tanto, el operador b se convierte temporalmente a tipo unsigned int . Durante esta conversión, pierde la información del signo y termina como un gran valor.

La razón por la cual cambiar el tipo a short en el ejemplo 3 soluciona el problema, es porque short es un tipo entero pequeño. Lo que significa que ambos operandos son enteros promovidos al tipo int que está firmado. Después de la promoción de enteros, ambos operandos tienen el mismo tipo ( int ), no se necesita más conversión. Y luego la operación se puede llevar a cabo en un tipo firmado como se esperaba.

Esta publicación está destinada a ser utilizada como una pregunta frecuente con respecto a la promoción de enteros implícitos en C, particularmente la promoción implícita causada por las conversiones aritméticas habituales y / o las promociones de enteros.

Ejemplo 1)
¿Por qué esto da un número entero grande y extraño y no 255?

unsigned char x = 0; unsigned char y = 1; printf("%u/n", x - y);

Ejemplo 2)
¿Por qué esto da "-1 es mayor que 0"?

unsigned int a = 1; signed int b = -2; if(a + b > 0) puts("-1 is larger than 0");

Ejemplo 3)
¿Por qué cambiar el tipo en el ejemplo anterior para solucionar el problema?

unsigned short a = 1; signed short b = -2; if(a + b > 0) puts("-1 is larger than 0"); // will not print

(Estos ejemplos estaban destinados a una computadora de 32 o 64 bits con 16 bits cortos).


De acuerdo con la publicación anterior, quiero dar más información sobre cada ejemplo.

Ejemplo 1)

int main(){ unsigned char x = 0; unsigned char y = 1; printf("%u/n", x - y); printf("%d/n", x - y); }

Como unsigned char es más pequeño que int, aplicamos la promoción entera en ellos, entonces tenemos (int) x- (int) y = (int) (- 1) y unsigned int (-1) = 4294967295.

La salida del código anterior: (igual a lo que esperábamos)

4294967295 -1

¿Como arreglarlo?

Intenté lo que recomendaba la publicación anterior, pero realmente no funciona. Aquí está el código basado en la publicación anterior:

cambiar uno de ellos a unsigned int

int main(){ unsigned int x = 0; unsigned char y = 1; printf("%u/n", x - y); printf("%d/n", x - y); }

Dado que x ya es un entero sin signo, solo aplicamos la promoción de entero a y. Luego obtenemos (unsigned int) x- (int) y. Como todavía no tienen el mismo tipo, aplicamos las conversiones aritméticas habituales, obtenemos (unsigned int) x- (unsigned int) y = 4294967295.

La salida del código anterior: (igual a lo que esperábamos):

4294967295 -1

Del mismo modo, el siguiente código obtiene el mismo resultado:

int main(){ unsigned char x = 0; unsigned int y = 1; printf("%u/n", x - y); printf("%d/n", x - y); }

cambiar ambos a int sin firmar

int main(){ unsigned int x = 0; unsigned int y = 1; printf("%u/n", x - y); printf("%d/n", x - y); }

Dado que ambos son unsigned int, no se necesita ninguna promoción de enteros. Por la conversión aritmética habitual (tienen el mismo tipo), (unsigned int) x- (unsigned int) y = 4294967295.

La salida del código anterior: (igual a lo que esperábamos):

4294967295 -1

Una de las formas posibles de arreglar el código: (agregue un tipo de conversión al final)

int main(){ unsigned char x = 0; unsigned char y = 1; printf("%u/n", x - y); printf("%d/n", x - y); unsigned char z = x-y; printf("%u/n", z); }

La salida del código anterior:

4294967295 -1 255

Ejemplo 2)

int main(){ unsigned int a = 1; signed int b = -2; if(a + b > 0) puts("-1 is larger than 0"); printf("%u/n", a+b); }

Como ambos son enteros, no se necesita ninguna promoción de enteros. Por la conversión aritmética habitual, obtenemos (unsigned int) a + (unsigned int) b = 1 + 4294967294 = 4294967295.

La salida del código anterior: (igual a lo que esperábamos)

-1 is larger than 0 4294967295

¿Como arreglarlo?

int main(){ unsigned int a = 1; signed int b = -2; signed int c = a+b; if(c < 0) puts("-1 is smaller than 0"); printf("%d/n", c); }

La salida del código anterior:

-1 is smaller than 0 -1

Ejemplo 3)

int main(){ unsigned short a = 1; signed short b = -2; if(a + b < 0) puts("-1 is smaller than 0"); printf("%d/n", a+b); }

El último ejemplo solucionó el problema ya que a y b se convirtieron a int debido a la promoción de enteros.

La salida del código anterior:

-1 is smaller than 0 -1

Si tengo algunos conceptos mezclados, hágamelo saber. Gracias ~