¿Tiene sentido una prohibición 'larga'?

105

En el mundo actual de C ++ (o C) multiplataforma, tenemos :

Data model  | short |   int |   long | long long | pointers/size_t  | Sample operating systems
... 
LLP64/IL32P64   16      32      32     64           64                Microsoft Windows (x86-64 and IA-64)
LP64/I32LP64    16      32      64     64           64                Most Unix and Unix-like systems, e.g. Solaris, Linux, BSD, and OS X; z/OS
...

Lo que esto significa hoy, es que para cualquier entero "común" (firmado), int será suficiente y posiblemente aún se puede usar como el tipo de entero predeterminado al escribir el código de aplicación de C ++. También, para fines prácticos actuales , tendrá un tamaño uniforme en todas las plataformas.

Si un caso de uso requiere al menos 64 bits, hoy podemos usar long long , aunque posiblemente usemos uno de los los tipos que especifican bitness o el tipo __int64 podrían tener más sentido.

Esto deja a long en el medio, y estamos considerando prohibir totalmente el uso de long de nuestro código de aplicación .

¿Esto tendría sentido , o hay un caso para usar long en el código C ++ (o C) moderno que debe ejecutarse en varias plataformas? (la plataforma es de escritorio, dispositivos móviles, pero no cosas como microcontroladores, DSP, etc.)

Enlaces de fondo posiblemente interesantes:

pregunta Martin Ba 05.05.2016 - 22:14

4 respuestas

17

La única razón por la que usaría long hoy es al llamar o implementar una interfaz externa que la use.

Como dice en su publicación, breve y actual tienen características razonablemente estables en todas las principales plataformas de escritorio / servidor / móviles hoy en día y no veo ninguna razón para que eso cambie en el futuro inmediato. Así que veo pocas razones para evitarlos en general.

long por otro lado es un desastre. En todos los sistemas de 32 bits que conozco tenían las siguientes características.

  1. Era exactamente de 32 bits de tamaño.
  2. Era del mismo tamaño que una dirección de memoria.
  3. Era del mismo tamaño que la unidad de datos más grande que se podía mantener en un registro normal y trabajar con una sola instrucción.

Se escribieron grandes cantidades de código en función de una o más de estas características. Sin embargo, con el cambio a 64 bits no fue posible preservarlos a todos. Las plataformas similares a Unix fueron para LP64 que conservó las características 2 y 3 al costo de la característica 1. Win64 fue para LLP64 que conservó la característica 1 al costo de las características 2 y 3. El resultado es que ya no puede confiar en ninguna de esas características y que IMO deja pocas razones para usar long .

Si quieres un tipo que tenga exactamente el tamaño de 32 bits, debes usar int32_t .

Si quieres un tipo del mismo tamaño que un puntero, debes usar intptr_t (o mejor uintptr_t ).

Si desea un tipo que sea el elemento más grande en el que se pueda trabajar en un solo registro / instrucción, desafortunadamente no creo que el estándar proporcione uno. size_t debería estar en la mayoría de las plataformas comunes, pero no estaría en x32 .

P.S.

No me molestaría con los tipos "rápidos" o "menos". Los tipos "menos" solo importan si te importa la portabilidad para ocultar las arquitecturas donde CHAR_BIT != 8 . El tamaño de los tipos "rápidos" en la práctica parece ser bastante arbitrario. Parece que Linux los hace al menos del mismo tamaño que el puntero, lo cual es una tontería en plataformas de 64 bits con soporte rápido de 32 bits como x86-64 y arm64. IIRC iOS los hace lo más pequeños posible. No estoy seguro de lo que hacen otros sistemas.

P.P.S

Una razón para usar unsigned long (pero no simple long ) es porque se garantiza que tiene un comportamiento de módulo. Desafortunadamente, debido a las fallidas reglas de promoción de C, los tipos sin firma más pequeños que int no tienen comportamiento de módulo.

En todas las plataformas principales, hoy uint32_t es del mismo tamaño o más grande que int y, por lo tanto, tiene un comportamiento de módulo. Sin embargo, históricamente ha habido y podría haber teóricamente en las plataformas futuras donde int es de 64 bits y, por lo tanto, uint32_t no tiene un comportamiento de módulo.

Personalmente, diría que es mejor obtener el hábito de forzar el comportamiento de módulo usando "1u *" o "0u +" al comienzo de tus ecuaciones, ya que esto funcionará para cualquier tamaño de tipo sin signo.

    
respondido por el Peter Green 06.05.2016 - 03:08
201

Como mencionó en su pregunta, el software moderno tiene que ver con la interoperación entre plataformas y sistemas en Internet. Los estándares C y C ++ dan rangos para tamaños de tipo entero, no tamaños específicos (en contraste con lenguajes como Java y C #).

Para asegurarse de que su software compilado en diferentes plataformas funciona con los mismos datos de la misma manera que y para asegurarse de que otro software pueda interactuar con su software utilizando los mismos tamaños, debe usar un tamaño fijo enteros.

Ingrese <cstdint> que proporciona exactamente eso y es un encabezado estándar que requiere todas las plataformas de compilación y biblioteca estándar. Para proveer. Nota: este encabezado solo fue necesario a partir de C ++ 11, pero muchas implementaciones antiguas de la biblioteca lo proporcionaron de todos modos.

¿Quieres un entero sin signo de 64 bits? Utilice uint64_t . Firmado 32 bits entero? Utilice int32_t . Si bien los tipos en el encabezado son opcionales, las plataformas modernas deberían admitir todos los tipos definidos en ese encabezado.

Algunas veces se necesita un ancho de bit específico, por ejemplo, en una estructura de datos utilizada para comunicarse con otros sistemas. Otras veces no lo es. Para situaciones menos estrictas, <cstdint> proporciona tipos que tienen un ancho mínimo.

Hay menos variantes: int_leastXX_t será un tipo entero de mínimo XX bits. Utilizará el tipo más pequeño que proporciona XX bits, pero se permite que el tipo sea más grande que el número especificado de bits. En la práctica, estos son típicamente los mismos que los tipos descritos anteriormente que dan el número exacto de bits.

También hay variantes rápidas : int_fastXX_t es al menos XX bits, pero debe usar un tipo que se ejecute rápidamente en una plataforma en particular. La definición de "rápido" en este contexto no está especificada. Sin embargo, en la práctica, esto generalmente significa que un tipo más pequeño que el tamaño de registro de una CPU puede ser un alias de un tipo de tamaño de registro de la CPU. Por ejemplo, el encabezado de Visual C ++ 2015 especifica que int_fast16_t es un número entero de 32 bits porque la aritmética de 32 bits es en general más rápida en x86 que en aritmética de 16 bits.

Todo esto es importante porque debería poder utilizar tipos que puedan contener los resultados de los cálculos que realiza su programa independientemente de la plataforma. Si un programa produce resultados correctos en una plataforma pero resultados incorrectos en otra debido a las diferencias en el desbordamiento de enteros, eso es malo. Al utilizar los tipos de enteros estándar, garantiza que los resultados en diferentes plataformas serán los mismos con respecto al tamaño de los enteros utilizados (por supuesto, podría haber otras diferencias entre las plataformas además del ancho de enteros). / p>

Entonces, sí, long debería estar prohibido del código C ++ moderno. Por lo tanto, int , short y long long .

    
respondido por el user22815 05.05.2016 - 22:24
37

No, prohibir los tipos de enteros incorporados sería absurdo. Sin embargo, tampoco deberían ser objeto de abuso.

Si necesita un entero de exactamente N de ancho, use std::intN_t (o std::uintN_t si necesita una versión unsigned ). Pensar en int como un entero de 32 bits y long long como un entero de 64 bits es simplemente incorrecto. Puede suceder que sea así en sus plataformas actuales, pero esto se basa en un comportamiento definido por la implementación.

El uso de tipos enteros de ancho fijo también es útil para interoperar con otras tecnologías. Por ejemplo, si algunas partes de su aplicación están escritas en Java y otras en C ++, probablemente querrá hacer coincidir los tipos de enteros para obtener resultados consistentes. (Tenga en cuenta que el desbordamiento en Java tiene una semántica bien definida, mientras que signed desbordamiento en C ++ es un comportamiento indefinido, por lo que la consistencia es un objetivo alto). También serán invaluables al intercambiar datos entre diferentes hosts informáticos.

Si no necesita exactamente N bits, pero solo un tipo que sea lo suficientemente ancho , considere usar std::int_leastN_t (optimizado para el espacio) o std::int_fastN_t (optimizado para la velocidad). Nuevamente, ambas familias también tienen unsigned homólogas.

Entonces, ¿cuándo usar los tipos incorporados? Bueno, dado que el estándar no especifica su ancho de manera precisa, utilícelos cuando no se preocupe por el ancho de bits real pero sobre otras características.

Un char es el entero más pequeño al que puede acceder el hardware. El lenguaje realmente te obliga a usarlo para crear alias de memoria arbitraria. También es el único tipo viable para representar cadenas de caracteres (estrechas).

Un int generalmente será el tipo más rápido que la máquina puede manejar. Será lo suficientemente ancho como para que se pueda cargar y almacenar con una sola instrucción (sin tener que enmascarar o desplazar los bits) y lo suficientemente estrecho para que pueda operarse con las instrucciones de hardware más eficientes. Por lo tanto, int es una opción perfecta para pasar datos y realizar operaciones aritméticas cuando el desbordamiento no es una preocupación. Por ejemplo, el tipo de enumeración subyacente predeterminado es int . No lo cambie a un entero de 32 bits solo porque puede hacerlo. Además, si tiene un valor que solo puede ser –1, 0 y 1, un int es una opción perfecta, a menos que vaya a almacenar grandes matrices de ellos, en cuyo caso es posible que desee utilizar datos más compactos. escriba al costo de tener que pagar un precio más alto por acceder a elementos individuales. El almacenamiento en caché más eficiente probablemente pagará por esto. Muchas funciones del sistema operativo también se definen en términos de int . Sería tonto convertir sus argumentos y resultados de un lado a otro. Todo lo que podría hacer es introducir errores de desbordamiento.

long generalmente será el tipo más amplio que se puede manejar con instrucciones de una sola máquina. Esto hace que especialmente unsigned long sea muy atractivo para tratar con datos en bruto y todo tipo de cosas de manipulación de bits. Por ejemplo, esperaría ver unsigned long en la implementación de un bit-vector. Si el código se escribe con cuidado, no importa qué tan ancho sea el tipo (porque el código se adaptará automáticamente). En las plataformas en las que la palabra de máquina nativa es de 32 bits, lo más deseable es que la matriz de respaldo del vector de bits sea una matriz de unsigned de enteros de 32 bits, ya que sería tonto utilizar un tipo de 64 bits que deba cargarse a través de instrucciones caras, solo para cambiar y enmascarar los bits innecesarios de nuevo de todos modos. Por otro lado, si el tamaño de palabra nativo de la plataforma es de 64 bits, quiero una matriz de ese tipo porque significa que las operaciones como "encontrar el primer conjunto" pueden ejecutarse hasta el doble de rápido. Por lo tanto, el "problema" del tipo de datos long que está describiendo, que su tamaño varía de una plataforma a otra, en realidad es una característica que se puede utilizar. Solo se convierte en un problema si piensa en los tipos incorporados como tipos de un cierto ancho de bits, que simplemente no son.

char , int y long son tipos muy útiles como se describe anteriormente. short y long long no son tan útiles porque su semántica es mucho menos clara.

    
respondido por el 5gon12eder 05.05.2016 - 23:44
6

Otra respuesta ya explica los tipos de cstdint y las variaciones menos conocidas en ellos.

Me gustaría agregar a eso:

usar nombres de tipos específicos del dominio

Es decir, no declare que sus parámetros y variables sean uint32_t (¡ciertamente no long !), sino nombres como channel_id_type , room_count_type etc.

sobre bibliotecas

Las bibliotecas de terceros que usan long o lo que sea pueden ser molestas, especialmente si se usan como referencias o punteros a ellas.

Lo mejor es hacer envoltorios.

En general, mi estrategia es hacer un conjunto de funciones similares a cast que se utilizarán. Están sobrecargados para aceptar solo aquellos tipos que coincidan exactamente con los tipos correspondientes, junto con las variaciones de puntero, etc. que necesite. Se definen específicamente para el sistema operativo / compilador / configuración. Esto le permite eliminar advertencias y, sin embargo, asegurarse de que solo se utilicen las conversiones "correctas".

channel_id_type cid_out;
...
SomeLibFoo (same_thing_really<int*>(&cid_out));

En particular, con diferentes tipos primitivos que producen 32 bits, su elección de cómo se define int32_t podría no coincidir con la llamada de la biblioteca (por ejemplo, int vs long en Windows).

La función de tipo fundido documenta el conflicto, proporciona una verificación en tiempo de compilación sobre el resultado que coincide con el parámetro de la función, y elimina cualquier advertencia o error si y solo si el tipo real coincide con el tamaño real involucrado. Es decir, está sobrecargado y definido si paso (en Windows) un int* o un long* y da un error de compilación de lo contrario.

Por lo tanto, si la biblioteca se actualiza o si alguien cambia qué es channel_id_type , esto continuará verificándose.

    
respondido por el JDługosz 07.05.2016 - 03:30

Lea otras preguntas en las etiquetas