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

11 minutos de lectura

avatar de usuario
cwick

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 gran número positivo, podría devolverlo a un En t y obtener el resultado real.)

avatar de usuario
Özgur Özcitak

Respuesta corta

Su i estarán convertido a un entero sin signo sumando UINT_MAX + 1entonces la suma se llevará a cabo con los valores sin signo, resultando en un 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, entonces no se necesita más conversión.
  2. De lo contrario, si ambos operandos tienen tipos de enteros con signo o ambos tienen tipos de enteros sin signo, el operando con el tipo de menor rango de conversión de enteros se convierte al tipo de 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 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). Refiriéndose a (3) arriba, ya 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 que no sea _Bool, si el valor se puede representar con el nuevo tipo, no se modifica.
  2. De lo contrario, si el nuevo tipo no tiene signo, 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 se firma y el valor no se puede representar en él; el resultado está definido por la implementación o se emite una señal definida por la implementación.

Ahora necesitamos referirnos a (2) arriba. Su i se convertirá en un valor sin signo agregando UINT_MAX + 1. Así que el resultado dependerá de cómo UINT_MAX se define en su implementación. Será grande, pero no rebosará, porque:

6.2.5 (9)

Un cálculo que involucre operandos sin signo nunca puede desbordarse, porque un resultado que no puede ser representado por el tipo entero sin signo resultante 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.

Bonificación: 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;
}

Puedes usar este enlace para probar esto en línea: https://repl.it/repls/QuickWhimsicalBytes

Bonificación: efecto secundario de conversión aritmética

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

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

Se garantiza que esto sea 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 usar -1 para establecer todos los bits en verdadero?

  • No entiendo por qué no puede simplemente hacer un valor absoluto y luego tratarlo como sin signo, al igual que con números positivos.

    – José Salvatierra

    22 de abril de 2013 a las 23:22

  • @ D. Singh, ¿puede señalar amablemente las partes incorrectas dentro de la respuesta?

    – Shmil el gato

    3 de septiembre de 2014 a las 10:27

  • Para convertir firmado a sin firmar, agregamos el valor máximo del valor sin firmar (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 firmar)? Por ejemplo: 140 cuando se convierte en número con signo se convierte en -116. Pero 20 se convierte en 20 mismo. Entonces, ¿algún truco fácil aquí?

    – Jon Wheelock

    18/10/2015 a las 14:01

  • @JonWheelock ver: stackoverflow.com/questions/8317295/…

    – Özgur Özcitak

    19/10/2015 a las 16:55

avatar de usuario
smh

En referencia a El lenguaje de programación C, segunda edición (ISBN 0131103628),

  • 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 de igual 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 en estos días).
  • Las reglas son un poco más complicadas en el caso de combinar firmados y no firmados de diferentes tamaños.

Cuando se agregan una variable sin signo y una 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á.

  • No es verdad. 6.3.1.8 Conversiones aritméticas habituales Si suma un int y un carácter sin signo, este último se convierte en int. Si suma dos caracteres sin firmar, se convierten en int.

    – 2501

    5 mayo 2015 a las 10:44


La conversión de firmado a sin firmar hace no necesariamente basta con copiar o reinterpretar 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 que no sea _Bool, si el valor se puede representar con el nuevo tipo, no se modifica.

De lo contrario, si el nuevo tipo no tiene signo, 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.

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 emite una señal definida por la implementación.

Para la representación del complemento a 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 signo)-1 == UINT_MAX, independientemente de la representación.

En general, las conversiones en C se definen 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 en unsigned int, dando como resultado UINT_MAX + 1 - 5678. Luego, este valor se agrega al valor sin signo 1234, lo que da como resultado UINT_MAX + 1 - 4444.

(A diferencia del desbordamiento sin firmar, el desbordamiento con firma invoca un comportamiento indefinido. El ajuste 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).

Al convertir de firmado a sin firmar, hay dos posibilidades. Los números que originalmente eran positivos siguen siendo (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.

avatar de usuario
CrisF

Respuestas horribles en abundancia

Özgur Özcitak

Cuando conviertes de firmado a sin 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.

Esteras Fredriksson

Cuando se agregan una variable sin signo y una 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.

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

Equivocado. Tal vez sí y tal vez no.

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 en estos días).

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

Anónimo

El valor de i se convierte en int sin signo…

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

precio de taylor

Como se respondió anteriormente, puede pasar 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 por fin puedo responder a la pregunta.

Si la precisión de int es igual a int sin signo, u se promoverá a un int con signo y obtendrá el valor -4444 de la expresión (u+i). Ahora, si usted y yo tuviéramos otros valores, es posible que obtenga un desbordamiento y un comportamiento indefinido, pero con esos números exactos obtendrá -4444 [1]. Este valor tendrá tipo int. Pero está tratando de almacenar ese valor en un int sin firmar para que luego se convierta en un int sin firmar 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. ¿Deberían u y yo tener 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Í esté 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 obviamente no tienen un bit de signo. Además, se garantiza que el carácter sin firmar no tiene bits de relleno. El número de bits de valor que tiene un entero es la precisión que tiene.

[Gotchas]

El tamaño de macro de macro solo no se puede usar 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 que ser un octeto (ocho bits) como lo define C99.

[1] El desbordamiento puede ocurrir en uno de dos puntos. Ya sea 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 aún puede desbordarse.

avatar de usuario
precio de taylor

Como se respondió anteriormente, puede pasar de un lado a otro entre firmado y sin firmar sin ningún 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 lanzar de un lado a otro, le recomiendo encarecidamente que nombre 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 un índice de matriz.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad