rotacion representarse representar pueden numeros mascaras manipulacion manejo hacer ejemplos desplazamiento cuantos con como binario c++ gcc undefined-behavior

c++ - representarse - manipulacion de bits pdf



¿Cómo obtuve un valor de más de 8 bits de un entero de 8 bits? (9)

Creo que esto está haciendo por la optimización del código:

for (int32_t i = 0; i <= 300; i++) printf("c: %i/n", c--);

El compilador usa la variable int32_t i tanto para i como para c . Desactive la optimización o realice una printf("c: %i/n", (int8_t)c--); directa printf("c: %i/n", (int8_t)c--);

Localicé un insecto extremadamente desagradable escondido detrás de esta pequeña joya. Soy consciente de que según la especificación de C ++, los desbordamientos firmados son un comportamiento indefinido, pero solo cuando se produce el desbordamiento cuando el valor se amplía al ancho de bits sizeof(int) . Tal como lo entiendo, incrementar un char no debería ser un comportamiento indefinido siempre que sizeof(char) < sizeof(int) . Pero eso no explica cómo c está obteniendo un valor imposible . Como un entero de 8 bits, ¿cómo puede c mantener valores mayores que su ancho de bit?

Código

// Compiled with gcc-4.7.2 #include <cstdio> #include <stdint.h> #include <climits> int main() { int8_t c = 0; printf("SCHAR_MIN: %i/n", SCHAR_MIN); printf("SCHAR_MAX: %i/n", SCHAR_MAX); for (int32_t i = 0; i <= 300; i++) printf("c: %i/n", c--); printf("c: %i/n", c); return 0; }

Salida

SCHAR_MIN: -128 SCHAR_MAX: 127 c: 0 c: -1 c: -2 c: -3 ... c: -127 c: -128 // <= The next value should still be an 8-bit value. c: -129 // <= What? That''s more than 8 bits! c: -130 // <= Uh... c: -131 ... c: -297 c: -298 // <= Getting ridiculous now. c: -299 c: -300 c: -45 // <= ..........

Compruébalo en ideone.


Creo que sucedió porque tu ciclo continuará hasta que int se convierta en 300 y c se convierta en -300. Y el último valor es porque

printf("c: %i/n", c);


El código ensamblador revela el problema:

:loop mov esi, ebx xor eax, eax mov edi, OFFSET FLAT:.LC2 ;"c: %i/n" sub ebx, 1 call printf cmp ebx, -301 jne loop mov esi, -45 mov edi, OFFSET FLAT:.LC2 ;"c: %i/n" xor eax, eax call printf

EBX se debe anadir con decremento post FF, o solo se debe usar BL con el resto de EBX clear. Es curioso que use sub en lugar de dec. El -45 es completamente misterioso. Es la inversión en bits de 300 y 255 = 44. -45 = ~ 44. Hay una conexión en alguna parte.

Pasa mucho más trabajo usando c = c - 1:

mov eax, ebx mov edi, OFFSET FLAT:.LC2 ;"c: %i/n" add ebx, 1 not eax movsx ebp, al ;uses only the lower 8 bits xor eax, eax mov esi, ebp

A continuación, utiliza solo la parte baja de RAX, por lo que está restringido a -128 a 127. Opciones del compilador "-g -O2".

Sin optimización, produce el código correcto:

movzx eax, BYTE PTR [rbp-1] sub eax, 1 mov BYTE PTR [rbp-1], al movsx edx, BYTE PTR [rbp-1] mov eax, OFFSET FLAT:.LC2 ;"c: %i/n" mov esi, edx

Entonces es un error en el optimizador.


Supongo que el hardware subyacente todavía está usando un registro de 32 bits para mantener ese int8_t. Dado que la especificación no impone un comportamiento para el desbordamiento, la implementación no verifica el desbordamiento y permite que también se almacenen valores más grandes.

Si marca la variable local como volatile , está forzando a usar memoria para ella y, en consecuencia, obtendrá los valores esperados dentro del rango.


Un compilador puede tener errores que no sean conformes al estándar, porque existen otros requisitos. Un compilador debería ser compatible con otras versiones de sí mismo. También se puede esperar que sea compatible de alguna manera con otros compiladores, y también para ajustarse a algunas creencias sobre el comportamiento que están en manos de la mayoría de su base de usuarios.

En este caso, parece ser un error de conformidad. La expresión c-- debería manipular c de una manera similar a c = c - 1 . Aquí, el valor de c a la derecha se promueve a tipo int , y luego se lleva a cabo la resta. Como c está en el rango de int8_t , esta resta no se desbordará, pero puede producir un valor que está fuera del rango de int8_t . Cuando se asigna este valor, se lleva a cabo una conversión al tipo int8_t para que el resultado vuelva a c . En el caso fuera de rango, la conversión tiene un valor definido por la implementación. Pero un valor fuera del rango de int8_t no es un valor válido definido por la implementación. Una implementación no puede "definir" que un tipo de 8 bits de repente contiene 9 o más bits. Para el valor que se definirá la implementación, significa que se produce algo en el rango de int8_t y el programa continúa. Por lo tanto, el estándar C permite comportamientos tales como la aritmética de saturación (común en DSP) o el envolvente (arquitecturas convencionales).

El compilador utiliza un tipo de máquina subyacente más amplio cuando manipula valores de tipos enteros pequeños como int8_t o char . Cuando se realiza la aritmética, los resultados que están fuera del rango del tipo entero pequeño se pueden capturar de manera confiable en este tipo más amplio. Para preservar el comportamiento externo visible de que la variable es un tipo de 8 bits, el resultado más amplio debe truncarse en el rango de 8 bits. Se requiere un código explícito para hacerlo, ya que las ubicaciones de almacenamiento de la máquina (registros) son más anchos que 8 bits y están contentos con los valores más grandes. Aquí, el compilador omitió normalizar el valor y simplemente lo pasó a printf como está. El especificador de conversión %i en printf no tiene idea de que el argumento originalmente provino de cálculos int8_t ; solo está trabajando con un argumento int .


Use %hhd lugar de %i ! Debería resolver su problema.

Lo que ves es el resultado de las optimizaciones del compilador combinadas con que le pidas a printf que imprima un número de 32 bits y luego inserte un número (supuestamente de 8 bits) en la pila, que es realmente del tamaño de un puntero, porque así funciona el opcode push en x86.


Este es un error del compilador.

Aunque obtener resultados imposibles para el comportamiento indefinido es una consecuencia válida, en realidad no hay un comportamiento indefinido en su código. Lo que sucede es que el compilador cree que el comportamiento no está definido y lo optimiza en consecuencia.

Si c se define como int8_t , e int8_t promueve a int , entonces se supone que c-- realiza la resta c - 1 en aritmética int y convierte el resultado de nuevo en int8_t . La resta en int no se desborda, y la conversión de valores integrales fuera de rango a otro tipo integral es válida. Si el tipo de destino está firmado, el resultado está definido por la implementación, pero debe ser un valor válido para el tipo de destino. (Y si el tipo de destino no está firmado, el resultado está bien definido, pero eso no se aplica aquí).


c se define a sí mismo como int8_t , pero cuando se opera ++ o -- sobre int8_t se convierte implícitamente primero a int y el resultado de la operación en cambio el valor interno de c se imprime con printf que pasa a ser int .

Vea el valor real de c después de todo el ciclo, especialmente después de la última disminución

-301 + 256 = -45 (since it revolved entire 8 bit range once)

es el valor correcto que se asemeja al comportamiento -128 + 1 = 127

c comienza a usar la memoria de tamaño int pero se imprime como int8_t cuando se imprime como si solo con 8 bits . Utiliza los 32 bits cuando se usa como int

[Error del compilador]


No puedo incluir esto en un comentario, así que lo estoy publicando como respuesta.

Por alguna extraña razón, el operador es el culpable.

Probé el código publicado en Ideone y reemplacé c-- con c = c - 1 y los valores se mantuvieron dentro del rango [-128 ... 127]:

c: -123 c: -124 c: -125 c: -126 c: -127 c: -128 // about to overflow c: 127 // woop c: 126 c: 125 c: 124 c: 123 c: 122

Freaky ey? No sé mucho sobre lo que hace el compilador a expresiones como i++ o i-- . Es probable que promocione el valor de retorno a un int y lo pase. Esa es la única conclusión lógica que se me ocurre porque, de hecho, obtienes valores que no caben en 8 bits.