¿Por qué se debe convertir un short a un int antes de las operaciones aritméticas en C y C++?
De las respuestas que obtuve de esta pregunta , parece que C++ heredó este requisito de conversión short
de int
cuando se realizan operaciones aritméticas de C. ¿Puedo explicarle por qué esto se introdujo en C en primer lugar? ¿Por qué no simplemente hacer estas operaciones comoshort
?
Por ejemplo ( tomado de la sugerencia de dyp en los comentarios ):
short s = 1, t = 2 ;
auto x = s + t ;
x
tendrá tipo de int .
Si nos fijamos en los fundamentos del estándar internacional—Lenguajes de programación—C en la sección 6.3.1.8
Conversiones aritméticas habituales , dice ( el énfasis es mío en el futuro ):
Las reglas del Estándar para estas conversiones son ligeras modificaciones de las de K&R: las modificaciones se adaptan a los tipos agregados y las reglas de preservación del valor. Se agregó una licencia explícita para realizar cálculos en un tipo "más amplio" de lo absolutamente necesario, ya que esto a veces puede producir un código más pequeño y más rápido, sin mencionar la respuesta correcta con mayor frecuencia . Los cálculos también se pueden realizar en un tipo "más limitado" mediante la regla como si siempre que se obtenga el mismo resultado final. La conversión explícita siempre se puede utilizar para obtener un valor en un tipo deseado
La sección 6.3.1.8 del borrador del estándar C99 cubre las conversiones aritméticas habituales que se aplican a operandos de expresiones aritméticas, por ejemplo, la sección 6.5.6 Operadores aditivos dice:
Si ambos operandos son de tipo aritmético, sobre ellos se realizan las conversiones aritméticas habituales .
También encontramos texto similar en la sección 6.5.5 Operadores multiplicativos . En el caso de un operando corto , primero se aplican las promociones de enteros de la sección 6.3.1.1 Booleanos, caracteres y enteros que dice:
Si un int puede representar todos los valores del tipo original, el valor se convierte en un int; de lo contrario, se convierte en un int sin signo. Éstas se denominan promociones enteras . 48) Todos los demás tipos no se modifican con las promociones de números enteros.
La discusión de 6.3.1.1
la sección de Justificación o Estándar Internacional—Lenguajes de Programación—C sobre promociones de números enteros es en realidad más interesante, voy a citar selectivamente porque es demasiado largo para citarlo en su totalidad:
Las implementaciones se dividieron en dos campos principales que pueden caracterizarse como preservación sin firmar y preservación de valor .
[...]
El enfoque de preservación sin firmar requiere promover los dos tipos sin firmar más pequeños a unsigned int. Esta es una regla simple y produce un tipo que es independiente del entorno de ejecución.
El enfoque de preservación de valores exige promover esos tipos a int firmados si ese tipo puede representar adecuadamente todos los valores del tipo original y, en caso contrario, promover esos tipos a int sin firmar. Por lo tanto, si el entorno de ejecución representa short como algo más pequeño que int, short sin firmar se convierte en int; de lo contrario, se vuelve int sin firmar.
Esto puede tener resultados bastante inesperados en algunos casos, como lo demuestra el comportamiento inconsistente de la conversión implícita entre tipos sin firmar y con signos más grandes ; hay muchos más ejemplos como ese. Aunque en la mayoría de los casos esto da como resultado que las operaciones funcionen como se esperaba.
No es tanto una característica del lenguaje como una limitación de las arquitecturas de procesadores físicos en las que se ejecuta el código. El int
tecleador en C suele ser el tamaño del registro de CPU estándar. Más silicio ocupa más espacio y más energía, por lo que en muchos casos la aritmética sólo se puede realizar con tipos de datos de "tamaño natural". Esto no es universalmente cierto, pero la mayoría de las arquitecturas todavía tienen esta limitación. En otras palabras, cuando se suman dos números de 8 bits, lo que realmente sucede en el procesador es algún tipo de aritmética de 32 bits seguida de una simple máscara de bits u otra conversión de tipo apropiada.
short
y char
los tipos se consideran según el tipo estándar de "tipos de almacenamiento", es decir, subrangos que puede utilizar para ahorrar algo de espacio pero que no le permitirán adquirir velocidad porque su tamaño no es "natural" para la CPU.
En ciertas CPU esto no es cierto, pero los buenos compiladores son lo suficientemente inteligentes como para darse cuenta de que si, por ejemplo, agrega una constante a un carácter sin firmar y almacena el resultado nuevamente en un carácter sin firmar, entonces no hay necesidad de realizar la unsigned char -> int
conversión. Por ejemplo con g++ el código generado para el bucle interno de
void incbuf(unsigned char *buf, int size) {
for (int i=0; i<size; i++) {
buf[i] = buf[i] + 1;
}
}
es solo
.L3:
addb $1, (%rdi,%rax)
addq $1, %rax
cmpl %eax, %esi
jg .L3
.L1:
donde puede ver que addb
se utiliza una instrucción de adición de caracteres sin firmar ().
Lo mismo sucede si realiza sus cálculos entre entradas cortas y almacena el resultado en entradas cortas.