Firmado para la conversión sin firma en C – ¿siempre es seguro?

Supongamos que tengo el siguiente código C.

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

¿Qué conversiones implícitas están ocurriendo aquí, y este código es seguro para todos los valores de u i ? (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).

Respuesta corta

Su i se convertirá en un entero sin signo agregando UINT_MAX + 1 , luego la adición se llevará a cabo con los valores sin signo, lo que resulta en un result grande (según los valores de u 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 más conversión.
  2. De lo contrario, si ambos operandos tienen tipos enteros con signo o ambos tienen tipos enteros 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 tipo entero con signo.
  5. De lo contrario, ambos operandos se convierten al tipo de entero sin signo correspondiente al tipo de operando con tipo de entero con signo.

En su caso, tenemos un int sin firmar ( u ) y firmado int ( i ). Con respecto a (3) arriba, dado que ambos operandos tienen el mismo rango, tendré que convertirlo a un entero sin signo.

6.3.1.3 enteros con signo y sin signo

  1. Cuando un valor con tipo entero se convierte a otro tipo entero distinto de _Bool, si el valor puede representarse con el tipo nuevo, no se modifica.
  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 necesitamos referirnos a (2) arriba. Tu i se convertirá a un valor sin firmar agregando UINT_MAX + 1 . Entonces, 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ómputo que involucre operandos sin firmar nunca puede desbordarse, porque un resultado que no puede ser representado por el tipo de entero sin signo resultante se reduce en módulo al número que es uno mayor que el valor más grande que puede ser representado por el tipo resultante.

Bono: Conversión aritmética Semi-WTF

 #include  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 usar este enlace para probar esto en línea: http://codepad.org/yPhYCMFO

Bono: Efecto secundario de conversión aritmética

Las reglas de conversión aritmética 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 de número firmado del sistema debido a las reglas de conversión descritas anteriormente. Vea esta pregunta SO para obtener más información: ¿Es seguro usar -1 para establecer todos los bits en verdadero?

La conversión de firmado a no firmado no necesariamente solo copia o reinterpreta la representación del valor firmado. Citando el estándar C (C99 6.3.1.3):

Cuando un valor con tipo entero se convierte a otro tipo entero distinto de _Bool, si el valor puede representarse con el tipo nuevo, no se modifica.

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.

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 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 signo 1234, produciendo UINT_MAX + 1 - 4444 .

(A diferencia del desbordamiento sin signo, el desbordamiento firmado invoca un comportamiento indefinido. El envolvente es común, pero no está garantizado por el estándar C, y las optimizaciones del comstackdor pueden causar esgulps en el código que hace suposiciones injustificadas).

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 resultaría en un gran resultado.

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

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.

Refiriéndose a la Biblia :

  • Su operación de adición hace que la int se convierta a una int sin firmar.
  • Suponiendo que la representación de dos complementos y los 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 funciona de la manera que espera en la mayoría de las plataformas estos días).
  • Las reglas son un poco más complicadas en el caso de combinar firmado y sin firmar de diferentes tamaños.

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

Sin embargo, si va a realizar un intercambio de ida y vuelta, le recomiendo encarecidamente que nombre sus variables de modo que quede claro de qué tipo son, por ejemplo:

 int iValue, iResult; unsigned int uValue, uResult; 

Es demasiado fácil distraerse con asuntos más importantes y olvidar 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.

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

me convertiré en un entero sin signo.

y este código es seguro para todos los valores de ti y 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 estándares típicamente difíciles 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 sum, la resta y la multiplicación funcionarán correctamente en estos números, lo que resulta en otro entero sin signo que contiene un número de complemento de dos que representa el “resultado real”.

la división y la conversió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 firmado a no firmado están definidas por el estándar, el reverso está definido por la implementación tanto gcc como msvc definen la conversión de manera que obtendrá el “resultado real” al convertir un número de complemento de 2 almacenado en un 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

Horrible Answers Galore

Ozgur Ozcitak

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

Esto es completamente incorrecto

Mats Fredriksson

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 resultaría en un gran resultado.

Esto también está mal. 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 adición hace que la int se convierta a una int sin firmar.

Incorrecto. Tal vez lo haga y tal vez no.

La conversión de unsigned int a signed int depende de la implementación. (Pero probablemente funciona de la manera que espera en la mayoría de las plataformas 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 a unsigned int …

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

Precio de Taylor

Como se respondió anteriormente, puede enviar y recibir mensajes entre firmados y sin firmar sin ningún problema.

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

Ahora finalmente puedo responder la pregunta.

Si la precisión de int es igual a unsigned int, u será promocionado a un int firmado y obtendrás el valor -4444 de la expresión (u + i). Ahora, si tú y yo tenemos otros valores, puedes obtener un comportamiento desbordado e indefinido, pero con esos números exactos obtendrás -4444 [1] . Este valor tendrá tipo int. Pero está tratando de almacenar ese valor en un int sin firmar para que luego se convierta a un int sin firmar y el valor que el resultado tendrá será (UINT_MAX + 1) – 4444.

En caso de que la precisión de unsigned int sea mayor que la de un int, la int firmada se promocionará a uns int sin firmar, produciendo el valor (UINT_MAX + 1) – 5678 que se agregará al otro unsigned int 1234. Deberíamos tener uy i otros valores, que hacen que la expresión caiga 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 tamaño macro de la macro sola no se puede usar para determinar la precisión de un entero si están presentes los bits de relleno. 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 no relacionada, soy un estudiante graduado reciente que intenta encontrar trabajo;)