¿Explicación envolvente para variables firmadas y sin firmar en C?

8 minutos de lectura

avatar de usuario
orustammanapov

Leí un poco en la especificación C que las variables sin firmar (en particular corto sin firmar int) realizar algunos de los llamados envolver alrededor en el desbordamiento de enteros, aunque no pude encontrar nada en las variables firmadas, excepto que me fui con comportamiento indefinido.

Mi profesor me dijo que sus valores también se envuelven (tal vez solo se refería a gcc). ¡Pensé que los bits simplemente se truncaban y los bits que dejé me dieron un valor extraño!

Qué es wrap around y en qué se diferencia de simplemente truncar bits.

  • Su profesor está equivocado a menos que su declaración de que los valores se ajustan se calificó para implementaciones específicas de C, posiblemente con indicadores específicos con respecto a la optimización o la semántica.

    –Eric Postpischil

    7 de noviembre de 2013 a las 17:31

  • @EricPostpischil, ¿qué quiere decir con la implementación específica de c y cómo es en general?

    – orustammanapov

    7 de noviembre de 2013 a las 17:47


  • El estándar C no define qué sucede cuando se produce un desbordamiento de enteros. Sin embargo, una implementación de C puede definir lo que sucede. Se permite que las implementaciones de C vayan más allá del estándar C. Entonces, es posible, por ejemplo, que GCC pueda emitir una declaración que diga “Cuando ocurre un desbordamiento de enteros y el -fSomething switch se usa al compilar, luego el resultado se envuelve en complemento a dos “. O, si GCC no establece esto, entonces Fred Doe puede verificar las fuentes de GCC, modificarlas como desee y emitir una nueva versión específica de Fred de GCC que se comporte de una manera particular.

    –Eric Postpischil

    7 de noviembre de 2013 a las 18:03


avatar de usuario
Hormiga

Las variables enteras con signo no tienen un comportamiento envolvente en el lenguaje C. El desbordamiento de enteros con signo durante los cálculos aritméticos produce comportamiento indefinido. Tenga en cuenta, por cierto, que el compilador GCC que mencionó es conocido por implementar semántica de desbordamiento estricta en las optimizaciones, lo que significa que aprovecha la libertad proporcionada por tales situaciones de comportamiento indefinido: el compilador GCC asume que los valores enteros con signo nunca se ajustan. Eso significa que GCC en realidad es uno de los compiladores en los que no poder confiar en el comportamiento envolvente de los tipos enteros con signo.

Por ejemplo, el compilador GCC puede suponer que para la variable int i la siguiente condición

if (i > 0 && i + 1 > 0)

equivale a un mero

if (i > 0)

Esto es exactamente lo que semántica de desbordamiento estricta medio.

Los tipos enteros sin signo implementan aritmética de módulo. el modulo es igual 2^N donde N es el número de bits en la representación de valor del tipo. Por esta razón, los tipos enteros sin signo parecen envolverse en el desbordamiento.

Sin embargo, el lenguaje C nunca realiza cálculos aritméticos en dominios más pequeños que el de int/unsigned int. Escribe unsigned short int que mencione en su pregunta generalmente se promoverá para escribir int en expresiones antes de que comiencen los cálculos (suponiendo que el rango de unsigned short encaja en el rango de int). Lo que significa que 1) los cálculos con unsigned short int se realizará en el dominio de intcon desbordamiento ocurriendo cuando int desbordamientos, 2) el desbordamiento durante dichos cálculos conducirá a un comportamiento indefinido, no a un comportamiento envolvente.

Por ejemplo, este código produce una envoltura

unsigned i = USHRT_MAX;
i *= INT_MAX; /* <- unsigned arithmetic, overflows, wraps around */

mientras que este código

unsigned short i = USHRT_MAX;
i *= INT_MAX; /* <- signed arithmetic, overflows, produces undefined behavior */

conduce a un comportamiento indefinido.

Si no int ocurre un desbordamiento y el resultado se vuelve a convertir en un unsigned short int tipo, se reduce de nuevo por módulo 2^Nque aparecerá como si el valor se hubiera envuelto.

  • ¿Qué significa “i sin firmar”? Lo has explicado muy bien, podrías mostrarlo en este ejemplo: short int y = 511, z = 512; y*=z;

    – orustammanapov

    7 de noviembre de 2013 a las 20:57


  • @orustammanapov: unsigned i representa unsigned int ial igual que long i representa long int i. int está implícito en tales contextos. En cuanto a tu ejemplo, y*=z se interpreta como y = (short) ((int) y * (int) z)que se reduce a y = (short) 261632. Tenga en cuenta que en este caso no hay desbordamiento durante las evaluaciones aritméticas (que encajan en int perfectamente bien), pero hay un desbordamiento durante la conversión de nuevo a short. El comportamiento en este caso es dependiente de la implementación.

    – Ant

    7 de noviembre de 2013 a las 21:05


  • Me pregunto cuánta ventaja en el mundo real hay para hacer que el desbordamiento de enteros UB sin restricciones en lugar de decir que los compiladores pueden incluir precisión adicional en los cálculos que involucran tipos enteros, pueden reescribir arbitrariamente cualquiera o todos los bits de exceso de precisión cada vez que no todos están de acuerdo con el bit de signo, y pueden en su tiempo libre almacenar tal precisión adicional en variables enteras no volátiles? Tales reglas permitirían a un compilador considerar i+1 > i como incondicionalmente cierto, pero no permitiría a los compiladores negar la causalidad. ¿Cuánta ventaja viene de ir más allá de eso?

    – Super gato

    14 mayo 2015 a las 19:45


avatar de usuario
Juan Bode

Imagine que tiene un tipo de datos de solo 3 bits de ancho. Esto le permite representar 8 valores distintos, del 0 al 7. Si agrega 1 a 7, volverá a 0, porque no tiene suficientes bits para representar el valor 8 (1000).

Este comportamiento está bien definido para tipos sin firmar. Está no bien definido para tipos con signo, porque existen varios métodos para representar valores con signo, y el resultado de un desbordamiento se interpretará de forma diferente en función de ese método.

Signo-magnitud: el bit superior representa el signo; 0 para positivo, 1 para negativo. Si mi tipo tiene tres bits de ancho nuevamente, entonces puedo representar valores con signo de la siguiente manera:

000  =  0
001  =  1
010  =  2
011  =  3
100  = -0
101  = -1
110  = -2
111  = -3

Dado que se toma un bit para el signo, solo tengo dos bits para codificar un valor de 0 a 3. Si sumo 1 a 3, me desbordaré con -0 como resultado. Sí, hay dos representaciones para 0, una positiva y otra negativa. No encontrará representaciones de magnitud de signo con tanta frecuencia.

Complemento a uno: el valor negativo es el inverso bit a bit del valor positivo. Nuevamente, usando el tipo de tres bits:

000  =  0
001  =  1
010  =  2
011  =  3
100  = -3
101  = -2
110  = -1 
111  = -0

Tengo tres bits para codificar mis valores, pero el rango es [-3, 3]. Si sumo 1 a 3, me desbordaré con -3 como resultado. Esto es diferente del resultado de signo-magnitud anterior. Nuevamente, hay dos codificaciones para 0 usando este método.

Complemento a dos: el valor negativo es el inverso bit a bit del valor positivo, más 1. En el sistema de tres bits:

000  =  0
001  =  1
010  =  2
011  =  3
100  = -4
101  = -3
110  = -2
111  = -1

Si sumo 1 a 3, obtendré un desbordamiento de -4 como resultado, que es diferente de los dos métodos anteriores. Tenga en cuenta que tenemos un rango de valores ligeramente mayor [-4, 3] y sólo una representación para 0.

El complemento a dos es probablemente el método más común para representar valores con signo, pero no es el único, por lo tanto, el estándar C no puede garantizar lo que sucederá cuando se desborde un tipo entero con signo. Así que deja el comportamiento indefinido por lo que el compilador no tiene que lidiar con la interpretación de múltiples representaciones.

  • Las diferentes representaciones con signo fueron la razón original para dejar indefinido el desbordamiento aritmético con signo. Ahora, los compiladores han comenzado a aprovechar el comportamiento indefinido para la optimización, por lo que no es seguro confiar en el desbordamiento firmado que produce resultados de complemento a dos, incluso si sabe que su objetivo es una arquitectura de complemento a dos. Ver por ejemplo airs.com/blog/archives/120

    – Pascal Cuoq

    7 de noviembre de 2013 a las 17:58


avatar de usuario
diapiro

los comportamiento indefinido proviene de los primeros problemas de portabilidad cuando los tipos enteros con signo se podían representar como signo y magnitud, complemento a uno o complemento a dos.

Hoy en día, todas las arquitecturas representan números enteros como complemento a dos que se envuelven. Pero tenga cuidado: dado que su compilador tiene razón al asumir que no ejecutará un comportamiento indefinido, es posible que encuentre errores extraños cuando la optimización está activada.

  • ¿Significa que obtengo un ajuste incluso con enteros con signo?

    – orustammanapov

    7 de noviembre de 2013 a las 17:46

  • @orustammanapov: No. Incluso si el hardware subyacente tiene algún comportamiento intrínseco sobre cómo se manejan los desbordamientos o qué tipo de ajuste ocurre, no se requiere la implementación de C para usarlo. El estándar C dice que el comportamiento ante el desbordamiento de enteros con signo no está definido. Esto significa que la implementación de C puede optimizar el código sin tener en cuenta lo que sucede en caso de desbordamiento. Esto puede provocar que se produzcan comportamientos como si los valores se envolvieran, o podría provocar que se produjeran comportamientos como si se hubiera producido una trampa o puede producir valores diferentes de los que podría producir el encapsulado.

    –Eric Postpischil

    7 de noviembre de 2013 a las 17:54


En un entero de 8 bits con signo, la definición intuitiva de ajuste podría parecer que va de +127 a -128, en binario complemento a dos: 0111111 (127) y 1000000 (-128). Como puede ver, ese es el progreso natural de incrementar los datos binarios, sin considerar que representan un número entero, con o sin signo. En contra de la intuición, el desbordamiento real tiene lugar cuando se pasa de -1 (11111111) a 0 (00000000) en el sentido de ajuste del entero sin signo.

Esto no responde a la pregunta más profunda de cuál es el comportamiento correcto cuando un entero con signo se desborda porque no hay un comportamiento “correcto” de acuerdo con el estándar.

¿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