¿Por qué no deja bit shift, “<<", para enteros de 32 bits que funcionan como se esperaba cuando se usan más de 32 veces?

Cuando escribo el siguiente progtwig y uso el comstackdor GNU C ++, el resultado es 1 que creo que se debe a la operación de rotación realizada por el comstackdor.

 #include  int main() { int a = 1; std::cout << (a << 32) << std::endl; return 0; } 

Pero lógicamente, como se dice que los bits se pierden si desbordan el ancho del bit, la salida debería ser 0. ¿Qué está sucediendo?

El código está en ideone, http://ideone.com/VPTwj .

Esto se debe a una combinación de un comportamiento indefinido en C y al hecho de que el código generado para los procesadores IA-32 tiene una máscara de 5 bits aplicada en el recuento de turnos. Esto significa que en los procesadores IA-32, el rango de un recuento de turnos es 0-31 solamente. 1

Desde el lenguaje de progtwigción C 2

El resultado no está definido si el operando derecho es negativo o mayor o igual que el número de bits en el tipo de expresión de la izquierda.

De IA-32 Intel Architecture Software Manual del desarrollador 3

El 8086 no oculta el recuento de turnos. Sin embargo, todos los demás procesadores IA-32 (comenzando con el procesador Intel 286) enmascaran el recuento de cambios a 5 bits, lo que da como resultado un recuento máximo de 31. Esta máscara se realiza en todos los modos operativos (incluido el modo virtual-8086) para reducir el tiempo máximo de ejecución de las instrucciones.


1 http://codeyarns.com/2004/12/20/c-shift-operator-mayhem/

2 A7.8 Operadores de cambio, Apéndice A. Manual de referencia, El lenguaje de progtwigción C

3 SAL / SAR / SHL / SHR – Shift, Capítulo 4. Referencia del conjunto de instrucciones, IA-32 Intel Architecture Software Manual del desarrollador

En C ++, el cambio solo está bien definido si cambia un valor menos pasos que el tamaño del tipo. Si int es 32 bits, entonces solo 0, e incluyendo, 31 pasos están bien definidos.

Entonces, ¿por qué es esto?

Si echa un vistazo al hardware subyacente que realiza el cambio, si solo tiene que mirar los cinco bits más bajos de un valor (en el caso de 32 bits), puede implementarse utilizando puertas menos lógicas que si tiene que inspeccionar cada bit del valor

Respuesta a pregunta en comentario

C y C ++ están diseñados para ejecutarse lo más rápido posible, en cualquier hardware disponible. Hoy, el código generado es simplemente una instrucción de ” cambio ”, independientemente de cómo el hardware subyacente maneje los valores fuera del rango especificado. Si los idiomas hubieran especificado cómo debería comportarse el cambio, el generado podría tener que verificar que el recuento de turnos esté dentro del rango antes de realizar el cambio. Típicamente, esto daría tres instrucciones (comparar, twig, desplazamiento). (Es cierto que, en este caso, no sería necesario ya que se conoce el recuento de turnos).

Es un comportamiento indefinido de acuerdo con el estándar C ++:

El valor de E1 << E2 es E1 posiciones de bit E2 desplazadas a la izquierda; los bits vacantes están llenos a cero. Si E1 tiene un tipo sin signo, el valor del resultado es E1 × 2 ^ E2, módulo reducido uno más que el valor máximo representable en el tipo de resultado. De lo contrario, si E1 tiene un tipo firmado y un valor no negativo, y E1 × 2 ^ E2 es representable en el tipo de resultado, entonces ese es el valor resultante; de lo contrario, el comportamiento no está definido .

Las respuestas de Lindydancer y 6502 explican por qué (en algunas máquinas) pasa a ser un 1 que se está imprimiendo (aunque el comportamiento de la operación no está definido). Estoy agregando los detalles en caso de que no sean obvios.

Asumo que (como yo) está ejecutando el progtwig en un procesador Intel. GCC genera estas instrucciones de ensamblaje para la operación de cambio:

 movl $32, %ecx sall %cl, %eax 

Sobre el tema de las operaciones de cambio y otras operaciones de cambio, la página 624 en el Manual de referencia del juego de instrucciones dice:

El 8086 no oculta el recuento de turnos. Sin embargo, todos los demás procesadores Intel Architecture (comenzando con el procesador Intel 286) enmascaran el recuento de cambios en cinco bits, lo que da como resultado un recuento máximo de 31. Esta máscara se realiza en todos los modos operativos (incluido el modo virtual-8086) para reducir el tiempo máximo de ejecución de las instrucciones.

Como los 5 bits más bajos de 32 son cero, entonces 1 << 32 es equivalente a 1 << 0 , que es 1 .

Experimentando con números más grandes, predeciríamos que

 cout << (a << 32) << " " << (a << 33) << " " << (a << 34) << "\n"; 

imprimiría 1 2 4 , y de hecho eso es lo que está sucediendo en mi máquina.

No funciona como se esperaba porque esperas demasiado.

En el caso de x86, el hardware no se preocupa por las operaciones de cambio donde el contador es más grande que el tamaño del registro (consulte, por ejemplo, la descripción de la instrucción SHL en la documentación de referencia x86 para obtener una explicación).

El estándar de C ++ no quería imponer un costo adicional al decir qué hacer en estos casos porque el código generado se habría visto obligado a agregar controles y lógica adicionales para cada cambio paramétrico.

Con esta libertad, los implementadores de comstackdores pueden generar solo una instrucción de ensamblaje sin ninguna prueba o twig.

Un enfoque más “útil” y “lógico” habría sido, por ejemplo, tener (x << y) equivalente a (x >> -y) y también el manejo de contadores altos con un comportamiento lógico y consistente.

Sin embargo, esto habría requerido un manejo mucho más lento para el cambio de bit así que la opción fue hacer lo que hace el hardware, dejando a los progtwigdores la necesidad de escribir sus propias funciones para los casos secundarios.

Dado que el hardware diferente hace cosas diferentes en estos casos, lo que el estándar dice es básicamente "Pase lo que pase cuando haces cosas extrañas, no culpes a C ++, es tu culpa" traducido en jerga legal.

Cambiar una variable de 32 bits por 32 o más bits es un comportamiento indefinido y puede hacer que el comstackdor haga que los demonios salgan volando de tu nariz.

En serio, la mayoría de las veces la salida será 0 (si int es 32 bits o menos) ya que está cambiando el 1 hasta que se cae de nuevo y no queda nada más que 0. Pero el comstackdor puede optimizarlo para hacer lo que quiera.

Vea la excelente entrada de blog de LLVM Lo que todo progtwigdor de C debe saber sobre el comportamiento indefinido , una lectura obligada para cada desarrollador de C.

Dado que estás desplazando un bit un int en 32 bits; Obtendrá: warning C4293: '<<' : shift count negative or too big, undefined behavior en VS. Esto significa que estás cambiando más allá del número entero y la respuesta podría ser CUALQUIER COSA, porque es un comportamiento indefinido.

Podrías probar lo siguiente. En realidad, esto da la salida como 0 después de 32 cambios a la izquierda.

 #include #include using namespace std; int main() { int a = 1; a <<= 31; cout << (a <<= 1); return 0; } 

Tuve el mismo problema y esto funcionó para mí:

f = ((larga duración) 1 << (i-1));

Donde puedo ser cualquier número entero mayor a 32 bits. El 1 tiene que ser un entero de 64 bits para que el cambio funcione.