Pregunta

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í? ¿Este código es seguro para todos los valores de u y i?(Seguro, en el sentido de que aunque resultado en este ejemplo se desbordará a un número positivo enorme, podría volver a convertirlo en un En t y obtener el resultado real.)

¿Fue útil?

Solución

Respuesta corta

Su i será convertido a un entero sin signo sumando UINT_MAX + 1, entonces la suma se realizará con los valores sin signo, dando como resultado una gran result (dependiendo de los valores de u y i).

Respuesta larga

Según la Norma C99:

6.3.1.8 Conversiones aritméticas habituales

  1. Si ambos operandos tienen el mismo tipo, no se necesita ninguna conversión adicional.
  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 un tipo entero con signo se convierte al tipo del operando con un 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 entero sin signo correspondiente al tipo del operando con tipo entero con signo.

En su caso, tenemos un int sin firmar (u) y firmado int (i).En referencia al punto (3) anterior, dado que ambos operandos tienen el mismo rango, su i tendrá que ser convertido a un entero sin signo.

6.3.1.3 Enteros con y sin signo

  1. Cuando un valor con tipo entero se convierte a otro tipo entero distinto de _Bool, si el valor puede representarse mediante el nuevo tipo, no se modifica.
  2. De lo contrario, si el nuevo tipo no está firmado, el valor se convierte sumando 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 está firmado y el valor no se puede representar en él;o el resultado está definido por la implementación o se genera una señal definida por la implementación.

Ahora debemos referirnos al punto (2) anterior.Su i se convertirá en un valor sin signo agregando UINT_MAX + 1.Entonces el resultado dependerá de cómo UINT_MAX se define 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 representarse mediante el tipo entero sin signo resultante se reduce en módulo el número que es uno mayor que el valor más grande que puede representarse mediante el tipo resultante.

Prima: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 esto en línea: https://repl.it/repls/QuickWhimsicalBytes

Prima:Efecto secundario de la conversión aritmética

Se pueden utilizar reglas de conversión aritmética para obtener el valor de UINT_MAX inicializando un valor sin signo para -1, es decir:

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

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

Otros consejos

La conversión de firmado a no firmado hace no necesariamente simplemente copie o reinterprete la representación del valor firmado.Citando el estándar C (C99 6.3.1.3):

Cuando un valor con el tipo entero se convierte en otro tipo entero que no sea _bool, si el valor puede representarse por el nuevo tipo, no cambia.

De lo contrario, si el nuevo tipo no está firmado, 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 está firmado y el valor no se puede representar en él;O el resultado está definido por implementación o se eleva una señal definida por implementación.

Para la representación en complemento a dos, que es casi universal hoy en día, las reglas corresponden a la reinterpretación de los bits.Pero para otras representaciones (signo y magnitud o complemento a uno), la implementación de C aún debe lograr 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 están definidas para operar sobre valores, no sobre 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 a int sin signo, lo que produce UINT_MAX + 1 - 5678.Este valor luego se suma al valor sin signo 1234, lo que produce UINT_MAX + 1 - 4444.

(A diferencia del desbordamiento sin firmar, el desbordamiento firmado invoca un comportamiento indefinido.El ajuste envolvente es común, pero el estándar C no lo garantiza, y las optimizaciones del compilador pueden causar estragos en el código que hace suposiciones injustificadas).

Refiriéndose a La biblia:

  • Su operación de suma hace que el int se convierta en un int sin signo.
  • Suponiendo una representación en complemento a dos y tipos del mismo tamaño, el patrón de bits no cambia.
  • La conversión de int sin firmar a int con signo depende de la implementación.(Pero probablemente funcione de la manera esperada en la mayoría de las plataformas hoy en día).
  • Las reglas son un poco más complicadas en el caso de combinar con y sin firmar de diferentes tamaños.

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 como resultado un resultado enorme.

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

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.Los números que originalmente eran negativos ahora se interpretarán como números positivos más grandes.

Como se respondió anteriormente, puede alternar entre firmado y sin firmar sin problema.El caso límite para enteros con signo es -1 (0xFFFFFFFF).Intente sumar y restar de eso y encontrará que puede retroceder y hacer que sea correcto.

Sin embargo, si va a realizar transmisiones de un lado a otro, le recomiendo encarecidamente nombrar sus variables de manera que quede claro de qué tipo son, por ejemplo:

int iValue, iResult;
unsigned int uValue, uResult;

Es demasiado fácil distraerse con cuestiones 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 í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 u e i?

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

Las reglas están escritas en un lenguaje estándar 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 del número en complemento a 2.

La suma, la resta y la multiplicación funcionarán correctamente en estos números, lo que dará como resultado otro entero sin signo que contenga un número en complemento a dos que represente 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 en complemento a 2 del "resultado real".

(Es seguro, en el sentido de que, aunque el resultado en este ejemplo se desbordará a un número positivo enorme, podría volver a convertirlo en un int y obtener el resultado real).

Si bien las conversiones de con signo a sin signo están definidas por el estándar, lo contrario 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 en complemento a 2 almacenado en un entero sin signo a un entero con signo. .Espero que solo encuentre otro comportamiento en sistemas oscuros que no usan el complemento a 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

Respuestas horribles en abundancia

Özgur Özcitak

Cuando emite desde Firmado a Unsigned (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 agrega una variable sin firmar y una variable firmada (o cualquier operación binaria), ambos se convierten implícitamente en sin firmar, lo que en este caso daría como resultado un gran resultado.

Esto también está mal.Los enteros sin signo pueden ascender 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 el INT se convierta a un ints firmado.

Equivocado.Tal vez sí y tal vez no.

La conversión de unsigned int to firmed int es dependiente de la implementación.(Pero probablemente funcione de la manera que espera en la mayoría de las plataformas en estos días).

Equivocado.Es un comportamiento indefinido si provoca un desbordamiento o si se conserva el valor.

Anónimo

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

Equivocado.Depende de la precisión de un int en relación con un int sin signo.

Precio de Taylor

Como se respondió anteriormente, puede lanzar de un lado a otro entre firmado y sin firmar sin ningún problema.

Equivocado.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 int sin signo, u será ascendido a int con signo y obtendrá el valor -4444 de la expresión (u+i).Ahora, si usted y yo tenemos otros valores, es posible que obtenga un comportamiento desbordado y no definido, pero con esos números exactos obtendrá -4444 [1].Este valor tendrá tipo int.Pero está intentando almacenar ese valor en un int sin signo para que luego se convierta en un int sin signo y el valor que terminará teniendo el resultado sería (UINT_MAX+1) - 4444.

Si la precisión de un int sin signo es mayor que la de un int, el int con signo se promoverá a un int sin signo que arrojará el valor (UINT_MAX+1) - 5678 que se agregará al otro int sin signo 1234.Si u y i tienen otros valores que hacen que la expresión quede fuera del rango {0..UINT_MAX}, el valor (UINT_MAX+1) se sumará o restará hasta que el resultado SÍ quede dentro del rango {0..UINT_MAX) y no se producirá ningún comportamiento indefinido.

¿Qué es la precisión?

Los números enteros tienen bits de relleno, bits de signo y bits de valor.Los enteros sin signo obviamente no tienen un bit de signo.Además, se garantiza que el carácter sin firmar no tendrá bits de relleno.La cantidad de bits de valores que tiene un número entero es la precisión que tiene.

[Errores]

El tamaño de la macro por sí solo no se puede utilizar para determinar la precisión de un número entero si hay bits de relleno presentes.Y el tamaño de un byte no tiene por qué ser un octeto (ocho bits) como se define en C99.

[1] El desbordamiento puede ocurrir en uno de dos puntos.Ya sea antes de la suma (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 suma, incluso si el int sin signo estaba dentro del rango de un int, después de la suma el resultado aún puede desbordarse.


En una nota no relacionada, soy un estudiante recién graduado que intenta encontrar trabajo;)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top