Parece que uint32_t
es mucho más frecuente que uint_fast32_t
(me doy cuenta de que esto es una evidencia anecdótica). Sin embargo, eso me parece contrario a la intuición.
Casi siempre, cuando veo el uso de una implementación uint32_t
, todo lo que realmente quiere es un número entero que pueda contener valores de hasta 4.294.967.295 (generalmente un límite mucho más bajo en algún lugar entre 65.535 y 4.294.967.295).
Parece extraño usarlo luego uint32_t
, ya que la garantía de 'exactamente 32 bits' no es necesaria, y la garantía de 'más rápido disponible> = 32 bits'uint_fast32_t
parece ser exactamente la idea correcta. Además, aunque generalmente se implementa, en uint32_t
realidad no se garantiza que exista.
Entonces, ¿por qué uint32_t
se preferiría? ¿Es simplemente más conocido o existen ventajas técnicas sobre el otro?
uint32_fast_t
, que si lo entiendo correctamente, es de al menos 32 bits (¿significa que podría ser más? Me parece engañoso). Actualmente estoy usandouint32_t
y amigos en mi proyecto porque estoy empaquetando estos datos y enviándolos a través de la red, y quiero que el remitente y el receptor sepan exactamente qué tan grandes son los campos. Parece que esta puede no ser la solución más robusta ya que una plataforma puede no implementarseuint32_t
, pero todas las mías aparentemente lo hacen, así que estoy bien con lo que estoy haciendo.uint32_t
no le da eso (y es una lástima que no hayauint32_t_be
yuint32_t_le
, que sería más apropiado para casi todos los casos posibles en losuint32_t
que actualmente es la mejor opción).Respuestas:
uint32_t
se garantiza que tendrá casi las mismas propiedades en cualquier plataforma que lo admita. 1uint_fast32_t
tiene muy pocas garantías sobre cómo se comporta en diferentes sistemas en comparación.Si cambia a una plataforma que
uint_fast32_t
tiene un tamaño diferente, todo el código que se utilizauint_fast32_t
debe volver a probarse y validarse. Todos los supuestos de estabilidad se perderán. Todo el sistema funcionará de manera diferente.Al escribir su código, es posible que ni siquiera tenga acceso a un
uint_fast32_t
sistema que no tenga un tamaño de 32 bits.uint32_t
no funcionará de manera diferente (ver nota al pie).La corrección es más importante que la velocidad. Por tanto, la corrección prematura es un plan mejor que la optimización prematura.
En el caso de que estuviera escribiendo código para sistemas con
uint_fast32_t
64 bits o más, podría probar mi código para ambos casos y usarlo. Salvo necesidad y oportunidad, hacerlo es un mal plan.Finalmente,
uint_fast32_t
cuando lo está almacenando durante cualquier período de tiempo o número de instancias, puede ser más lento queuint32
simplemente debido a problemas de tamaño de caché y ancho de banda de memoria. Las computadoras de hoy están mucho más ligadas a la memoria que a la CPU, yuint_fast32_t
podrían ser más rápidas de forma aislada, pero no después de tener en cuenta la sobrecarga de la memoria.1 Como @chux ha señalado en un comentario, si
unsigned
es mayor queuint32_t
, la aritmética activauint32_t
pasa por las promociones habituales de enteros, y si no, permanece comouint32_t
. Esto puede provocar errores. Nada es perfecto.fuente
unsigned
es más ancho queuint32_t
y luegouint32_t
en una plataforma pasa por las promociones enteras habituales y en otra no. Sin embargo, conuint32_t
estos problemas matemáticos enteros se reducen significativamente.uint32_t
es para donde los detalles exactos de la representación de la máquina del tipo son importantes, mientras queuint_fast32_t
es para donde la velocidad computacional es más importante, el (des) signo y el rango mínimo son importantes, y los detalles de representación no son esenciales. También hayuint_least32_t
donde la (des) firma y el rango mínimo son más importantes, la compacidad es más importante que la velocidad, y la representación exacta no es esencial.uint32_t
lugar de los otros tipos es porque generalmente no tienen ese hardware para realizar pruebas . (Lo mismo ocurreint32_t
en menor medida, e inclusoint
yshort
).unsigned short
==uint32_t
yint
==int48_t
. Si calcula algo como(uint32_t)0xFFFFFFFF * (uint32_t)0xFFFFFFFF
, entonces los operandos se promocionansigned int
y desencadenarán un desbordamiento de entero con signo, que es un comportamiento indefinido. Vea esta pregunta.Nota: El nombre incorrecto
uint32_fast_t
debería seruint_fast32_t
.uint32_t
tiene una especificación más estrictauint_fast32_t
y, por lo tanto, ofrece una funcionalidad más consistente.uint32_t
pros:uint32_t
contras:Ej: Plataformas que carecen de 8/16 números enteros / 32 bits (9/18/ 36 -bit, otros ).
Ej .: Plataformas que utilizan un complemento distinto de 2. viejo 2200
uint_fast32_t
pros:Esto siempre permite que todas las plataformas, nuevas y antiguas, utilicen tipos rápidos / mínimos.
uint_fast32_t
contras:uint32_fast_t
. Parece que muchos simplemente no necesitan ni usan este tipo. ¡Ni siquiera usamos el nombre correcto!uint_fast32_t
es solo una aproximación de primer orden.Al final, lo que es mejor depende del objetivo de codificación. A menos que se codifique para una portabilidad muy amplia o alguna función de rendimiento mejorada, utilice
uint32_t
.Hay otro problema al usar estos tipos que entra en juego: su rango en comparación con
int/unsigned
Presumiblemente
uint_fastN_t
podría ser el rango deunsigned
. Esto no está especificado, pero es una condición cierta y comprobable.Por lo tanto,
uintN_t
es más probable queuint_fastN_t
sea más estrecho elunsigned
. Esto significa queuintN_t
es más probable que el código que usa matemáticas esté sujeto a promociones de números enteros queuint_fastN_t
cuando se trata de portabilidad.Con esta preocupación: ventaja de portabilidad
uint_fastN_t
con operaciones matemáticas seleccionadas.Nota al margen sobre en
int32_t
lugar deint_fast32_t
: en máquinas raras,INT_FAST32_MIN
puede ser -2,147,483,647 y no -2,147,483,648. El punto más importante: los(u)intN_t
tipos están estrictamente especificados y conducen a un código portátil.fuente
uint32_fast_t
es un tipo de 64 bits, por lo que guarda la extensión de signo ocasional y permite, enimul rax, [mem]
lugar de una instrucción de carga de extensión cero separada, cuando se usa con números enteros o punteros de 64 bits. Pero eso es todo lo que obtiene por el precio de duplicar la huella de caché y el tamaño del código adicional (REX prefijado en todo)popcnt
. Y recuerde, solo es seguro usar este tipo para valores de 32 bits, porque es más pequeño en otras arquitecturas, por lo que está pagando este costo por nada.uint32_fast_t
en x86 sea una elección terrible. Las operaciones que son más rápidas son pocas y distantes entre sí, y el beneficio cuando ocurren son en su mayoría minúsculas: las diferencias para elimul rax, [mem]
caso que menciona @PeterCordes son muy , muy pequeñas: una sola uop en el dominio fusionado y cero en el dominio no fusionado. En la mayoría de los escenarios interesantes, ni siquiera agregará un solo ciclo. Equilibre eso con el doble del uso de memoria y una peor vectorización, es difícil ver que gane con mucha frecuencia.fast_t
aún peorint
: no solo tiene diferentes tamaños en diferentes plataformas, sino que tendría diferentes tamaños dependiendo de las decisiones de optimización y diferentes tamaños en diferentes archivos. Como cuestión práctica, creo que no puede funcionar incluso con la optimización del programa completo: los tamaños en C y C ++ son fijossizeof(uint32_fast_t)
o cualquier cosa que lo determine, incluso directamente, siempre debe devolver el mismo valor, por lo que sería muy difícil para el compilador hacer tal transformación.Respuesta tonta:
uint32_fast_t
, la ortografía correcta esuint_fast32_t
.Respuesta práctica:
uint32_t
oint32_t
para su semántica precisa, exactamente 32 bits con aritmética envolvente sin firmar (uint32_t
) o representación de complemento a 2 (int32_t
). Losxxx_fast32_t
tipos pueden ser más grandes y, por lo tanto, inapropiados para almacenar en archivos binarios, usar en matrices y estructuras empaquetadas o enviar a través de una red. Además, es posible que ni siquiera sean más rápidos.Respuesta pragmática:
uint_fast32_t
, como se demuestra en los comentarios y respuestas, y probablemente asumen que es simpleunsigned int
tener la misma semántica, aunque muchas arquitecturas actuales todavía tienen 16 bitsint
y algunas muestras raras de Museum tienen otros tamaños de int extraños menores de 32.Respuesta de UX:
uint32_t
,uint_fast32_t
es más lento de usar: lleva más tiempo escribir, especialmente teniendo en cuenta la búsqueda de ortografía y semántica en la documentación de C ;-)La elegancia importa, (obviamente basada en opiniones):
uint32_t
Se ve lo suficientemente mal como para que muchos programadores prefieran definir el suyo propiou32
o eluint32
tipo ... Desde esta perspectiva,uint_fast32_t
parece torpe más allá de la reparación. No es de extrañar que se sienta en el banco con sus amigosuint_least32_t
y demás.fuente
std::reference_wrapper
que supongo, pero a veces me pregunto si el comité de estándares realmente quiere que se utilicen los tipos que estandariza ...Una razón es que
unsigned int
ya es "más rápido" sin la necesidad de ningún typedefs especial o la necesidad de incluir algo. Entonces, si lo necesita rápido, simplemente use el fundamentalint
o elunsigned int
tipo.Si bien el estándar no garantiza explícitamente que sea más rápido, indirectamente lo hace indicando "Los enteros simples tienen el tamaño natural sugerido por la arquitectura del entorno de ejecución" en 3.9.1. En otras palabras,
int
(o su contraparte sin firmar) es con lo que el procesador se siente más cómodo.Ahora, por supuesto, no sabes qué tamaño
unsigned int
podría ser. Solo sabes que es al menos tan grande comoshort
(y creo recordar queshort
debe tener al menos 16 bits, ¡aunque ahora no puedo encontrar eso en el estándar!). Por lo general, son simplemente 4 bytes, pero en teoría podría ser más grande o, en casos extremos, incluso más pequeño (aunque personalmente nunca me he encontrado con una arquitectura en la que este fuera el caso, ni siquiera en computadoras de 8 bits en la década de 1980. ... tal vez algunos microcontroladores, quién saberesulta que sufro de demencia,int
era muy claramente de 16 bits en ese entonces).El estándar C ++ no se molesta en especificar cuáles son los
<cstdint>
tipos o qué garantizan, simplemente menciona "lo mismo que en C".uint32_t
, según el estándar C, garantiza que obtiene exactamente 32 bits. Nada diferente, nada menos y nada de relleno. A veces, esto es exactamente lo que necesita y, por lo tanto, es muy valioso.uint_least32_t
garantiza que sea cual sea el tamaño, no puede ser menor de 32 bits (pero muy bien podría ser mayor). A veces, pero mucho más raramente que un witdh exacto o "no me importa", esto es lo que quieres.Por último,
uint_fast32_t
es algo superfluo en mi opinión, excepto para propósitos de documentación de intención. El estándar C dice "designa un tipo de entero que suele ser el más rápido" (tenga en cuenta la palabra "normalmente") y menciona explícitamente que no es necesario que sea el más rápido para todos los propósitos. En otras palabras,uint_fast32_t
es casi lo mismo queuint_least32_t
, que también suele ser el más rápido, solo que sin garantía (pero no garantía de ninguna manera).Dado que la mayoría de las veces no te importa el tamaño exacto o quieres exactamente 32 (o 64, a veces 16) bits, y dado que el tipo "no importa"
unsigned int
es el más rápido de todos modos, esto explica por quéuint_fast32_t
no es así. usado frecuentemente.fuente
int
en 8 bits, no puedo recordar ninguno de esos días que usaba algo más grande. Si la memoria funciona, los compiladores para la arquitectura x86 segmentada también utilizaron 16 bitsint
.int
el 68000 tenía 32 bits (lo que pensé, como ejemplo). No fue ...int
estaba destinado a ser el tipo más rápido en el pasado con un ancho mínimo de 16 bits (es por eso que C tiene una regla de promoción de enteros), pero hoy con arquitecturas de 64 bits esto ya no es cierto. Por ejemplo, los enteros de 8 bytes son más rápidos que los enteros de 4 bytes en x86_64 bit porque con enteros de 4 bytes el compilador tiene que insertar instrucciones adicionales que expanden el valor de 4 bytes en un valor de 8 bytes antes de compararlo con otros valores de 8 bytes.long
, por razones históricas, debe ser de 32 bits yint
ahora se requiere que no sea más ancho quelong
, por lo que esint
posible que deba permanecer en 32 bits incluso cuando 64 bits serían más rápidos.No he visto evidencia que
uint32_t
se utilice para su rango. En cambio, la mayoría de las veces que he vistouint32_t
se usa para contener exactamente 4 octetos de datos en varios algoritmos, ¡con semántica de cambio y envolvente garantizada!También hay otras razones para usar en
uint32_t
lugar deuint_fast32_t
: A menudo es que proporcionará ABI estable. Además, el uso de la memoria se puede conocer con precisión. Esto contrarresta en gran medida lo que sea la ganancia de velocidaduint_fast32_t
, siempre que ese tipo sea distinto del deuint32_t
.Para valores <65536, ya existe un tipo útil, se llama
unsigned int
(unsigned short
se requiere que tenga al menos ese rango también, perounsigned int
es del tamaño de palabra nativa) Para valores <4294967296, hay otro llamadounsigned long
.Y, por último, la gente no lo usa
uint_fast32_t
porque es muy largo de escribir y fácil de escribir mal: Dfuente
short
edición.int
es presumiblemente el rápido cuando es distinto deshort
.unsigned int
lugar deuint16_fast_t
significa que afirma saber más que el compilador.unsigned long
no es una buena opción si su plataforma tiene 64 bitslong
y solo necesita números<2^32
.uint16_t
yuint_fast16_t
. Siuint_fast16_t
se especificaran más libremente que los tipos enteros normales, de modo que su rango no necesita ser consistente para los objetos cuyas direcciones no se toman, eso podría ofrecer algunos beneficios de rendimiento en plataformas que realizan aritmética de 32 bits internamente pero tienen un bus de datos de 16 bits. . Sin embargo, la Norma no permite tal flexibilidad.Muchas rasones.
En resumen, los tipos "rápidos" son basura sin valor. Si realmente necesita averiguar qué tipo es más rápido para una aplicación determinada, necesita comparar su código en su compilador.
fuente
Desde el punto de vista de la corrección y la facilidad de codificación,
uint32_t
tiene muchas ventajas sobreuint_fast32_t
en particular, debido al tamaño definido con mayor precisión y la semántica aritmética, como muchos usuarios han señalado anteriormente.Lo que quizás se ha pasado por alto es la supuesta ventaja de
uint_fast32_t
que puede ser más rápido , pero nunca se materializó de manera significativa. La mayoría de los procesadores de 64 bits que han dominado la era de los 64 bits (x86-64 y Aarch64 en su mayoría) evolucionaron a partir de arquitecturas de 32 bits y tienen operaciones nativas rápidas de 32 bits incluso en el modo de 64 bits. Así queuint_fast32_t
es lo mismo queuint32_t
en esas plataformas.Incluso si algunas de las plataformas "también ejecutadas" como POWER, MIPS64, SPARC solo ofrecen operaciones ALU de 64 bits, la gran mayoría de las operaciones interesantes de 32 bits se pueden realizar sin problemas en registros de 64 bits: los de 32 bits inferiores lo harán tener los resultados deseados (y todas las plataformas principales al menos le permiten cargar / almacenar 32 bits). El desplazamiento a la izquierda es el principal problema, pero incluso eso se puede optimizar en muchos casos mediante optimizaciones de seguimiento de valor / rango en el compilador.
Dudo que el desplazamiento a la izquierda ocasionalmente un poco más lento o la multiplicación 32x32 -> 64 supere el doble del uso de memoria para tales valores, en todas las aplicaciones excepto en las más oscuras.
Finalmente, señalaré que si bien la compensación se ha caracterizado en gran medida como "uso de memoria y potencial de vectorización" (a favor de
uint32_t
) versus recuento / velocidad de instrucciones (a favor deuint_fast32_t
), incluso eso no me queda claro. Sí, en algunas plataformas necesitará instrucciones adicionales para algunas operaciones de 32 bits, pero también guardará algunas instrucciones porque:struct two32{ uint32_t a, b; }
en merax
gustatwo32{1, 2}
se puede optimizar en una sola,mov rax, 0x20001
mientras que la versión de 64 bits necesita dos instrucciones. En principio, esto también debería ser posible para operaciones aritméticas adyacentes (misma operación, diferente operando), pero no lo he visto en la práctica.Los tipos de datos más pequeños a menudo aprovechan mejores convenciones de llamadas modernas como SysV ABI, que empaquetan datos de estructura de datos de manera eficiente en registros. Por ejemplo, puede devolver una estructura de hasta 16 bytes en los registros
rdx:rax
. Para una función que devuelve una estructura con 4uint32_t
valores (inicializada a partir de una constante), eso se traduce enret_constant32(): movabs rax, 8589934593 movabs rdx, 17179869187 ret
La misma estructura con 4 64 bits
uint_fast32_t
necesita un movimiento de registro y cuatro tiendas en la memoria para hacer lo mismo (y la persona que llama probablemente tendrá que leer los valores de la memoria después de la devolución):ret_constant64(): mov rax, rdi mov QWORD PTR [rdi], 1 mov QWORD PTR [rdi+8], 2 mov QWORD PTR [rdi+16], 3 mov QWORD PTR [rdi+24], 4 ret
De manera similar, al pasar argumentos de estructura, los valores de 32 bits se empaquetan aproximadamente el doble de densamente en los registros disponibles para los parámetros, por lo que es menos probable que se quede sin argumentos de registro y tenga que pasar a la pila 1 .
Incluso si elige usarlo
uint_fast32_t
para lugares donde "la velocidad importa", a menudo también tendrá lugares donde necesite un tipo de tamaño fijo. Por ejemplo, al pasar valores para salida externa, de entrada externa, como parte de su ABI, como parte de una estructura que necesita un diseño específico, o porque usa inteligentementeuint32_t
para grandes agregaciones de valores para ahorrar espacio en la memoria. En los lugares donde susuint_fast32_t
tipos y `` uint32_t` necesitan interactuar, puede encontrar (además de la complejidad del desarrollo), extensiones de signos innecesarias u otro código relacionado con la discrepancia de tamaño. Los compiladores hacen un buen trabajo optimizando esto en muchos casos, pero aún no es inusual ver esto en la salida optimizada cuando se mezclan tipos de diferentes tamaños.Puedes jugar con algunos de los ejemplos anteriores y más en godbolt .
1 Para ser claros, la convención de empaquetar estructuras de manera ajustada en registros no siempre es una clara ventaja para valores más pequeños. Significa que es posible que los valores más pequeños tengan que "extraerse" antes de que puedan utilizarse. Por ejemplo, una función simple que devuelve la suma de los dos miembros de la estructura juntos necesita un
mov rax, rdi; shr rax, 32; add edi, eax
tiempo para la versión de 64 bits, cada argumento obtiene su propio registro y solo necesita un soloadd
olea
. Aún así, si acepta que el diseño de "empaquetar las estructuras firmemente al pasar" tiene sentido en general, los valores más pequeños aprovecharán más esta característica.fuente
uint_fast32_t
, lo cual es un error en mi opinión. (Aparentemente, Windowsuint_fast32_t
es un tipo de 32 bits en Windows). Al ser 64 bits en x86-64 Linux es la razón por la que nunca recomendaría que nadie useuint_fast32_t
: está optimizado para un recuento bajo de instrucciones (los argumentos de función y los valores de retorno nunca necesitan extensión cero para utilizar como índice de matriz) no para la velocidad general o el tamaño del código en una de las principales plataformas importantes.pair<int,bool>
opair<int,int>
. Si ambos miembros no son constantes en tiempo de compilación, normalmente hay más que un OR, y la persona que llama tiene que descomprimir los valores devueltos. ( bugs.llvm.org/show_bug.cgi?id=34840 LLVM optimiza el paso de valor de retorno para funciones privadas, y debería tratar el int de 32 bits como si tomara el todo,rax
por lo quebool
está separado endl
lugar de necesitar una constante de 64 bits paratest
it.)__attribute__((noinline))
exactamente para asegurarse de que gcc no alinee la función de manejo de errores y coloque un montón depush rbx
/...
/pop rbx
/ ... en la ruta rápida de algunas funciones importantes del kernel que tienen muchas llamadas y no están alineadas.A efectos prácticos,
uint_fast32_t
es completamente inútil. Está definido incorrectamente en la plataforma más extendida (x86_64) y realmente no ofrece ninguna ventaja en ningún lugar a menos que tenga un compilador de muy baja calidad. Conceptualmente, nunca tiene sentido utilizar los tipos "rápidos" en estructuras / matrices de datos: cualquier ahorro que obtenga del tipo que es más eficiente para operar será eclipsado por el costo (pérdidas de caché, etc.) de aumentar el tamaño de su conjunto de datos de trabajo. Y para las variables locales individuales (contadores de bucles, temporales, etc.), un compilador que no sea de juguete generalmente puede trabajar con un tipo más grande en el código generado si eso es más eficiente, y solo truncar al tamaño nominal cuando sea necesario para la corrección (y con tipos firmados, nunca es necesario).La única variante que es teóricamente útil es
uint_least32_t
, para cuando necesita poder almacenar cualquier valor de 32 bits, pero quiere ser portátil a máquinas que carecen de un tipo de tamaño exacto de 32 bits. Prácticamente, hablando, sin embargo, eso no es algo de lo que deba preocuparse.fuente
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:
int
en todas las plataformas.int
sea de al menos 32 bits.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:int
tener un tamaño de 32 bits.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
hacerlouint_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 queuint32_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 %rbx sub $0x20,%rsp mov %rcx,%rbx callq d <sum+0xd> add %rbx,%rax add $0x20,%rsp pop %rbx retq
Ahora, si cambia
get()
el valor de retorno auint_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 Linuxuint_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 siint
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 usarlosize_t
para este propósito, ya que generalmente coincidirá con elnative
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).fuente
uint_fast32_t
vuelve ambigua, salvo en circunstancias muy selectas. Sospecho que la razónuint_fastN_t
principal es acomodar el "no usemosunsigned
como de 64 bits, aunque a menudo es más rápido en una nueva plataforma, porque se romperá demasiado código", pero "todavía quiero un tipo rápido de al menos N bits . " Te volvería a UV si pudiera.int
64 bits, y generalmente una pérdida de velocidad de la huella de caché.int
64 bits, suuint32_t
typedef de ancho fijo necesitaría uno__attribute__
u otro truco, o algún tipo personalizado que sea más pequeño queint
. (Oshort
, pero luego tienes el mismo problema parauint16_t
.) Nadie quiere eso. 32 bits es lo suficientemente ancho para casi todo (a diferencia de 16 bits); usar enteros de 32 bits cuando eso es todo lo que necesita no es "ineficiente" de ninguna manera significativa en una máquina de 64 bits.En muchos casos, cuando un algoritmo funciona con una matriz de datos, la mejor forma de mejorar el rendimiento es minimizar el número de pérdidas de caché. Cuanto más pequeño sea cada elemento, más de ellos pueden caber en el caché. Esta es la razón por la que todavía se escribe mucho código para usar punteros de 32 bits en máquinas de 64 bits: no necesitan nada cercano a 4 GiB de datos, pero el costo de hacer todos los punteros y compensaciones necesita ocho bytes en lugar de cuatro. sería sustancial.
También hay algunas ABI y protocolos especificados para necesitar exactamente 32 bits, por ejemplo, direcciones IPv4. Eso es lo que
uint32_t
realmente significa: use exactamente 32 bits, independientemente de si eso es eficiente en la CPU o no. Estos solían declararse comolong
ounsigned long
, lo que causó muchos problemas durante la transición de 64 bits. Si solo necesita un tipo sin signo que contenga números hasta al menos 2³²-1, esa ha sido la definición deunsigned long
desde que salió el primer estándar C. En la práctica, sin embargo, se asumió que un código antiguolong
podría contener cualquier puntero o desplazamiento de archivo o marca de tiempo, y se asumió que el código antiguo tenía exactamente 32 bits de ancho, por lo que los compiladores no necesariamente pueden hacerlong
lo mismoint_fast32_t
sin romper demasiadas cosas.En teoría, sería más apto para el futuro que un programa lo use
uint_least32_t
, y tal vez incluso cargaruint_least32_t
elementos en unauint_fast32_t
variable para los cálculos. ¡Una implementación que no tuviera ningúnuint32_t
tipo podría incluso declararse en conformidad formal con la norma! (Simplemente no sería capaz de compilar muchos programas existentes.) En la práctica, no hay arquitectura ya dóndeint
,uint32_t
yuint_least32_t
no son los mismos, y ninguna ventaja, actualmente , a la realización deuint_fast32_t
. Entonces, ¿por qué complicar demasiado las cosas?Sin embargo, mire la razón por la que todos los
32_t
tipos debían existir cuando ya lo teníamoslong
, y verá que esas suposiciones han estallado en nuestras caras antes. Es posible que su código termine ejecutándose algún día en una máquina donde los cálculos de 32 bits de ancho exacto son más lentos que el tamaño de la palabra nativa, y hubiera sido mejor usarlouint_least32_t
para almacenamiento yuint_fast32_t
cálculo religiosamente. O si cruzas ese puente cuando llegas y solo quieres algo simple, ahí estáunsigned long
.fuente
int
no es de 32 bits, por ejemplo ILP64. No es que sean comunes.int
tendrá 32 bits de ancho. El tipoMKL_INT
es lo que cambiará.Para dar una respuesta directa: creo que la verdadera razón por la que
uint32_t
se usa overuint_fast32_t
ouint_least32_t
es simplemente que es más fácil de escribir y, debido a que es más corto, mucho más agradable de leer: si crea estructuras con algunos tipos, y algunos de ellos sonuint_fast32_t
o similar, entonces a menudo es difícil alinearlos bien conint
ubool
otros tipos en C, que son bastante cortos (caso en cuestión:char
vs.character
). Por supuesto, no puedo respaldar esto con datos duros, pero las otras respuestas solo pueden adivinar la razón también.En cuanto a razones técnicas para preferir
uint32_t
, no creo que las haya; cuando sea absolutamente necesario un int sin firmar exacto de 32 bits, entonces este tipo es su única opción estandarizada. En casi todos los demás casos, las otras variantes son técnicamente preferibles, específicamente,uint_fast32_t
si le preocupa la velocidad yuint_least32_t
si le preocupa el espacio de almacenamiento. El usouint32_t
en cualquiera de estos casos corre el riesgo de no poder compilar, ya que no se requiere que exista el tipo.En la práctica, los
uint32_t
tipos y relacionados existen en todas las plataformas actuales, excepto algunos DSP muy raros (hoy en día) o implementaciones de broma, por lo que existe poco riesgo real al usar el tipo exacto. Del mismo modo, si bien puede encontrarse con penalizaciones de velocidad con los tipos de ancho fijo, ya no son (en las CPU modernas) paralizantes.Por eso, creo, el tipo más corto simplemente gana en la mayoría de los casos, debido a la pereza del programador.
fuente