¿Es más probable que el uso de un int sin firmar en lugar de firmado cause errores? ¿Por qué?

81

En la Guía de estilo de Google C ++ , sobre el tema "Enteros sin firmar", se sugiere que

Debido a un accidente histórico, el estándar C ++ también usa números enteros sin firmar para representar el tamaño de los contenedores; muchos miembros del cuerpo de estándares creen que esto es un error, pero es efectivamente imposible de solucionar en este momento. El hecho de que la aritmética sin firmar no modele el comportamiento de un entero simple, sino que esté definida por el estándar para modelar la aritmética modular (envolviendo el overflow / underflow), significa que el compilador no puede diagnosticar una clase significativa de errores.

¿Qué hay de malo en la aritmética modular? ¿No es ese el comportamiento esperado de un int sin firmar?

¿A qué tipo de errores (una clase importante) se refiere la guía? ¿Errores desbordados?

No utilice un tipo sin firmar simplemente para afirmar que una variable no es negativa.

Una razón por la que puedo pensar en usar un int firmado sobre un int no firmado es que si se desborda (a negativo), es más fácil de detectar.

usuario7586189
fuente
4
Trate de hacer unsigned int x = 0; --x;y ver qué se xconvierte. Sin controles de límite, el tamaño podría obtener repentinamente un valor inesperado que podría conducir fácilmente a UB.
Algún tipo programador
33
Al menos el desbordamiento sin firmar tiene un comportamiento bien definido y produce los resultados esperados.
user7860670
35
En una nota no relacionada (con su pregunta pero no con las guías de estilo de Google), si busca un poco, encontrará algunas críticas (a veces con razón) de las guías de estilo de Google. No los tome como un evangelio.
Un tipo programador
18
Por otro lado, el intdesbordamiento y el subdesbordamiento son UB. Es menos probable que experimente una situación en la que un inttrataría de expresar un valor que no puede que una situación que disminuya un valor unsigned intpor debajo de cero, pero el tipo de personas que se sorprenderían con el comportamiento de la unsigned intaritmética es el tipo de personas que también podrían escriba el código que causaría el intdesbordamiento relacionado con UB, como usar a < a + 1para verificar el desbordamiento.
François Andrieux
12
Si un entero sin signo se desborda, está bien definido. Si el entero con signo se desborda, es un comportamiento indefinido. Prefiero un comportamiento bien definido, pero si su código no puede manejar valores desbordados, está perdido con ambos. La diferencia es: para firmado ya está perdido para la operación de desbordamiento, para no firmado en el siguiente código. El único punto en el que estoy de acuerdo es que si necesita valores negativos, un tipo entero sin signo es la elección incorrecta, obviamente.
demasiado honesto para este sitio

Respuestas:

70

Algunas de las respuestas aquí mencionar las reglas de la promoción sorprendentes entre los valores con y sin signo, pero que parece más como un problema relacionado con la mezcla de los valores con y sin signo, y no necesariamente explica por qué firmados serían preferibles variables a lo largo sin signo exterior de escenarios de mezcla.

En mi experiencia, fuera de las comparaciones mixtas y las reglas de promoción, hay dos razones principales por las que los valores sin firmar son imanes de errores de la siguiente manera.

Los valores sin signo tienen una discontinuidad en cero, el valor más común en programación.

Tanto los enteros sin signo como con signo tienen discontinuidades en sus valores mínimo y máximo, donde se envuelven (sin signo) o causan un comportamiento indefinido (con signo). Porque unsignedestos puntos están en cero y UINT_MAX. Porque intestán en INT_MINy INT_MAX. Los valores típicos de INT_MINy INT_MAXen el sistema con intvalores de 4 bytes son -2^31y 2^31-1, y en tal sistema UINT_MAXes típicamente 2^32-1.

El problema principal que induce errores con unsignedeso no se aplica intes que tiene una discontinuidad en cero . Cero, por supuesto, es un valor muy común en los programas, junto con otros valores pequeños como 1,2,3. Es común sumar y restar valores pequeños, especialmente 1, en varias construcciones, y si restas algo de un unsignedvalor y resulta ser cero, obtienes un valor positivo masivo y un error casi seguro.

Considere que el código itera sobre todos los valores en un vector por índice, excepto el último 0.5 :

for (size_t i = 0; i < v.size() - 1; i++) { // do something }

Esto funciona bien hasta que un día pasa en un vector vacío. En lugar de hacer cero iteraciones, obtienes v.size() - 1 == a giant number1 y harás 4 mil millones de iteraciones y casi tendrás una vulnerabilidad de desbordamiento de búfer.

Tienes que escribirlo así:

for (size_t i = 0; i + 1 < v.size(); i++) { // do something }

Por lo tanto, se puede "arreglar" en este caso, pero solo si se piensa detenidamente en la naturaleza sin firmar de size_t. A veces no puede aplicar la corrección anterior porque, en lugar de una constante, tiene un desplazamiento variable que desea aplicar, que puede ser positivo o negativo: por lo que el "lado" de la comparación en el que debe colocarlo depende del signo - ahora el código se vuelve realmente complicado.

Existe un problema similar con el código que intenta iterar hasta cero, inclusive. Algo como while (index-- > 0)funciona bien, pero el aparentemente equivalente while (--index >= 0)nunca terminará por un valor sin firmar. Su compilador puede advertirle cuando el lado derecho es literal cero, pero ciertamente no si es un valor determinado en tiempo de ejecución.

Contrapunto

Algunos podrían argumentar que los valores con signo también tienen dos discontinuidades, entonces, ¿por qué elegir sin firmar? La diferencia es que ambas discontinuidades están muy (como máximo) lejos de cero. Realmente considero que esto es un problema separado de "desbordamiento", tanto los valores firmados como los no firmados pueden desbordarse en valores muy grandes. En muchos casos, el desbordamiento es imposible debido a las limitaciones del posible rango de valores, y el desbordamiento de muchos valores de 64 bits puede ser físicamente imposible). Incluso si es posible, la posibilidad de un error relacionado con el desbordamiento suele ser minúscula en comparación con un error "en cero", y el desbordamiento también se produce para los valores sin firmar . So unsigned combina lo peor de ambos mundos: desbordamiento potencial con valores de magnitud muy grandes y una discontinuidad en cero. Firmado solo tiene el primero.

Muchos dirán que "pierdes un poco" con unsigned. Esto a menudo es cierto, pero no siempre (si necesita representar diferencias entre valores sin firmar, perderá ese bit de todos modos: muchas cosas de 32 bits están limitadas a 2 GiB de todos modos, o tendrá un área gris extraña donde digamos un archivo puede tener 4 GiB, pero no puede usar ciertas API en la segunda mitad de 2 GiB).

Incluso en los casos en los que unsigned te compra un poco: no te compra mucho: si tuvieras que soportar más de 2 mil millones de "cosas", probablemente pronto tendrás que soportar más de 4 mil millones.

Lógicamente, los valores sin signo son un subconjunto de valores con signo

Matemáticamente, los valores sin signo (enteros no negativos) son un subconjunto de enteros con signo (simplemente llamados _ enteros). 2 . Sin embargo, los valores con signo emergen naturalmente de las operaciones únicamente en valores sin signo , como la resta. Podríamos decir que los valores sin firmar no se cierran mediante sustracción. No ocurre lo mismo con los valores con signo.

¿Quiere encontrar el "delta" entre dos índices sin firmar en un archivo? Bueno, será mejor que hagas la resta en el orden correcto, o de lo contrario obtendrás la respuesta incorrecta. Por supuesto, a menudo necesita una verificación de tiempo de ejecución para determinar el orden correcto. Al tratar con valores sin signo como números, a menudo encontrará que los valores con signo (lógicamente) siguen apareciendo de todos modos, por lo que también puede comenzar con firmado.

Contrapunto

Como se menciona en la nota al pie (2) anterior, los valores con signo en C ++ no son en realidad un subconjunto de valores sin signo del mismo tamaño, por lo que los valores sin signo pueden representar el mismo número de resultados que los valores con signo.

Es cierto, pero el rango es menos útil. Considere la resta y los números sin signo con un rango de 0 a 2N, y los números con signo con un rango de -N a N. Las restas arbitrarias dan como resultado resultados en el rango de -2N a 2N en ambos casos, y cualquier tipo de entero solo puede representar la mitad. Bueno, resulta que la región centrada alrededor de cero de -N a N suele ser mucho más útil (contiene más resultados reales en el código del mundo real) que el rango de 0 a 2N. Considere cualquier distribución típica que no sea uniforme (log, zipfian, normal, lo que sea) y considere restar valores seleccionados al azar de esa distribución: muchos más valores terminan en [-N, N] que [0, 2N] (de hecho, la distribución resultante siempre está centrado en cero).

64 bits cierra la puerta a muchas de las razones para usar valores con signo como números

Creo que los argumentos anteriormente ya fueron convincentes para los valores de 32 bits, pero los casos de desbordamiento, que afectan tanto con y sin signo en diferentes umbrales, no se produce para valores de 32 bits, ya que "2 mil millones" es un número que puede superado por muchos cantidades abstractas y físicas (miles de millones de dólares, miles de millones de nanosegundos, matrices con miles de millones de elementos). Entonces, si alguien está lo suficientemente convencido por la duplicación del rango positivo para valores sin firmar, puede argumentar que el desbordamiento sí importa y favorece ligeramente a unsigned.

Fuera de los dominios especializados, los valores de 64 bits eliminan en gran medida esta preocupación. Los valores de 64 bits firmados tienen un rango superior de 9.223.372.036.854.775.807, más de nueve trillones . Eso es muchos nanosegundos (unos 292 años) y mucho dinero. También es una matriz más grande de lo que es probable que cualquier computadora tenga RAM en un espacio de direcciones coherente durante mucho tiempo. Entonces, ¿quizás 9 trillones es suficiente para todos (por ahora)?

Cuando usar valores sin firmar

Tenga en cuenta que la guía de estilo no prohíbe ni desalienta necesariamente el uso de números sin firmar. Concluye con:

No utilice un tipo sin firmar simplemente para afirmar que una variable no es negativa.

De hecho, existen buenos usos para las variables sin firmar:

  • Cuando desee tratar una cantidad de N bits no como un número entero, sino simplemente como una "bolsa de bits". Por ejemplo, como una máscara de bits o un mapa de bits, o N valores booleanos o lo que sea. Este uso a menudo va de la mano con los tipos de ancho fijo como uint32_ty uint64_tya que a menudo desea saber el tamaño exacto de la variable. Un indicio de que una variable en particular merece este tratamiento es que sólo se opera en él con los bit a bit operadores como ~, |, &, ^, >>y así sucesivamente, y no con las operaciones aritméticas tales como +, -, *, /etc.

    Unsigned es ideal aquí porque el comportamiento de los operadores bit a bit está bien definido y estandarizado. Los valores con signo tienen varios problemas, como un comportamiento indefinido y no especificado al cambiar, y una representación no especificada.

  • Cuando realmente quieres aritmética modular. A veces, realmente quieres aritmética modular 2 ^ N. En estos casos, el "desbordamiento" es una característica, no un error. Los valores sin signo le brindan lo que desea aquí, ya que están definidos para usar aritmética modular. Los valores firmados no se pueden usar (fácil y eficientemente) en absoluto, ya que tienen una representación no especificada y el desbordamiento no está definido.


0.5 Después de escribir esto, me di cuenta de que es casi idéntico al ejemplo de Jarod , que no había visto, y por una buena razón, ¡es un buen ejemplo!

1 Estamos hablando size_taquí, por lo que generalmente es 2 ^ 32-1 en un sistema de 32 bits o 2 ^ 64-1 en uno de 64 bits.

2 En C ++ este no es exactamente el caso porque los valores sin signo contienen más valores en el extremo superior que el tipo con signo correspondiente, pero existe el problema básico de que la manipulación de valores sin signo puede resultar en valores con signo (lógicamente), pero no hay un problema correspondiente con valores firmados (dado que los valores firmados ya incluyen valores sin firmar).

BeeOnRope
fuente
10
Estoy de acuerdo con todo lo que ha publicado, pero "64 bits debería ser suficiente para todos" parece estar demasiado cerca de "640k debería ser suficiente para todos".
Andrew Henle
6
@Andrew: sí, elegí mis palabras con cuidado :).
BeeOnRope
4
"64 bits cierra la puerta a valores sin firmar" -> En desacuerdo. Algunas tareas de programación de enteros son simples, no se trata de contar y no necesitan valores negativos, pero necesitan anchos de potencia de 2: contraseñas, cifrado, gráficos de bits, beneficio con matemáticas sin firmar. Muchas ideas aquí señalan por qué el código podría usar matemáticas con signos cuando sea posible, pero no llega a hacer inútiles los tipos sin firmar y cerrarles la puerta.
chux - Reincorporar a Monica
2
@Deduplicator: sí, lo dejé fuera porque parece más o menos como un empate. En el lado del mod-2 ^ N sin firmar, al menos tiene un comportamiento definido y no se activarán "optimizaciones" inesperadas. En el lado de UB, cualquier desbordamiento durante la aritmética en no firmado o firmado es probablemente un error en la abrumadora mayoría de los casos (fuera de los pocos que esperan mod aritmética), y los compiladores proporcionan opciones como -ftrapvesa que pueden capturar todos los desbordamientos firmados, pero no todos los desbordamientos sin firmar. El impacto en el rendimiento no es tan malo, por lo que podría ser razonable realizar la compilación -ftrapven algunos escenarios.
BeeOnRope
2
@BeeOnRope That's about the age of the universe measured in nanoseconds.Lo dudo. El universo se trata de lo 13.7*10^9 yearsviejo que es 4.32*10^17 so 4.32*10^26 ns. Para representar 4.32*10^26como int necesitas al menos 90 bits. 9,223,372,036,854,775,807 nssolo sería sobre 292.5 years.
Osiris
36

Como se indicó, la mezcla de unsignedy signedpodría dar lugar a un comportamiento inesperado (incluso si está bien definido).

Supongamos que desea iterar sobre todos los elementos del vector excepto los últimos cinco, podría escribir incorrectamente:

for (int i = 0; i < v.size() - 5; ++i) { foo(v[i]); } // Incorrect
// for (int i = 0; i + 5 < v.size(); ++i) { foo(v[i]); } // Correct

Supongamos v.size() < 5, entonces, que tal como v.size()está unsigned, s.size() - 5sería un número muy grande, y también lo i < v.size() - 5sería truepara un rango de valor más esperado de i. Y UB luego ocurre rápidamente (fuera de acceso una vez i >= v.size())

Si v.size()hubiera devuelto un valor con signo, entonces s.size() - 5habría sido negativo y, en el caso anterior, la condición sería falsa inmediatamente.

Por otro lado, el índice debe estar entre, [0; v.size()[por lo que unsignedtiene sentido. Signed también tiene su propio problema como UB con desbordamiento o comportamiento definido por la implementación para el desplazamiento a la derecha de un número con signo negativo, pero una fuente de error menos frecuente para la iteración.

Jarod42
fuente
2
Si bien yo mismo uso números con signo siempre que puedo, no creo que este ejemplo sea lo suficientemente sólido. Alguien que usa números sin firmar durante mucho tiempo, seguramente conoce este idioma: en lugar de i<size()-X, se debe escribir i+X<size(). Claro, es algo para recordar, pero no es tan difícil acostumbrarse, en mi opinión.
geza
8
Lo que estás diciendo es que básicamente hay que conocer el lenguaje y las reglas de coerción entre tipos. No veo cómo esto cambia si uno usa firmado o no firmado como pregunta la pregunta. No es que recomiendo usar firmado en absoluto si no hay necesidad de valores negativos. Estoy de acuerdo con @geza, solo uso firmado cuando sea necesario. Esto hace que la guía de Google sea cuestionable en el mejor de los casos . Imo es un mal consejo.
demasiado honesto para este sitio
2
@toohonestforthissite El punto es que las reglas son causas misteriosas, silenciosas y principales de errores. El uso de tipos exclusivamente firmados para aritmética lo libera del problema. Por cierto, el uso de tipos sin firmar con el fin de imponer valores positivos es uno de los peores abusos para ellos.
Pasador antes del
2
Afortunadamente, los compiladores e IDE modernos dan advertencias cuando se mezclan números con y sin signo en una expresión.
Alexey B.
5
@PasserBy: Si los llama arcanos, debe agregar las promociones de enteros y el UB para el desbordamiento de tipos firmados arcanos también. Y el operador sizeof muy común devuelve un unsigned de todos modos, por lo que debe conocerlos. Dijo que: si no quieres aprender los detalles del idioma, ¡simplemente no uses C o C ++! Teniendo en cuenta que Google promueve ir, tal vez ese sea exactamente su objetivo. Los días de "no seas malvado" se acabaron hace mucho ...
demasiado honesto para este sitio
20

Uno de los ejemplos más espeluznantes de un error es cuando MEZCLAS valores firmados y no firmados:

#include <iostream>
int main()  {
    auto qualifier = -1 < 1u ? "makes" : "does not make";
    std::cout << "The world " << qualifier << " sense" << std::endl;
}

La salida:

El mundo no tiene sentido

A menos que tenga una aplicación trivial, es inevitable que termine con mezclas peligrosas entre valores firmados y no firmados (lo que resulta en errores de tiempo de ejecución) o si genera advertencias y las comete errores en tiempo de compilación, terminará con una gran cantidad de static_casts en su código. Es por eso que es mejor usar estrictamente enteros con signo para tipos de comparación matemática o lógica. Utilice solo sin firmar para máscaras de bits y tipos que representan bits.

Modelar un tipo para que no esté firmado en función del dominio esperado de los valores de sus números es una mala idea. La mayoría de los números están más cerca de 0 que de 2 mil millones, por lo que con los tipos sin signo, muchos de sus valores están más cerca del límite del rango válido. Para empeorar las cosas, el valor final puede estar en un rango positivo conocido, pero al evaluar expresiones, los valores intermedios pueden subdesbordarse y si se usan en forma intermedia pueden ser valores MUY incorrectos. Finalmente, incluso si se espera que sus valores siempre sean positivos, eso no significa que no interactúen con otras variables que pueden ser negativas, por lo que termina con una situación forzada de mezclar tipos con y sin signo, que es el peor lugar para estar.

Chris Uzdavinis
fuente
8
Modelar un tipo para que no esté firmado en función del dominio esperado de los valores de sus números es una mala idea * si no trata las conversiones implícitas como advertencias y es demasiado perezoso para usar las conversiones de tipos adecuadas. * Modelar sus tipos según su validez esperada values ​​es completamente razonable, pero no en C / C ++ con tipos integrados.
villasv
1
@ user7586189 Es una buena práctica hacer que los datos no válidos sean imposibles de instanciar, por lo que tener variables solo positivas para los tamaños es perfectamente razonable. Pero no puede ajustar con precisión los tipos incorporados de C / C ++ para no permitir de forma predeterminada conversiones incorrectas como la de esta respuesta y la validez termina siendo responsabilidad de otra persona. Si está en un lenguaje con conversiones más estrictas (incluso entre integradas), el modelado de dominio esperado es una muy buena idea.
villasv
1
Tenga en cuenta, no menciona poniendo encima de las advertencias y estableciendo su valor a los errores, pero no todos lo hacen. Sigo en desacuerdo @villasv con su afirmación sobre el modelado de valores. Al elegir unsigned, TAMBIÉN está modelando implícitamente todos los demás valores con los que puede entrar en contacto sin tener mucha previsión de lo que será. Y es casi seguro que se equivoque.
Chris Uzdavinis
1
Modelar con el dominio en mente es algo bueno. El uso de unsigned para modelar el dominio NO lo es. (Firmado o sin firmar debe elegirse según los tipos de uso , no el rango de valores , a menos que sea imposible hacer lo contrario).
Chris Uzdavinis
2
Una vez que su base de código tiene una combinación de valores firmados y sin firmar, cuando muestra advertencias y las promueve como errores, el código termina lleno de static_casts para hacer explícitas las conversiones (porque las matemáticas aún deben hacerse). Incluso cuando es correcto, es propenso a errores, más difícil de trabajar y más difícil de leer.
Chris Uzdavinis
11

¿Por qué es más probable que el uso de un int sin firmar cause errores que el uso de un int firmado?

No es más probable que el uso de un tipo sin firmar cause errores que el uso de un tipo firmado con ciertas clases de tareas.

Utilice la herramienta adecuada para el trabajo.

¿Qué hay de malo en la aritmética modular? ¿No es ese el comportamiento esperado de un int sin firmar?
¿Por qué es más probable que el uso de un int sin firmar cause errores que el uso de un int firmado?

Si la tarea está bien adaptada: no hay nada de malo. No, no más probable.

El algoritmo de seguridad, cifrado y autenticación cuenta con matemática modular sin firmar.

Los algoritmos de compresión / descompresión también, así como varios formatos gráficos, se benefician y tienen menos errores con las matemáticas sin firmar .

Cada vez que se utilizan operadores bit a bit y cambios, las operaciones sin firmar no se confunden con los problemas de extensión de signo de las matemáticas con signo .


Las matemáticas enteras con signo tienen un aspecto intuitivo y se sienten fácilmente entendidas por todos, incluidos los estudiantes de codificación. C / C ++ no se apuntó originalmente ni ahora debería ser un lenguaje de introducción. Para la codificación rápida que emplea redes de seguridad en relación con el desbordamiento, otros lenguajes son más adecuados. Para el código Lean Fast, C asume que los programadores saben lo que están haciendo (tienen experiencia).

Un error de firmado matemáticas hoy en día es el ubicuo de 32 bits intque con tantos problemas es también lo suficientemente amplia como para las tareas comunes sin verificación de rango. Esto conduce a la complacencia contra la que no se codifica el desbordamiento. En cambio, for (int i=0; i < n; i++) int len = strlen(s);se ve como correcto porque nse supone < INT_MAXy las cadenas nunca serán demasiado largas, en lugar de estar protegidas por completo en el primer caso o usar size_t, unsignedo incluso long longen el segundo.

C / C ++ se desarrolló en una era que incluía 16 bits y 32 bits, inty el bit adicional que ofrece un 16 bits sin firmar size_tfue significativo. Se necesitaba la atención en lo que se refiere a desbordarse problemas ya sea into unsigned.

Con aplicaciones de 32 bits (o más amplias) de Google en int/unsignedplataformas que no son de 16 bits , brinda la falta de atención al desbordamiento de +/- intdada su amplia gama. Esto tiene sentido para que dichas aplicaciones fomenten intel cambio unsigned. Sin embargo, las intmatemáticas no están bien protegidas.

Las int/unsignedpreocupaciones estrechas de 16 bits se aplican hoy en día con aplicaciones integradas seleccionadas.

Las pautas de Google se aplican bien al código que escriben hoy. No es una guía definitiva para el amplio rango de código C / C ++.


Una razón por la que puedo pensar en usar un int firmado sobre un int no firmado es que si se desborda (a negativo), es más fácil de detectar.

En C / C ++, el desbordamiento matemático int firmado es un comportamiento indefinido y, por lo tanto, no es más fácil de detectar que el comportamiento definido de las matemáticas sin firmar .


Como bien comentó @Chris Uzdavinis , es mejor evitar mezclar firmado y no firmado por todos (especialmente los principiantes) y codificado cuidadosamente cuando sea necesario.

chux - Restablecer a Monica
fuente
2
Hace un buen punto que inttampoco modela el comportamiento de un entero "real". El comportamiento indefinido en el desbordamiento no es lo que un matemático piensa de los números enteros: no hay posibilidad de "desbordamiento" con un entero abstracto. Pero estas son unidades de almacenamiento de máquinas, no números de matemáticos.
tchrist
1
@tchrist: El comportamiento sin signo en el desbordamiento es cómo pensaría un matemático acerca de un anillo algebraico abstracto de números enteros congruentes mod (type_MAX + 1).
supercat
Si está utilizando gcc, el signed intdesbordamiento es fácil de detectar (con -ftrapv), mientras que el "desbordamiento" sin firmar es difícil de detectar.
anatolyg
5

Tengo algo de experiencia con la guía de estilo de Google, también conocida como la Guía del autoestopista sobre las directivas locas de los malos programadores que entraron en la empresa hace mucho, mucho tiempo. Esta pauta en particular es solo un ejemplo de las docenas de reglas locas en ese libro.

Los errores solo ocurren con tipos sin firmar si intenta hacer aritmética con ellos (vea el ejemplo de Chris Uzdavinis arriba), en otras palabras, si los usa como números. Los tipos sin firmar no están destinados a almacenar cantidades numéricas, están destinados a almacenar recuentos como el tamaño de los contenedores, que nunca pueden ser negativos, y pueden y deben usarse para ese propósito.

La idea de usar tipos aritméticos (como enteros con signo) para almacenar tamaños de contenedores es una idiotez. ¿Usarías un doble para almacenar el tamaño de una lista también? Que haya personas en Google que almacenen tamaños de contenedores usando tipos aritméticos y requieran que otros hagan lo mismo dice algo sobre la empresa. Una cosa que noto acerca de tales dictados es que cuanto más tontos son, más deben ser reglas estrictas de "hazlo o te despiden" porque, de lo contrario, las personas con sentido común ignorarían la regla.

Tyler Durden
fuente
Si bien entiendo su deriva, las declaraciones generales realizadas eliminarían virtualmente las operaciones bit a bit si los unsignedtipos solo pudieran contener recuentos y no se usarían en aritmética. Así que la parte "Insane Directives from Bad Programmers" tiene más sentido.
David C. Rankin
@ DavidC.Rankin Por favor, no lo tome como una declaración "general". Obviamente, existen múltiples usos legítimos para enteros sin signo (como almacenar valores bit a bit).
Tyler Durden
Sí, sí, no lo hice, por eso dije "Entiendo tu deriva".
David C. Rankin
1
Los recuentos a menudo se comparan con cosas que tienen operaciones aritméticas, como índices. La forma en que C maneja las comparaciones que involucran números firmados y sin firmar puede dar lugar a muchas peculiaridades extrañas. Excepto en las situaciones en las que el valor superior de un recuento encajaría en un tipo sin firmar pero no en el correspondiente con signo (común en los días en que intera de 16 bits, pero mucho menos hoy) es mejor tener recuentos que se comporten como números.
supercat
1
"Los errores solo ocurren con tipos sin firmar si intentas hacer aritmética con ellos", lo que sucede todo el tiempo. "La idea de usar tipos aritméticos (como enteros con signo) para almacenar tamaños de contenedores es idiota" - No lo es y el comité de C ++ ahora considera que es un error histórico usar size_t. ¿La razón? Conversiones implícitas.
Átila Neves
1

Usando tipos sin firmar para representar valores no negativos ...

  • es más probable que cause errores relacionados con la promoción de tipos, cuando se usan valores firmados y sin firmar, como otras respuestas demuestran y discuten en profundidad, pero
  • es menos probable que cause errores relacionados con la elección de tipos con dominios capaces de representar valores no permitidos o no admitidos. En algunos lugares, asumirá que el valor está en el dominio y puede tener un comportamiento inesperado y potencialmente peligroso cuando otro valor se cuela de alguna manera.

Las Pautas de codificación de Google ponen énfasis en el primer tipo de consideración. Otros conjuntos de pautas, como las Pautas principales de C ++ , ponen más énfasis en el segundo punto. Por ejemplo, considere la Directriz básica I.12 :

I.12: Declare un puntero que no debe ser nulo como not_null

Razón

Para ayudar a evitar la eliminación de referencias a errores nullptr. Para mejorar el rendimiento evitando comprobaciones redundantes de nullptr.

Ejemplo

int length(const char* p);            // it is not clear whether length(nullptr) is valid
length(nullptr);                      // OK?
int length(not_null<const char*> p);  // better: we can assume that p cannot be nullptr
int length(const char* p);            // we must assume that p can be nullptr

Al indicar la intención en la fuente, los implementadores y las herramientas pueden proporcionar mejores diagnósticos, como encontrar algunas clases de errores a través del análisis estático y realizar optimizaciones, como eliminar ramas y pruebas nulas.

Por supuesto, podría argumentar a favor de un non_negativecontenedor para enteros, que evite ambas categorías de errores, pero eso tendría sus propios problemas ...

einpoklum
fuente
0

La declaración de Google trata sobre el uso de unsigned como tipo de tamaño para contenedores . Por el contrario, la pregunta parece ser más general. Por favor, tenlo en cuenta mientras sigues leyendo.

Dado que la mayoría de las respuestas hasta ahora reaccionaron a la declaración de Google, menos a la pregunta más importante, comenzaré mi respuesta sobre los tamaños negativos de los contenedores y, posteriormente, intentaré convencer a cualquiera (sin esperanza, lo sé ...) de que sin firmar es bueno.

Tamaños de contenedores firmados

Supongamos que alguien codificó un error, lo que da como resultado un índice de contenedor negativo. El resultado es un comportamiento indefinido o una excepción / infracción de acceso. ¿Es eso realmente mejor que obtener un comportamiento indefinido o una excepción / violación de acceso cuando el tipo de índice no estaba firmado? Creo que no.

Ahora, hay una clase de gente a la que le encanta hablar de matemáticas y lo que es "natural" en este contexto. ¿Cómo puede ser natural que un tipo integral con número negativo describa algo, que es inherentemente> = 0? ¿Usas mucho matrices con tamaños negativos? En mi humilde opinión, especialmente a las personas con inclinaciones matemáticas les resultaría irritante este desajuste de semántica (el tipo de tamaño / índice dice que es posible lo negativo, mientras que una matriz de tamaño negativo es difícil de imaginar).

Entonces, la única pregunta que queda sobre este asunto es si, como se indica en el comentario de Google, un compilador podría ayudar activamente a encontrar tales errores. E incluso mejor que la alternativa, que serían enteros sin firmar protegidos por subdesbordamiento (el ensamblaje x86-64 y probablemente otras arquitecturas tienen medios para lograr eso, solo C / C ++ no usa esos medios). La única forma que puedo comprender es si el compilador agregó automáticamente comprobaciones de tiempo de ejecución ( if (index < 0) throwOrWhatever) o en caso de que las acciones de tiempo de compilación produzcan muchas advertencias / errores potencialmente falsos positivos "El índice para este acceso a la matriz podría ser negativo". Tengo mis dudas, esto sería de gran ayuda.

Además, las personas que realmente escriben verificaciones en tiempo de ejecución para sus índices de matriz / contenedor, es más trabajo tratar con enteros firmados. En lugar de escribir if (index < container.size()) { ... }ahora tiene que escribir: if (index >= 0 && index < container.size()) { ... }. Me parece un trabajo forzado y no una mejora ...

Los idiomas sin tipos sin firmar apestan ...

Sí, esta es una puñalada en Java. Ahora, vengo de una experiencia en programación integrada y trabajamos mucho con buses de campo, donde las operaciones binarias (y, o, xor, ...) y la composición de valores a nivel de bits es literalmente el pan y la mantequilla. Para uno de nuestros productos, nosotros, o más bien un cliente, queríamos un puerto java ... y me senté frente al tipo afortunadamente muy competente que hizo el puerto (me negué ...). Trató de mantener la compostura ... y sufrir en silencio ... pero el dolor estaba ahí, no podía dejar de maldecir después de unos días de lidiar constantemente con valores integrales firmados, los cuales DEBERÍAN estar sin firmar ... Incluso escribiendo pruebas unitarias para esos escenarios son dolorosos y yo, personalmente, creo que Java habría estado mejor si hubieran omitido los enteros con signo y solo se hubieran ofrecido sin firmar ... al menos entonces, no tiene que preocuparse por las extensiones de signo, etc.

Esos son mis 5 centavos al respecto.

BitTickler
fuente