studio - superponer graficas en r
¿Cuándo debería usar "int" versus tipos más específicos de signos o tamaños específicos? (5)
Aquí hay algunas cosas que hago. No estoy seguro de que sean para todos, pero funcionan para mí.
-
Nunca use
int
ounsigned int
directamente. Siempre parece haber un tipo con un nombre más apropiado para el trabajo. -
Si una variable debe tener un ancho específico (por ejemplo, para un registro de hardware o para que coincida con un protocolo), use un tipo específico de ancho (por ejemplo,
uint32_t
). -
Para los iteradores de matriz, donde quiero acceder a los elementos de matriz de 0 a n, esto también debe estar sin signo (no hay razón para acceder a ningún índice menor que 0) y utilizo uno de los tipos rápidos (por ejemplo,
uint_fast16_t
), seleccionando el tipo basado en tamaño mínimo requerido para acceder a todos los elementos de la matriz. Por ejemplo, si tengo un ciclofor
que iterará a través de un máximo de 24 elementos,uint_fast8_t
y dejaré que el compilador (o stdint.h, dependiendo de lo pedante que queramos obtener) decida cuál es el tipo más rápido para esa operación. - Utilice siempre variables sin signo a menos que haya una razón específica para que se firmen.
- Si sus variables sin signo y las variables con signo necesitan jugar juntas, use modelos explícitos y tenga en cuenta las consecuencias. (Afortunadamente, esto se minimizará si evita usar variables con signo, excepto donde sea absolutamente necesario).
Si no está de acuerdo con alguno de ellos o tiene alternativas recomendadas, ¡hágamelo saber en los comentarios! Esa es la vida de un desarrollador de software ... seguimos aprendiendo o nos volvemos irrelevantes.
Tengo una pequeña VM para un lenguaje de programación implementado en C. Admite la compilación en arquitecturas de 32 bits y 64 bits, así como en C y C ++.
Estoy tratando de hacer una compilación limpia con tantas advertencias habilitadas como sea posible.
Cuando
CLANG_WARN_IMPLICIT_SIGN_CONVERSION
, recibo una cascada de nuevas advertencias.
Me gustaría tener una buena estrategia para cuándo usar
int
versus tipos explícitamente sin signo y / o de tamaño explícito.
Hasta ahora, tengo problemas para decidir cuál debería ser esa estrategia.
Ciertamente es cierto que mezclarlos, usando principalmente
int
para cosas como variables y parámetros locales y usando tipos más estrechos para campos en estructuras, causa muchos problemas de conversión implícita.
Me gusta usar tipos de tamaños más específicos para los campos de estructura porque me gusta la idea de controlar explícitamente el uso de memoria para los objetos en el montón.
Además, para las tablas hash, confío en el desbordamiento sin signo cuando se realiza el hashing, por lo que es bueno si el tamaño de la tabla hash se almacena como
uint32_t
.
Pero, si trato de usar tipos más específicos en todas partes , me encuentro en un laberinto de moldes retorcidos por todas partes.
¿Qué hacen otros proyectos de C?
El uso de
int
todas partes puede parecer tentador, ya que minimiza la necesidad de lanzar, pero hay varias dificultades potenciales que debe tener en cuenta:
-
Un
int
puede ser más corto de lo que esperas. Aunque, en la mayoría de las plataformas de escritorio, unint
es típicamente de 32 bits, el estándar C solo garantiza una longitud mínima de 16 bits . ¿Podría su código necesitar números mayores que 2 16 −1 = 32,767, incluso para valores temporales? Si es así, no use unint
. (Es posible que desee utilizar unlong
; se garantiza que unlong
sea de al menos 32 bits). -
Incluso un
long
no siempre es lo suficientemente largo. En particular, no hay garantía de que la longitud de una matriz (o de una cadena, que es una matriz de caracteres) se ajuste en unlong
. Usesize_t
(optrdiff_t
, si necesita una diferencia con signo) para esos.En particular, un
size_t
se define para ser lo suficientemente grande como para contener cualquier índice de matriz válido , mientras que unint
o incluso unlong
podría no serlo. Por lo tanto, por ejemplo, al iterar sobre una matriz, su contador de bucle (y sus valores iniciales / finales) generalmente debe ser unsize_t
, al menos a menos que sepa con certeza que la matriz es lo suficientemente corta como para que funcione un tipo más pequeño. (¡Pero tenga cuidado al iterar hacia atrás:size_t
no está firmado,for(size_t i = n-1; i >= 0; i--)
lo quefor(size_t i = n-1; i >= 0; i--)
es un bucle infinito! Usari != SIZE_MAX
oi != (size_t) -1
sin embargo, debería funcionar; o usar un bucledo
/ while, ¡pero cuidado con el cason == 0
!) -
Se firma un
int
. En particular, esto significa que el desbordamientoint
es un comportamiento indefinido. Si alguna vez existe el riesgo de que sus valores puedan desbordarse legítimamente, no use unint
; use ununsigned int
(o ununsigned long
, ouint NN _t
) en su lugar. -
A veces, solo necesitas una longitud de bit fija. Si está interactuando con un ABI, o leyendo / escribiendo un formato de archivo, eso requiere enteros de una longitud específica, entonces esa es la longitud que necesita usar. (Por supuesto, en tales situaciones, es posible que también deba preocuparse por cosas como la endianness, por lo que a veces puede tener que recurrir al empaquetado manual de datos byte a byte de todos modos).
Dicho todo esto, también hay razones para evitar el uso de los tipos de longitud fija todo el tiempo: no solo es difícil de escribir
int32_t
todo el tiempo, sino que obligar al compilador a usar siempre números enteros de 32 bits no siempre es óptimo, especialmente en plataformas donde el tamaño
int
nativo podría ser, digamos, 64 bits.
Podría
usar, por ejemplo, C99
int_fast32_t
, pero eso es aún más incómodo de escribir.
Por lo tanto, aquí están mis sugerencias personales para la máxima seguridad y portabilidad:
-
Defina sus propios tipos enteros para uso casual en un archivo de encabezado común, algo como esto:
#include <limits.h> typedef int i16; typedef unsigned int u16; #if UINT_MAX >= 4294967295U typedef int i32; typedef unsigned int u32; #else typedef long i32; typedef unsigned long i32; #endif
Use estos tipos para cualquier cosa donde el tamaño exacto del tipo no importe, siempre que sean lo suficientemente grandes. Los nombres de tipo que he sugerido son breves y autodocumentados, por lo que deberían ser fáciles de usar en los moldes donde sea necesario y minimizar el riesgo de errores debido al uso de un tipo demasiado estrecho.
Convenientemente, se garantiza que los tipos
u32
yu16
definidos anteriormente son al menos tan anchos comounsigned int
y, por lo tanto, se pueden usar de manera segura sin tener que preocuparse de que sean promovidos aint
y causen un comportamiento de desbordamiento indefinido. -
Use
size_t
para todos los tamaños de matriz e indexación, pero tenga cuidado al convertir entre él y cualquier otro tipo de entero. Opcionalmente, si no le gusta escribir tantos guiones bajos, escriba también un alias más conveniente. -
Para los cálculos que suponen un desbordamiento en un número específico de bits, use
uint NN _t
, o simplemente useu16
/u32
como se definió anteriormente y una máscara de bits explícita con&
. Si elige usaruint NN _t
, asegúrese de protegerse contra una promoción inesperada aint
; Una forma de hacerlo es con una macro como:#define u(x) (0U + (x))
que debería permitirte escribir de forma segura, por ejemplo:
uint32_t a = foo(), b = bar(); uint32_t c = u(a) * u(b); /* this is always unsigned multiply */
-
Para ABI externos que requieren una longitud entera específica, defina nuevamente un tipo específico, por ejemplo:
typedef int32_t fooint32; /* foo ABI needs 32-bit ints */
Una vez más, este nombre de tipo es autodocumentado, tanto con respecto a su tamaño como a su propósito.
Si la ABI realmente puede requerir, digamos, entradas de 16 o 64 bits, dependiendo de la plataforma y / o las opciones de tiempo de compilación, puede cambiar la definición de tipo para que coincida (y cambiar el nombre del tipo a simple), pero luego Realmente debes tener cuidado cada vez que lanzas algo hacia o desde ese tipo, ya que puede desbordarse inesperadamente.
-
Si su código tiene sus propias estructuras o formatos de archivo que requieren longitudes de bits específicas, considere definir tipos personalizados para ellos también, exactamente como si fuera un ABI externo. O simplemente puede usar
uint NN _t
enuint NN _t
lugar, pero perderá un poco de autodocumentación de esa manera. -
Para todos estos tipos, no olvides definir también las constantes
_MIN
y_MAX
correspondientes para verificar fácilmente los límites. Esto puede parecer mucho trabajo, pero en realidad son solo un par de líneas en un solo archivo de encabezado.
Finalmente, recuerde tener cuidado con las matemáticas de enteros, especialmente los desbordamientos.
Por ejemplo, tenga en cuenta que la diferencia de dos enteros con signo de
n
bits puede no encajar en un int
n de
bits.
(Encajará en un int
sin signo de
n
bits, si sabe que no es negativo; ¡pero recuerde que necesita convertir las entradas a un tipo sin signo
antes de
tomar su diferencia para evitar un comportamiento indefinido!) Del mismo modo, para encontrar el promedio de dos enteros (por ejemplo, para una búsqueda binaria), no use
avg = (lo + hi) / 2
, sino más bien,
avg = lo + (hi + 0U - lo) / 2
;
el primero se romperá si la suma se desborda.
Mantenga grandes números que se utilizan para acceder a los miembros de las matrices, o controle las memorias intermedias como
size_t
.
Para ver un ejemplo de un proyecto que utiliza
size_t
, consulte
GNU''s dd.c, línea 155
.
Pareces saber lo que estás haciendo, a juzgar por el código fuente vinculado, que eché un vistazo.
Lo dijiste tú mismo: usar tipos "específicos" te hace tener más lanzamientos.
Esa no es una ruta óptima para tomar de todos modos.
Use
int
tanto como pueda, para cosas que no requieren un tipo más especializado.
La belleza de
int
es que se abstrae sobre los tipos de los que hablas.
Es óptimo en todos los casos en los que no necesita exponer la construcción a un sistema que no tenga conocimiento de
int
.
Es su propia herramienta para abstraer la plataforma de sus programas.
También puede brindarle velocidad, tamaño y ventaja de alineación, dependiendo.
En todos los demás casos, por ejemplo, donde desea permanecer deliberadamente cerca de las especificaciones de la máquina,
int
puede y, a veces, debe abandonarse.
Los casos típicos incluyen protocolos de red en los que los datos van por cable e instalaciones de interoperabilidad: puentes de tipo entre C y otros lenguajes, rutinas de ensamblaje de kernel que acceden a estructuras C.
Pero no olvide que a veces desearía usar
int
incluso en estos casos, ya que sigue el tamaño de palabra "nativo" o preferido de las plataformas, y es posible que desee confiar en esa misma propiedad.
Con tipos de plataforma como
uint32_t
, un kernel puede querer usarlos (aunque puede que no sea necesario) en sus estructuras de datos si se accede a ellos desde C y ensamblador, ya que este último generalmente no sabe qué se supone que es
int
.
En resumen, use
int
tanto como sea posible y recurra a pasar de tipos más abstractos a tipos de "máquina" (bytes / octetos, palabras, etc.) en cualquier situación que lo requiera.
En cuanto a
size_t
y otros tipos "sugestivos de uso", siempre que la sintaxis siga la semántica inherente al tipo, digamos, usando
size_t
para
valores
de
tamaño
de todo tipo, no contestaría.
Pero no lo aplicaría liberalmente a nada solo porque está garantizado que es el tipo más grande (independientemente de si es realmente cierto).
Esa es una piedra submarina que no querrás pisar más tarde.
El código tiene que explicarse por sí mismo en la medida de lo posible, diría: tener un
size_t
donde no se espera ninguno naturalmente, levantaría las cejas, por una buena razón.
Use
size_t
para tamaños.
Use
offset_t
para desplazamientos.
Use
[u]intN_t
para octetos, palabras y esas cosas.
Y así.
Se trata de aplicar la semántica inherente a un tipo de C en particular, a su código fuente, y sobre las implicaciones en el programa en ejecución.
Además, como otros han ilustrado, no
typedef
, ya que le da el poder de definir eficientemente sus propios tipos, una instalación de abstracción que personalmente valoro.
Un buen código fuente del programa puede que ni siquiera exponga un solo
int
, sin embargo, confía en
int
alias detrás de una multitud de tipos definidos con un propósito.
No voy a cubrir
typedef
aquí, espero que las otras respuestas lo hagan.
Siempre.
A menos que tenga razones específicas para usar un tipo más específico, incluso que se encuentre en una plataforma de 16 bits y necesite números enteros mayores que 32767, o que necesite asegurar el orden de bytes y la señalización adecuados para el intercambio de datos a través de una red o en un archivo ( y a menos que tenga recursos limitados, considere transferir datos en "texto plano", es decir, ASCII o UTF8 si lo prefiere).
Mi experiencia ha demostrado que "solo usar ''int''" es una buena máxima para vivir y hace posible que el código funcione, se mantenga fácilmente y se corrija rápidamente cada vez. Pero su situación específica puede ser diferente, así que tome este consejo con un poco de escrutinio bien merecido.