2010-09-16 16 views

Respuesta

21

Declarar variables para valores semánticamente no negativos como unsigned es un buen estilo y una buena práctica de programación.

Sin embargo, tenga en cuenta que esto no le impide cometer errores. Si es perfectamente legal asignar valores negativos a enteros sin signo, el valor se convierte implícitamente a la forma sin signo de acuerdo con las reglas de la aritmética sin signo. Algunos compiladores pueden emitir advertencias en tales casos, otros lo harán silenciosamente.

También vale la pena señalar que para trabajar con enteros sin signo es necesario conocer algunas técnicas dedicadas sin firmar. Por ejemplo, un ejemplo "clásico" que a menudo se menciona con relación a este tema es al revés iteración

for (int i = 99; i >= 0; --i) { 
    /* whatever */ 
} 

El ciclo anterior se ve natural con firmado i, pero no se puede convertir directamente en forma sin firmar, lo que significa que

for (unsigned i = 99; i >= 0; --i) { 
    /* whatever */ 
} 

realmente no hace lo que está destinado a hacer (en realidad es un ciclo interminable). La técnica apropiada en este caso es bien

for (unsigned i = 100; i > 0;) { 
    --i; 
    /* whatever */ 
} 

o

for (unsigned i = 100; i-- > 0;) { 
    /* whatever */ 
} 

Esto se utiliza a menudo como un argumento en contra de los tipos sin signo, es decir, al parecer las versiones sin firmar por encima del ciclo se ven "no natural" y "ilegible ". En realidad, aunque el problema que estamos tratando aquí es el problema genérico de trabajar cerca del extremo izquierdo de un rango cerrado. Este problema se manifiesta de muchas maneras diferentes en C y C++ (como la iteración hacia atrás sobre una matriz usando la técnica de "puntero deslizante" de iteración hacia atrás sobre un contenedor estándar usando un iterador). Es decir. Independientemente de lo poco elegante que parezcan los ciclos sin signo anteriores, no hay forma de evitarlos por completo, incluso si nunca usa tipos enteros sin signo. Por lo tanto, es mejor aprender estas técnicas e incluirlas en su conjunto de expresiones idiomáticas establecidas.

+2

Uso unsigned exclusivamente para la manipulación de bits y en casos más raros para el rango de biggger. Sé cómo codificar con unsigneds, pero no espero que todos mis compañeros de trabajo lo hagan bien todas las veces. PD: la técnica adecuada usa> 0 en lugar de> = 0 ;-). –

+0

@ Peter G .: Fixed. Gracias por mencionarlo. – AnT

+0

@AndreyT: en realidad la iteración inversa está bastante bien manejada por la biblioteca estándar, creo. De todos modos, yo no bucle enteros con frecuencia yo mismo ... –

6

No impide que las personas utilicen indebidamente su interfaz, pero al menos deberían recibir una advertencia a menos que agreguen un molde de estilo C o static_cast para que desaparezca (en cuyo caso no puede ayudarlos más).

Sí, hay un valor en esto ya que expresa adecuadamente la semántica que desea.

+0

No necesita modelos de estilo C para descartar (no utilizar) la firma. Es un elenco estático simple. –

+0

observado, gracias ... –

4

misma hace dos cosas:

1) Se da el doble de la gama de valores de sus valores sin signo. Cuando se "firma" el bit más alto se utiliza como el bit de signo (1 significa negativo, 0 para positivo), cuando "no está firmado" puede usar ese bit para los datos. Por ejemplo, un tipo char va desde -128 y 127, un unsigned char va Forma 0 a 255

2) Afecta cómo los >> actos operador, específicamente cuando la derecha de desplazamiento de valores negativos.

+0

No es realmente un signo de bit. 1 para negativo, pero 0 para no negativo. – csj

+0

@csj: ¿Qué valor asignarías a un bit de signo "real" para cero? La afirmación "0 para positivo" es correcta; "0 significa positivo" no es lo que dijo. – Potatoswatter

+0

Al cumplir con las plataformas C++ no es necesario utilizar el complemento de dos. En la práctica, usar 'unsigned' duplica el rango de valores disponibles. – strager

1

Al usar unsigned cuando los valores con signo no serán necesarios, además de garantizar que el tipo de datos no represente valores por debajo del límite inferior deseado, aumenta el límite superior máximo. Todas las combinaciones de bits que de otra manera se usarían para representar números negativos, se usan para representar un conjunto mayor de números positivos.

2

Esto tiene valor por la misma razón que "const corrección" tiene valor. Si sabe que un valor particular no debería cambiar, declare const y deje que el compilador lo ayude. Si sabes que una variable siempre debe ser no negativa, entonces declara como unsigned y el compilador te ayudará a detectar inconsistencias.

(Eso, y se puede expresar números dos veces más grande si se utiliza unsigned int en lugar de int en este contexto.)

4

Una sutileza menor es que se reduce la cantidad de límites de la matriz de pruebas comprobando que podría ser necesario ... por ejemplo en lugar de tener que escribir:

int idx = [...]; 
if ((idx >= 0)&&(idx < arrayLength)) printf("array value is %i\n", array[idx]); 

sólo puede escribir:

unsigned int idx = [...]; 
if (idx < arrayLength) printf("array value is %i\n", array[idx]); 
+0

Si '[...]' arroja un valor negativo, detectará el error en el primer caso. Sin embargo, en el segundo caso, no detectará el error, pero trabajará con otro índice positivo "aleatorio" que resultó del comportamiento sin firmar. Eso es mucho peor. –

+0

@Johannes: No veo eso. Si idx fue negativo, se cambiará a un número sin signo muy grande (más de dos mil millones para 'int's de 32 bits). Suponiendo que 'arrayLength' es un número vagamente razonable, el segundo caso detectará el error. –

+0

@David Bueno, es un problema principal de ese pensamiento, no necesariamente un problema en este caso particular. ¿Qué pasa si en lugar de 'idx' tienes' len' y no hay límite superior? La aplicación del principal aplicado en esta respuesta significa que ya no realiza ningún control, y luego opera en esa gran longitud. No es bueno. –

4

Las otras respuestas son buenas, pero a veces puede dar lugar a confusión. Que es lo que creo que algunos idiomas han optado por no tener unsigned tipos enteros.

Por ejemplo, suponga que tiene una estructura que se parece a esto para representar un objeto de imagen:

struct T { 
    int x; 
    int y; 
    unsigned int width; 
    unsigned int height; 
}; 

La idea es que es imposible tener una anchura negativo. Bueno, ¿qué tipo de datos usas para almacenar el borde derecho del rectángulo?

int right = r.x + r.width; // causes a warning on some compilers with certain flags 

y ciertamente todavía no lo protege de cualquier desbordamiento de enteros.Así que en este escenario, aunque width y height no pueden ser negativos desde el punto de vista conceptual, no hay ganancia real en hacerlos unsigned, excepto que se requieren algunos moldes para deshacerse de las advertencias sobre la mezcla de tipos firmados y no firmados. Al final, al menos en casos como este, es mejor simplemente hacerlos todos int s, después de todo, las probabilidades son que no tiene una ventana lo suficientemente amplia como como que es unsigned.

+2

Este ejemplo rect es bueno. Considere: En el caso anterior, si 'r.x' es negativo,' r.x + r.width' arrojará un resultado totalmente extraño: '-5 + 4u', por ejemplo, arroja' UINT_MAX'. –

+0

A veces deseo que los idiomas definan 7, 15, 31, etc. sin firmar.bits enteros; dichos tipos se procesarían como sin firmar, pero las operaciones entre dichos tipos y tipos firmados más grandes (aunque solo sea por un bit) se firmarían. En algunos procesadores, ciertas operaciones pueden ser más rápidas con valores firmados o sin firmar. Por ejemplo, si un procesador de 32 bits siempre muestra los valores de 16 bits al cargar, se podría cargar un valor "sin firmar de 15 bits" en una instrucción; un valor "sin firmar de 16 bits" requeriría dos. Un compilador podría usar el código más corto con el tipo de 15 bits sin signo. – supercat

+0

Es un buen ejemplo de un caso en el que unsigned no tiene sentido, pero la eliminación completa de valores sin firmar de un idioma, a la Java, simplemente causa otros problemas. Varias veces he encontrado errores sutiles y difíciles de identificar en programas Java donde alguien escribió algunas rutinas de E/S binarias que fallaron de maneras extrañas cuando intentaron trabajar con bytes mayores a 0x7F. – Porculus

1

También evita que tenga que realizar un envío a/sin signo al interactuar con otras interfaces. Por ejemplo:

for (int i = 0; i < some_vector.size(); ++i) 

Eso generalmente molestará a cualquiera que necesite compilar sin advertencias.

1

No impedirá que se ingresen números negativos en una función; en cambio, los interpretará como grandes números positivos. Esto puede ser moderadamente útil si conoce un límite superior para la comprobación de errores, pero debe verificar el error usted mismo. Algunos compiladores emitirán advertencias, pero si está utilizando mucho los tipos sin firmar, es posible que haya demasiadas advertencias para tratar con facilidad. Estas advertencias se pueden cubrir con moldes, pero eso es peor que solo adherirse a tipos firmados.

No utilizaría un tipo sin signo si supiera que la variable no debería ser negativa, sino más bien si no fuera así.size_t es un tipo sin firmar, por ejemplo, ya que un tipo de datos simplemente no puede tener un tamaño negativo. Si un valor podría ser negativo pero no debería serlo, es más fácil expresarlo al tenerlo como un tipo firmado y usar algo como i < 0 o i >= 0 (estas condiciones aparecen como false y true respectivamente si i es un tipo sin firmar, independientemente de de su valor).

Si le preocupa la conformidad estándar estricta, puede ser útil saber que los desbordamientos en la aritmética sin signo están completamente definidos, mientras que en la aritmética con signo son un comportamiento indefinido.

0

Un argumento contrario al uso de unsigned es que puede encontrarse en situaciones muy razonables en las que se torna incómodo y se introducen errores involuntarios. Considere una clase, por ejemplo, una clase de lista o algo así, con el siguiente método:

unsigned int length() { ... } 

Parece muy razonable. Pero a continuación, cuando se quiere iterar sobre ella, se obtiene lo siguiente:

for (unsigned int i = my_class.length(); i >= 0; --i) { ... } 

su bucle no terminará y ahora se ve obligado a emitir o hacer alguna otra incomodidad.

Una alternativa al uso de unsigned es solo a assert que sus valores no son negativos.

Reference.

4

¿Es importante declarar una variable como no firmada si se sabe que nunca debe ser negativa?

Ciertamente no es importante. Algunas personas (Stroustrup y Scott Meyers, ver "Unsigned vs signed - Is Bjarne mistaken?") rechazan la idea de que una variable no debería estar firmada solo porque representa una cantidad sin firmar. Si el punto de usar unsigned sería indicar que una variable solo puede almacenar valores no negativos, debe verificarlo de alguna manera. De lo contrario, lo único que consigue es

  • Un tipo que se esconde en silencio errores, ya que no permite que los valores negativos para exponer
  • doble del rango positivo del correspondiente tipo firmado
  • Definido desbordamiento/bit-shift/etc semántica

Ciertamente no impiden a las personas el suministro de valores negativos a su función, y el compilador no será capaz de advertirle sobre cualquiera de estos casos (pensar en un tiempo de ejecución negativo siendo int-valor basado aprobado). ¿Por qué no afirmar en la función en su lugar?

assert((idx >= 0) && "Index must be greater/equal than 0!"); 

El tipo sin signo presenta muchas trampas también. Usted tiene que tener cuidado cuando se utiliza en los cálculos que pueden ser temporales menor que cero (bucle hacia abajo contando, o algo así) y, especialmente, las promociones automáticas que ocurren en las C y C idiomas ++ entre sin firmar y valores con signo

// assume idx is unsigned. What if idx is 0 !? 
if(idx - 1 > 3) /* do something */; 
+2

"¿Por qué no afirmar en la función"? Porque el tiempo de compilación es mejor que el tiempo de ejecución y es bueno dejar de lado las afirmaciones. Como ha señalado, el uso de 'unsigned' no hace que desaparezca' assert', pero hace que la condición sea más simple: 'x

+0

Muchos, si no la mayoría de los compiladores, pueden advertir sobre las conversiones firmadas a las no firmadas. Esto una vez salvó mi trasero. – Potatoswatter

+2

@Konrad Mi punto es que el uso de 'unsigned' hará que la función no pueda detectar el error porque el parámetro en la función siempre es positivo por definición.El compilador no puede advertirle en todos los casos y, a veces, solo enviar a 'unsigned' en el lado de la llamada solo para deshacerse de una advertencia no solucionará ningún error; en su lugar, un valor negativo se ajustará silenciosamente. –

1

La combinación de tipos firmados y sin firmar puede ser un gran dolor de cabeza. El código resultante a menudo será inflado, incorrecto o ambos (*).En muchos casos, a menos que necesite almacenar valores entre 2,147,483,648 y 4,294,967,295 dentro de una variable de 32 bits, o si necesita trabajar con valores superiores a 9,223,372,036,854,775,807, le recomiendo que no se moleste en absoluto con los tipos sin firmar.

(*) Lo que debería ocurrir, por ejemplo, si un programador hace:

 
{ Question would be applicable to C, Pascal, Basic, or any other language } 
    If SignedVar + UnsignedVar > OtherSignedVar Then DoSomething; 

Creo edad Pascal de Borland se ocuparía de la situación anterior mediante la conversión de SignedVar y UnsignedVar a un tipo con signo más grande (con el apoyo de la mayor tipo, por cierto, se firmó, por lo que cada tipo sin firmar podría convertirse en uno más grande firmado). Esto produciría código grande, pero sería correcto. En C, si una variable con signo es negativa, es probable que el resultado sea numéricamente incorrecto incluso si UnsignedVar tiene cero. Muchos otros malos escenarios existen también.

+0

Nunca he encontrado código de hinchazón (o dolores de cabeza, incluso menores) relacionados con valores sin signo. Hace código más explícito, sin duda. Pero eso es * bueno *. –

+0

@Konrad Rudolph: si todas las variables no están firmadas, las cosas funcionan de manera predecible. Pero, ¿para qué valores de variables se ejecutará la condición anterior? El código no es exactamente complicado, pero en muchos idiomas su comportamiento exacto es. – supercat

+0

@supercat: fácil, advertirá (o, con la configuración de mi compilador: no compilar) debido a la comparación firmada/no firmada. Ese es el comportamiento que esperaba y el que obtuve. –

1

Hay dos cosas principales que utilizan unsigned le da

  • que le permite utilizar el derecho de operador de desplazamiento >> con seguridad en cualquier valor, ya que no puede ser negativo - el uso de un desplazamiento a la derecha en una el valor negativo no está definido.

  • le da la modificación aritmética 2^n aritmética. Con valores firmados, el efecto de underflow/overflow no está definido. Con los valores sin signo obtienes la aritmética mod 2^n, por lo que 0U - 1U siempre te dará el mayor valor posible sin signo (que siempre será 1 menos que la potencia de 2).

-1

Como acotación al margen, que no suelen utilizar int o unsigned int, en lugar utilizo cualquiera {int16_t, int32_t, ...} o {uint16_t, uint32_t, ...}. (Debe incluir stdint.h para usarlos). No estoy seguro de cómo lo encuentran mis colegas, pero trato de transmitir el tamaño de la variable de esta manera. En algunos lugares, trato de ser más descarado haciendo algo como: typedef uint32_t Counter32;

Cuestiones relacionadas