Según tengo entendido, int
inicialmente se suponía que era un tipo entero "nativo" con una garantía adicional de que debería tener al menos 16 bits de tamaño, algo que se consideraba un tamaño "razonable" en ese entonces.
Cuando las plataformas de 32 bits se volvieron más comunes, podemos decir que el tamaño "razonable" ha cambiado a 32 bits:
- Windows moderno usa 32 bits
int
en todas las plataformas.
- POSIX garantiza que
int
sea de al menos 32 bits.
- C #, Java tiene un tipo
int
que se garantiza que sea exactamente de 32 bits.
Pero cuando la plataforma de 64 bits se convirtió en la norma, nadie se expandió int
para convertirse en un entero de 64 bits debido a:
- Portabilidad: mucho código depende de
int
tener un tamaño de 32 bits.
- Consumo de memoria: duplicar el uso de memoria para cada uno
int
puede no ser razonable en la mayoría de los casos, ya que en la mayoría de los casos las cifras en uso son mucho menores que 2 mil millones.
Ahora, ¿por qué preferirías uint32_t
hacerlo uint_fast32_t
? Por la misma razón, los lenguajes, C # y Java siempre usan números enteros de tamaño fijo: el programador no escribe código pensando en tamaños posibles de diferentes tipos, escribe para una plataforma y prueba el código en esa plataforma. La mayor parte del código depende implícitamente de tamaños específicos de tipos de datos. Y es por eso que uint32_t
es una mejor opción para la mayoría de los casos: no permite ninguna ambigüedad con respecto a su comportamiento.
Además, ¿es uint_fast32_t
realmente el tipo más rápido en una plataforma con un tamaño igual o superior a 32 bits? Realmente no. Considere este compilador de código de GCC para x86_64 en Windows:
extern uint64_t get(void);
uint64_t sum(uint64_t value)
{
return value + get();
}
El ensamblado generado se ve así:
push
sub $0x20,
mov
callq d <sum+0xd>
add
add $0x20,
pop
retq
Ahora, si cambia get()
el valor de retorno a uint_fast32_t
(que es 4 bytes en Windows x86_64) obtendrá esto:
push %rbx
sub $0x20,%rsp
mov %rcx,%rbx
callq d <sum+0xd>
mov %eax,%eax ; <-- additional instruction
add %rbx,%rax
add $0x20,%rsp
pop %rbx
retq
Observe cómo el código generado es casi el mismo excepto por una mov %eax,%eax
instrucción adicional después de la llamada a la función que está destinada a expandir el valor de 32 bits en un valor de 64 bits.
No existe tal problema si solo usa valores de 32 bits, pero probablemente usará aquellos con size_t
variables (¿tamaños de matriz probablemente?) Y esos son de 64 bits en x86_64. En Linux uint_fast32_t
es de 8 bytes, por lo que la situación es diferente.
Muchos programadores usan int
cuando necesitan devolver un valor pequeño (digamos en el rango [-32,32]). Esto funcionaría perfectamente si int
fuera un tamaño entero nativo de la plataforma, pero como no está en plataformas de 64 bits, otro tipo que coincida con el tipo nativo de la plataforma es una mejor opción (a menos que se use con frecuencia con otros enteros de menor tamaño).
Básicamente, independientemente de lo que diga el estándar, de uint_fast32_t
todos modos está roto en algunas implementaciones. Si le preocupa la instrucción adicional generada en algunos lugares, debe definir su propio tipo de entero "nativo". O puede usarlo size_t
para este propósito, ya que generalmente coincidirá con el native
tamaño (no estoy incluyendo plataformas antiguas y oscuras como 8086, solo plataformas que pueden ejecutar Windows, Linux, etc.).
Otro signo que muestra que int
se suponía que era un tipo entero nativo es "regla de promoción de enteros". La mayoría de las CPU solo pueden realizar operaciones en modo nativo, por lo que la CPU de 32 bits generalmente solo puede hacer sumas, restas, etc. de 32 bits (las CPU Intel son una excepción aquí). Los tipos enteros de otros tamaños solo se admiten mediante instrucciones de carga y almacenamiento. Por ejemplo, el valor de 8 bits debe cargarse con la instrucción apropiada "cargar 8 bits con signo" o "cargar 8 bits sin signo" y expandirá el valor a 32 bits después de la carga. Sin la regla de promoción de enteros, los compiladores C tendrían que agregar un poco más de código para las expresiones que usan tipos más pequeños que el tipo nativo. Desafortunadamente, esto ya no es válido con arquitecturas de 64 bits, ya que los compiladores ahora tienen que emitir instrucciones adicionales en algunos casos (como se mostró arriba).