Tengo una pregunta muy simple que me desconcierta durante mucho tiempo. Estoy tratando con redes y bases de datos, por lo que una gran cantidad de datos con los que estoy contando son contadores de 32 y 64 bits (sin signo), identificadores de identificación de 32 y 64 bits (tampoco tienen un mapeo significativo para el signo). Prácticamente nunca trato con ningún asunto de palabras reales que pueda expresarse como un número negativo.
Mis compañeros de trabajo y yo usamos rutinariamente tipos sin signo como uint32_t
y uint64_t
para estos asuntos y debido a que sucede con tanta frecuencia, también los usamos para índices de matriz y otros usos enteros comunes.
Al mismo tiempo, varias guías de codificación que estoy leyendo (por ejemplo, Google) desalientan el uso de tipos enteros sin signo, y que yo sepa, ni Java ni Scala tienen tipos enteros sin signo.
Por lo tanto, no pude averiguar qué es lo correcto: usar valores con signo en nuestro entorno sería muy inconveniente, al mismo tiempo que codificaría las guías para insistir en hacer exactamente esto.
fuente
Respuestas:
Hay dos escuelas de pensamiento sobre esto, y ninguna de las dos estará de acuerdo.
El primero argumenta que hay algunos conceptos que son inherentemente sin signo, como los índices de matriz. No tiene sentido usar números con signo para aquellos, ya que puede conducir a errores. También puede imponer límites innecesarios a las cosas: una matriz que utiliza índices de 32 bits con signo solo puede acceder a 2 mil millones de entradas, mientras que cambiar a números de 32 bits sin signo permite 4 mil millones de entradas.
El segundo argumenta que en cualquier programa que use números sin signo, tarde o temprano terminarás haciendo aritmética mixta con signo y sin signo. Esto puede dar resultados extraños e inesperados: convertir un gran valor sin signo en firmado da un número negativo y, por el contrario, emitir un número negativo en sin signo da un número positivo grande. Esto puede ser una gran fuente de errores.
fuente
int
es más corto de escribir :)int
es más que suficiente para los índices de matriz el 99,99% de las veces. Los problemas aritméticos con signo sin signo son mucho más comunes y, por lo tanto, tienen prioridad en términos de qué evitar. Sí, los compiladores le advierten sobre esto, pero ¿cuántas advertencias recibe al compilar cualquier proyecto considerable? Ignorar las advertencias es peligroso y una mala práctica, pero en el mundo real ...size_t
, a menos que haya un caso especial Buena razón de lo contrario.En primer lugar, la directriz de codificación de Google C ++ no es muy buena para seguir: evita cosas como excepciones, impulso, etc., que son elementos básicos de C ++ moderno. En segundo lugar, el hecho de que una determinada directriz funcione para la empresa X no significa que sea la adecuada para usted. Seguiría usando tipos sin signo, ya que los necesita.
Una regla general decente para C ++ es: preferir a
int
menos que tenga una buena razón para usar otra cosa.fuente
return false
si ese invariante no está establecido. Por lo tanto, puede separar cosas y usar funciones de inicio para sus objetos, o puede lanzar unstd::runtime_error
, dejar que se produzca el desbobinado de la pila y dejar que todos sus objetos RAII se limpien automáticamente y usted, el desarrollador, puede manejar la excepción donde sea conveniente para que lo hagasnullptr
? devolver un objeto "predeterminado" (lo que sea que eso signifique)? No resolvió nada: acaba de ocultar el problema debajo de una alfombra y espera que nadie se entere.signal(6)
? Si usa una excepción, el 50% de los desarrolladores que saben cómo tratar con ellos pueden escribir un buen código, y el resto puede ser llevado por sus pares.Las otras respuestas carecen de ejemplos del mundo real, por lo que agregaré uno. Una de las razones por las que (personalmente) trato de evitar los tipos sin firmar.
Considere usar size_t estándar como índice de matriz:
Ok, perfectamente normal. Luego, considere que decidimos cambiar la dirección del bucle por alguna razón:
Y ahora no funciona. Si lo usáramos
int
como iterador, no habría problema. He visto ese error dos veces en los últimos dos años. Una vez sucedió en producción y fue difícil de depurar.Otra razón para mí son las advertencias molestas, que te hacen escribir algo así cada vez :
Estas son cosas menores, pero suman. Siento que el código es más limpio si solo se usan enteros con signo en todas partes.
Editar: Claro, los ejemplos parecen tontos, pero vi a personas cometer este error. Si hay una manera tan fácil de evitarlo, ¿por qué no usarlo?
Cuando compilo el siguiente código con VS2015 o GCC, no veo advertencias con la configuración de advertencia predeterminada (incluso con -Wall para GCC). Debe solicitar -Wextra para recibir una advertencia sobre esto en GCC. Esta es una de las razones por las que siempre debe compilar con Wall y Wextra (y usar un analizador estático), pero en muchos proyectos de la vida real la gente no hace eso.
fuente
for (size_t i = n - 1; i < n; --i)
para que funcione correctamente.size_t
en reversa, hay una guía de codificación al estilo defor (size_t revind = 0u; revind < n; ++revind) { size_t ind = n - 1u - revind; func(ind); }
int
? :)int
sea lo suficientemente grande como para contener todos los valores válidos desize_t
. En particular,int
puede permitir números solo hasta 2 ^ 15-1, y comúnmente lo hace en sistemas que tienen límites de asignación de memoria de 2 ^ 16 (o en algunos casos incluso más).long
puede ser una apuesta más segura, aunque todavía no se garantiza que funcione. Solosize_t
se garantiza que funcionará en todas las plataformas y en todos los casos.El problema aquí es que usted escribió el ciclo de una manera no inteligente que conduce a un comportamiento erróneo. La construcción del bucle es como si los principiantes lo aprendieran para los tipos con signo (que está bien y es correcto) pero simplemente no se ajusta a los valores sin signo. Pero esto no puede servir como contraargumento contra el uso de tipos sin signo, la tarea aquí es simplemente acertar. Y esto se puede solucionar fácilmente para que funcione de manera confiable para tipos sin signo de la siguiente manera:
Este cambio simplemente revierte la secuencia de la operación de comparación y decremento y es, en mi opinión, la forma más efectiva, tranquila, limpia y corta para manejar contadores sin firmar en bucles hacia atrás. Harías lo mismo (intuitivamente) cuando uses un ciclo while:
No puede ocurrir un desbordamiento, el caso de un contenedor vacío está cubierto implícitamente, como en la variante bien conocida para el bucle de contador firmado, y el cuerpo del bucle puede permanecer inalterado en comparación con un contador firmado o un bucle de avance. Solo tiene que acostumbrarse a la primera construcción de bucle de aspecto algo extraño. Pero después de haber visto eso una docena de veces, ya no hay nada ininteligible.
Tendría suerte si los cursos para principiantes no solo mostraran el bucle correcto para los tipos firmados sino también para los no firmados. Esto evitaría un par de errores que, en mi humilde opinión, se debe culpar a los desarrolladores involuntarios en lugar de culpar al tipo sin firmar.
HTH
fuente
Los enteros sin signo están ahí por una razón.
Considere, por ejemplo, la entrega de datos como bytes individuales, por ejemplo, en un paquete de red o un búfer de archivo. Ocasionalmente puede encontrar bestias como números enteros de 24 bits. Fácilmente desplazado de tres enteros sin signo de 8 bits, no es tan fácil con enteros con signo de 8 bits.
O piense en algoritmos que usan tablas de búsqueda de caracteres. Si un carácter es un entero sin signo de 8 bits, puede indexar una tabla de búsqueda por un valor de carácter. Sin embargo, ¿qué haces si el lenguaje de programación no admite enteros sin signo? Tendría índices negativos para una matriz. Bueno, supongo que podrías usar algo como
charval + 128
eso, pero eso es feo.Muchos formatos de archivo, de hecho, usan enteros sin signo y si el lenguaje de programación de la aplicación no admite enteros sin signo, eso podría ser un problema.
Luego considere los números de secuencia TCP. Si escribe cualquier código de procesamiento TCP, definitivamente querrá usar enteros sin signo.
A veces, la eficiencia es tan importante que realmente necesitas ese número extra de enteros sin signo. Considere, por ejemplo, los dispositivos IoT que se envían en millones. Muchos recursos de programación pueden justificarse para gastarse en micro optimizaciones.
Yo diría que la compilación con advertencias adecuadas puede superar la justificación para evitar el uso de tipos enteros sin signo (aritmética de signos mixtos, comparaciones de signos mixtos). Dichas advertencias generalmente no están habilitadas de forma predeterminada, pero vea, por ejemplo,
-Wextra
o por separado-Wsign-compare
(habilitado automáticamente en C por-Wextra
, aunque no creo que esté habilitado automáticamente en C ++) y-Wsign-conversion
.Sin embargo, en caso de duda, utilice un tipo con signo. Muchas veces, es una elección que funciona bien. ¡Y habilite esas advertencias del compilador!
fuente
Hay muchos casos en los que los enteros en realidad no representan números, pero, por ejemplo, una máscara de bits, una identificación, etc. Básicamente, los casos en que agregar 1 a un entero no tienen ningún resultado significativo. En esos casos, use sin firmar.
Hay muchos casos en los que haces aritmética con enteros. En estos casos, use enteros con signo, para evitar el mal comportamiento alrededor de cero. Vea muchos ejemplos con bucles, donde ejecutar un bucle a cero usa un código muy poco intuitivo o se rompe debido al uso de números sin signo. Existe el argumento "pero los índices nunca son negativos", claro, pero las diferencias de índices, por ejemplo, son negativas.
En el caso muy raro de que los índices excedan 2 ^ 31 pero no 2 ^ 32, no usa enteros sin signo, usa enteros de 64 bits.
Finalmente, una buena trampa: en un bucle "for (i = 0; i <n; ++ i) a [i] ..." si no estoy firmado 32 bits y la memoria excede las direcciones de 32 bits, el compilador no puede optimizar el acceso a a [i] incrementando un puntero, porque en i = 2 ^ 32 - 1 me envuelve. Incluso cuando n nunca crece tanto. El uso de enteros con signo evita esto.
fuente
Finalmente, encontré una muy buena respuesta aquí: "Libro de cocina de programación segura" de J.Viega y M.Messier ( http://shop.oreilly.com/product/9780596003944.do )
Problemas de seguridad con enteros firmados:
Existen problemas con las conversiones con signo <-> sin signo, por lo que no es aconsejable utilizar mix.
fuente