Conversión firmada a no firmada en C: ¿es siempre seguro?

135

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 sucediendo aquí, y este código es seguro para todos los valores de uy i? (Seguro, en el sentido de que a pesar de que el resultado en este ejemplo se desbordará a un gran número positivo, podría devolverlo a un int y obtener el resultado real).

Cwick
fuente

Respuestas:

223

Respuesta corta

Su ise convertirá en un entero sin signo agregando UINT_MAX + 1, luego la adición se llevará a cabo con los valores sin signo, dando como resultado un gran result(dependiendo de los valores de uy i).

Respuesta larga

De acuerdo con el estándar C99:

6.3.1.8 Conversiones aritméticas usuales

  1. Si ambos operandos tienen el mismo tipo, 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 de entero menor se convierte al tipo del operando con rango mayor.
  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 en el tipo del operando con un tipo entero sin signo.
  4. De lo contrario, si el tipo de operando con tipo entero con signo puede representar todos los valores del tipo de operando con tipo entero sin signo, entonces el operando con tipo entero sin signo se convierte en el tipo de operando con tipo entero con signo.
  5. De lo contrario, ambos operandos se convierten al tipo entero sin signo correspondiente al tipo del operando con tipo entero con signo.

En su caso, tenemos uno sin signo int ( u) y firmado int ( i). En referencia a (3) anterior, dado que ambos operandos tienen el mismo rango, iserá necesario convertirlo a un entero sin signo.

6.3.1.3 Enteros con y sin signo

  1. Cuando un valor con tipo entero se convierte en otro tipo entero distinto de _Bool, si el valor puede ser representado por el nuevo tipo, no se modifica.
  2. De lo contrario, si el nuevo tipo no tiene signo, el valor se convierte agregando o restando 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 está definido por la implementación o se genera una señal definida por la implementación.

Ahora necesitamos referirnos a (2) arriba. Su ise convertirá en un valor sin signo al agregar UINT_MAX + 1. Por lo tanto, el resultado dependerá de cómo UINT_MAXse defina en su implementación. Será grande, pero no se desbordará porque:

6.2.5 (9)

Un cálculo que involucra operandos sin signo nunca puede desbordarse, porque un resultado que no puede ser representado por el tipo entero resultante sin signo se reduce módulo el número que es uno mayor que el valor más grande que puede ser representado por el tipo resultante.

Bonus: 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 usar este enlace para probar esto en línea: https://repl.it/repls/QuickWhimsicalBytes

Bonus: efecto secundario de conversión aritmética

Las reglas de conversión aritmética se pueden usar para obtener el valor UINT_MAXinicializando un valor sin signo -1, es decir:

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

Se garantiza que es portátil independientemente de la representación de números firmados del sistema debido a las reglas de conversión descritas anteriormente. Consulte esta pregunta SO para obtener más información: ¿Es seguro usar -1 para establecer todos los bits en verdadero?

Ozgur Ozcitak
fuente
No entiendo por qué no puede simplemente hacer un valor absoluto y luego tratarlo como sin signo, al igual que con los números positivos.
José Salvatierra
77
@ D.Singh, ¿puede señalar amablemente las partes incorrectas en la respuesta?
Shmil The Cat
Para convertir firmado a sin signo, agregamos el valor máximo del valor sin signo (UINT_MAX +1). Del mismo modo, ¿cuál es la manera fácil de convertir de sin firmar a firmado? ¿Necesitamos restar el número dado del valor máximo (256 en caso de caracteres sin signo)? Por ejemplo: 140 cuando se convierte en número firmado se convierte en -116. Pero 20 se convierte en 20 en sí mismo. ¿Algún truco fácil aquí?
Jon Wheelock
@JonWheelock ver: stackoverflow.com/questions/8317295/…
Ozgur Ozcitak
24

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

Cuando un valor con tipo entero se convierte en otro tipo entero distinto de _Bool, si el valor puede ser representado por el nuevo tipo, no se modifica.

De lo contrario, si el nuevo tipo no tiene signo, el valor se convierte agregando o restando 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 está definido por la implementación o se genera una señal definida por la implementación.

Para la representación del complemento de los dos que es casi universal en estos días, las reglas corresponden a la reinterpretación de los bits. Pero para otras representaciones (signo y magnitud o complemento de unos), la implementación de C aún debe organizar el mismo resultado, lo que significa que la conversión no puede simplemente copiar los bits. Por ejemplo, (sin firmar) -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, rindiendo UINT_MAX + 1 - 5678. Este valor se agrega luego al valor sin signo 1234, dando como resultado UINT_MAX + 1 - 4444.

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


fuente
5

Refiriéndose a la biblia :

  • Su operación de suma hace que int se convierta en un int sin signo.
  • Suponiendo que la representación del complemento a dos y los tipos de igual tamaño, el patrón de bits no cambia.
  • La conversión de int sin signo a int con signo 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 con signo y sin signo de diferentes tamaños.
smh
fuente
3

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

Por lo tanto, es seguro en el sentido de que el resultado puede ser enorme e incorrecto, pero nunca se bloqueará.

Mats Fredriksson
fuente
No es verdad. 6.3.1.8 Conversiones aritméticas habituales Si suma un int y un char 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 no firmado hay dos posibilidades. Los números que originalmente eran positivos permanecen (o se interpretan como) el mismo valor. Los números que originalmente eran negativos ahora se interpretarán como números positivos más grandes.

Tim Ring
fuente
1

Como se respondió anteriormente, puede emitir de un lado a otro entre firmado y sin firmar sin ningún problema. El caso límite para los enteros con signo es -1 (0xFFFFFFFF). Intenta sumar y restar de eso y verás que puedes volver a lanzar y hacer que sea correcto.

Sin embargo, si va a emitir de un lado a otro, le recomiendo nombrar 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 problemas más importantes y olvidar qué variable es de qué tipo si se nombran sin una pista. No desea convertir a un sin firmar y luego usarlo como un índice de matriz.

Taylor Price
fuente
0

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

Seré convertido a un entero sin signo.

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

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

Las reglas están escritas en lenguaje estándar 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 número entero sin signo que contiene un número de complemento a 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 en este ejemplo se desbordará a un número positivo enorme, podría devolverlo a un int y obtener el resultado real).

Mientras que las conversiones de firmado a sin signo 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 el número de complemento de 2 almacenado en un entero sin signo de nuevo a un entero con signo . Espero que solo encuentre cualquier otro comportamiento en sistemas oscuros que no usan 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

lavado
fuente
-17

Respuestas horribles en abundancia

Ozgur Ozcitak

Cuando se convierte de firmado a no firmado (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 está completamente mal.

Mats Fredriksson

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

Esto también está mal. Los ints sin signo pueden promoverse a ints 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 int se convierta en un int sin signo.

Incorrecto. Tal vez sí y tal vez no.

La conversión de int sin signo a int con signo depende de la implementación. (Pero probablemente funcione de la manera que espera 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 int sin signo ...

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

Taylor Price

Como se respondió anteriormente, puede emitir de un lado a otro entre firmado 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á promovido a int firmado y obtendrá el valor -4444 de la expresión (u + i). Ahora, si tu y yo tenemos otros valores, puedes obtener un desbordamiento y un comportamiento indefinido, pero con esos números exactos obtendrás -4444 [1] . Este valor tendrá el tipo int. Pero está tratando de almacenar ese valor en un int sin signo para que luego se convierta en un int sin signo y el valor que terminará teniendo sería (UINT_MAX + 1) - 4444.

En caso de que la precisión de unsigned int sea mayor que la de int, se promoverá a unsigned int a unsigned int produciendo el valor (UINT_MAX + 1) - 5678 que se agregará a la otra unsigned int 1234. Si tu y yo tenemos 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 NO se encuentre dentro del rango {0..UINT_MAX) y no se producirá un 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. Unsigned char está garantizado además de no tener bits de relleno. El número de bits de valores que tiene un entero es la precisión que tiene.

[Gotchas]

El tamaño de macro de la macro por sí solo no se puede utilizar para determinar la precisión de un entero si hay 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. Antes de la adición (durante la promoción): cuando tiene 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 signo estaba dentro del rango de un int, después de la adición, el resultado puede desbordarse.

Elite Mx
fuente
66
"Los ints sin firmar pueden ser promovidos a ints". No es verdad. No se produce una promoción de enteros ya que los tipos ya están clasificados> = 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 lo hay". 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 entero sin signo tipo." ambos garantizan que intse convierte unsigned intcuando se aplican las conversiones aritméticas habituales.
CB Bailey
1
6.3.1.8 Ocurre solo después de la promoción de enteros. El párrafo inicial dice "De lo contrario, las promociones enteras se realizan en ambos operandos. ENTONCES las siguientes reglas se aplican a los operandos promocionados". Entonces, lea 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 menor o IGUAL al rango de int y unsigned int" y "Si un int puede representar todos los valores del tipo original, el valor se convierte en un int ".
Elite Mx
1
6.3.1.1 promoción entero utilizado utiliza para convertir algunos tipos de enteros que no son into unsigned inta uno de esos tipos que algo del tipo unsigned into intse espera. El "o igual" se agregó en TC2 para permitir que los tipos enumerados de rango de conversión sean iguales into unsigned intse conviertan a uno de esos tipos. Nunca se pretendió que la promoción descrita se convirtiera entre unsigned inty int. La determinación de tipo común entre unsigned inty inttodavía se rige por 6.3.1.8, incluso después de TC2.
CB Bailey
19
La publicación de las respuestas incorrectas, mientras que las respuestas incorrectas que critican los demás no suena como una estrategia buena para conseguir el trabajo ... ;-)
R .. GitHub dejar de ayudar a ICE
66
No estoy votando para eliminar ya que este nivel de error combinado con arrogancia es demasiado entretenido
MM