2008-09-08 12 views
103

Supongamos que tengo el siguiente código C.Conversión firmada a sin firmar en C: ¿siempre es seguro?

unsigned int u = 1234; 
int i = -5678; 

unsigned int result = u + i; 

Qué conversiones implícitas están pasando aquí, y este código es seguro para todos los valores de u y i? (Seguro, en el sentido de que a pesar de que resultado en este ejemplo se desbordará a algún número positivo enorme, que podría echarlo hacia atrás a una int y obtener el resultado real.)

Respuesta

176

respuesta Corto

Su i será convertidos a un entero sin signo mediante la adición de UINT_MAX + 1, a continuación, la adición se llevará a cabo con los valores sin signo, lo que resulta en un gran result (dependiendo de los valores de u y i).

Respuesta larga

De acuerdo con el estándar C99:

6.3.1.8 conversiones aritméticas habituales

  1. Si ambos operandos tienen el mismo tipo, entonces no se necesita una mayor conversión .
  2. De lo contrario, si ambos operandos tienen tipos de entero con signo o ambos tienen tipos de entero sin signo, el operando con el tipo de rango de conversión entero menor se convierte al tipo del operando con mayor rango.
  3. De lo contrario, si el operando que tiene un tipo entero sin signo tiene un rango mayor o igual al rango del tipo del otro operando, entonces el operando con tipo entero con signo se convierte al tipo del operando con tipo entero sin signo.
  4. De lo contrario, si el tipo del operando con tipo entero con signo puede representar todos los valores del tipo del operando con tipo entero sin signo, entonces el operando con tipo entero sin signo se convierte al tipo del operando con entero con signo tipo.
  5. De lo contrario, ambos operandos se convierten al tipo de entero sin signo correspondiente al tipo del operando con tipo de entero con signo.

En su caso, tenemos una unsigned int (u) e int firmado (i). Con respecto a (3) anterior, dado que ambos operandos tienen el mismo rango, su i deberá ser convertido en un entero sin signo.

6.3.1.3 con y sin signo enteros

  1. Cuando un valor con el tipo de número entero se convierte en otro tipo entero distinto de _Bool, si el valor puede ser representado por el nuevo tipo, es sin cambios.
  2. De lo contrario, si el nuevo tipo no está firmado, el valor se convierte al agregar o restar repetidamente uno más que el valor máximo que se puede representar en el nuevo tipo hasta que el valor esté en el rango del nuevo tipo.
  3. De lo contrario, el nuevo tipo se firma y el valor no se puede representar en él; el resultado es definido por la implementación o se genera una señal definida por la implementación.

Ahora tenemos que referirse a (2) arriba. Su i se convertirá a un valor sin firmar agregando UINT_MAX + 1. Por lo tanto, el resultado dependerá de cómo se defina UINT_MAX en su implementación. Será grande, pero no se desbordará, porque:

6.2.5 (9)

Un cálculo que implica operandos unsigned nunca puede desbordamiento, porque un resultado que no puede ser representado por el número entero sin signo resultante tipo es módulo reducido el número que es uno mayor que el valor más grande que puede representarse por el tipo resultante.

Bono: Conversión aritmética semi-WTF

#include <stdio.h> 

int main(void) 
{ 
    unsigned int plus_one = 1; 
    int minus_one = -1; 

    if(plus_one < minus_one) 
    printf("1 < -1"); 
    else 
    printf("boring"); 

    return 0; 
} 

Puede utilizar este enlace para probar esta línea: http://codepad.org/yPhYCMFO

Bono: Aritmética Conversión de Efectos Secundarios

Aritmética las reglas de conversión se pueden usar para obtener el valor de UINT_MAX inicializando un valor sin signo a -1, es decir:

unsigned int umax = -1; // umax set to UINT_MAX 

Esto está garantizado para ser portátil, independientemente de la representación número firmada del sistema debido a las reglas de conversión descritas anteriormente. Consulte esta pregunta SO para obtener más información: Is it safe to use -1 to set all bits to true?

+11

Whoa allí. Está bien definido pasar de firmado a no firmado, pero pasar de unsigned a signed está definido por la implementación. – rlbond

+5

Esto no es correcto. Desde un punto de vista del lenguaje La conversión del entero de 'int' a' unsigned int' tiene todo que ver con el valor del objeto fuente y nada (conceptualmente) con su representación interna. El valor se convierte usando la aritmética de módulo 2^N donde N es el número de bits de valor en una "int sin signo", cualquier representación que la implementación use para 'int'. –

+0

Esta respuesta es simplemente incorrecta. Está explicando cómo funcionan las implementaciones comunes, no cómo funciona el lenguaje. –

3

Cuando se agregan una variable sin signo y otra con signo (o cualquier operación binaria) ambas se convierten implícitamente a sin signo, lo que en este caso daría lugar a un resultado enorme.

Por lo tanto, es seguro en el sentido de que el resultado puede ser enorme y erróneo, pero nunca se bloqueará.

+0

No es cierto. * 6.3.1.8 Conversiones aritméticas habituales * Si suma un int yy un carácter sin signo, este último se convierte en int. Si suma dos caracteres sin signo, se convierten a int. – 2501

3

Al convertir de firmado a sin firmar hay dos posibilidades. Los números que originalmente eran positivos permanecen (o se interpretan como) el mismo valor. El número que originalmente era negativo ahora se interpretará como números positivos más grandes.

1

Como se ha respondido anteriormente, puede enviar y recibir documentos entre firmado y sin firmar sin ningún problema. El caso de borde para enteros con signo es -1 (0xFFFFFFFF). Intenta sumar y restar de eso y encontrarás que puedes devolverlo y que sea correcto.

Sin embargo, si usted va a ser la fundición de ida y vuelta, te recomiendo muchísimo asignar nombres a las variables de tal manera que está claro de qué tipo son, por ejemplo:

int iValue, iResult; 
unsigned int uValue, uResult; 

Es demasiado fácil de conseguir distraído por cuestiones más importantes y olvide qué variable es qué tipo si se nombran sin una pista. No desea convertir a un unsigned y luego usarlo como un índice de matriz.

3

Haciendo referencia a the bible:

  • Su operación de suma hace que la int que ser convertido a un unsigned int.
  • Suponiendo que hay dos representaciones de complemento y tipos de igual tamaño, el patrón de bits no cambia.
  • La conversión de unsigned int a signed int depende de la implementación. (Pero probablemente funcione de la manera que espera en la mayoría de las plataformas en estos días.)
  • Las reglas son un poco más complicadas en el caso de combinar firmado y sin firmar de diferentes tamaños.
16

La conversión de firmado a firmar hace no necesariamente sólo copiar o reinterpretan la representación del valor con signo. Citando el estándar C (C99 6.3.1.3):

Cuando un valor con el tipo de número entero se convierte en otro tipo entero distinto de _Bool, si el valor puede ser representado por el nuevo tipo, es sin cambios.

De lo contrario, si el nuevo tipo es sin signo, el valor se convierte mediante la adición repetida o restando uno más que el valor máximo que se puede representar en el nuevo tipo de hasta que el valor está en el rango del nuevo tipo.

De lo contrario, el nuevo tipo se firma y el valor no se puede representar en él; el resultado es definido por la implementación o se genera una señal definida por la implementación.

Para la representación complementaria de los dos que es casi universal en la actualidad, las reglas corresponden a la reinterpretación de los bits. Pero para otras representaciones (signo-y-magnitud o complemento de unos), la implementación C aún debe organizar el mismo resultado, lo que significa que la conversión no puede simplemente copiar los bits. Por ejemplo, (sin signo) -1 == UINT_MAX, independientemente de la representación.

En general, las conversiones en C se definen para operar en valores, no en representaciones.

Para responder a la pregunta original:

unsigned int u = 1234; 
int i = -5678; 

unsigned int result = u + i; 

El valor de i se convierte en unsigned int, produciendo UINT_MAX + 1 - 5678. Este valor luego se agrega al valor sin firmar 1234, produciendo UINT_MAX + 1 - 4444.

(diferencia de desbordamiento no firmado, firmado desbordamiento invoca un comportamiento indefinido Wraparound es común, pero no está garantizada por la norma C -. Y optimizaciones del compilador puede causar estragos en código que hace suposiciones injustificadas.)

-15

Respuestas Horribles abundancia

Ozgur Ozcitak

Cuando lanzas de firmado a firmar (y viceversa) la representación interna del número no cambia .Lo que cambia es cómo el compilador interpreta el bit de signo.

Esto es completamente incorrecto.

Mats Fredriksson se añaden

Cuando uno sin firmar y uno firmado variable de

(o cualquier operación binaria ) ambos son implícitamente convertido a unsigned, que haría en este caso dar lugar a un enorme resultado .

Esto también es incorrecto. Los enteros sin signo pueden promoverse a enteros si tienen la misma precisión debido a los bits de relleno en el tipo sin signo.

SMH

Su operación de suma hace que el int a convertir en un entero sin signo.

Wrong. Tal vez lo haga y tal vez no.

La conversión de unsigned int a la firma int depende de la implementación. (Pero que probablemente funciona de la forma esperada en la mayoría de las plataformas en estos días.)

incorrecto. Es un comportamiento indefinido si causa un desbordamiento o si se conserva el valor.

Anónimo

El valor de i se convierte en unsigned int ...

incorrecto. Depende de la precisión de un int relativo a un int sin signo.

Taylor Precio

Como fue respondida con anterioridad, puede echado hacia atrás y adelante entre firmado y sin firmar y sin un problema.

Wrong. Intentar almacenar un valor fuera del rango de un entero con signo da como resultado un comportamiento indefinido.

Ahora puedo finalmente responder la pregunta.

Si la precisión de int fuera igual a unsigned int, se promocionará a un int firmado y obtendrá el valor -4444 de la expresión (u + i). Ahora, si usted y yo tenemos otros valores, es posible que tenga un exceso de flujo y un comportamiento indefinido, pero con esos números exactos obtendrá -4444 [1]. Este valor tendrá tipo int.Pero está intentando almacenar ese valor en un int sin firmar para que luego se convierta en un int sin firmar y el valor que resultará será (UINT_MAX + 1) - 4444.

¿Debería la precisión de unsigned? int ser mayor que la de un int, el int firmado se promocionará a un int sin signo que arroje el valor (UINT_MAX + 1) - 5678 que se agregará al otro unsigned int 1234. En caso de que uy tenga otros valores, lo que la expresión cae fuera del rango {0..UINT_MAX} el valor (UINT_MAX + 1) se agregará o restará hasta que el resultado caiga dentro del rango {0..UINT_MAX) y no se producirá ningún comportamiento indefinido.

¿Qué es la precisión?

Los enteros tienen bits de relleno, bits de signo y bits de valor. Los enteros sin signo no tienen un bit de signo obviamente. El carácter sin signo está garantizado además de no tener bits de relleno. El número de bits de valores que tiene un número entero es la precisión que tiene.

[Gotchas]

El sizeof macro macro por sí sola no puede utilizarse para determinar la precisión de un número entero si bits de relleno están presentes. Y el tamaño de un byte no tiene que ser un octeto (ocho bits) como lo define C99.

[1] El desbordamiento puede ocurrir en uno de dos puntos. O bien antes de la adición (durante la promoción): cuando tienes un int sin firmar que es demasiado grande para caber dentro de un int. El desbordamiento también puede ocurrir después de la adición, incluso si el int sin firmar estaba dentro del rango de un int, después de la adición, el resultado puede desbordarse.


En una nota relacionada, Soy un estudiante recién graduado tratando de encontrar trabajo;)

+6

"Ints sin firmar se pueden promocionar a ints". No es verdad. No se produce un entero _promotion_ ya que los tipos ya son rank> = int. 6.3.1.1: "El rango de cualquier tipo de entero sin signo será igual al rango del tipo de entero con signo correspondiente, si corresponde". y 6.3.1.8: "De lo contrario, si el operando que tiene un tipo entero sin signo tiene un rango mayor ** o igual ** al rango del tipo del otro operando, entonces el operando con tipo entero con signo se convierte al tipo del operando con tipo entero sin signo. " ambos garantizan que 'int' se convierta en' unsigned int' cuando se aplican las conversiones aritméticas habituales. –

+1

6.3.1.8 Ocurre solo después de la promoción entera. El párrafo de apertura dice "De lo contrario, las promociones enteras se realizan en ambos operandos. ENTONCES las siguientes reglas se aplican a los operandos promocionados". Así que ve a leer las reglas de promoción 6.3.1.1 ... "Un objeto o expresión con un tipo entero cuyo rango de conversión entero es menos que o IGUAL al rango de int y unsigned int" y "Si un int puede representar todos los valores de el tipo original, el valor se convierte en un int ". –

+1

6.3.1.1 La promoción de enteros utilizada se usa para convertir algunos tipos de enteros que no son 'int' o' unsigned int' a uno de esos tipos donde se espera algo de tipo 'unsigned int' o' int'. El "o igual" se agregó en TC2 para permitir que los tipos enumerados del rango de conversión igual a 'int' o' unsigned int' se conviertan a uno de esos tipos. Nunca se pretendía que la promoción descrita convirtiera entre 'unsigned int' y' int'. La determinación de tipo común entre 'unsigned int' e' int' todavía se rige por 6.3.1.8, incluso se publica TC2. –

0

Qué conversiones implícitas están pasando aquí,

i se convertirá en una entero sin signo.

y este código es seguro para todos los valores de uy yo?

Seguro en el sentido de estar bien definido sí (ver https://stackoverflow.com/a/50632/5083516).

Las reglas están escritas en lenguaje de lectura típicamente difícil de leer, pero esencialmente cualquier representación que se haya utilizado en el entero con signo, el entero sin signo contendrá una representación complementaria de 2 del número.

La suma, la resta y la multiplicación funcionarán correctamente en estos números, dando como resultado otro entero sin signo que contiene un número de complemento de dos que representa el "resultado real".

división y fundición a tipos enteros sin signo más grandes tendrán resultados bien definidos, pero esos resultados no serán representaciones complementarias de 2 del "resultado real".

(Seguro, en el sentido de que, aunque el resultado de este ejemplo se desborde a un gran número positivo, podría devolverlo a un int y obtener el resultado real.)

Mientras que las conversiones de suscritos a firmar se definen por la norma a la inversa es definida por la implementación tanto gcc y msvc definen la conversión de tal manera que obtendrá el "número real" cuando convertir un número complementario del 2 almacena en una entero sin signo a un entero con signo. Espero que solo encuentres otro comportamiento en sistemas oscuros que no usen el complemento de 2 para enteros con signo.

https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation https://msdn.microsoft.com/en-us/library/0eex498h.aspx

Cuestiones relacionadas