Comportamiento extraño del operador de desplazamiento a la derecha (1 >> 32)

Recientemente me enfrenté a un comportamiento extraño usando el operador de desplazamiento a la derecha.

El siguiente progtwig:

#include  #include  #include  #include  int foo(int a, int b) { return a >> b; } int bar(uint64_t a, int b) { return a >> b; } int main(int argc, char** argv) { std::cout << "foo(1, 32): " << foo(1, 32) << std::endl; std::cout << "bar(1, 32): " << bar(1, 32) << std::endl; std::cout <> 32: " <> 32) << std::endl; //warning here std::cout <> (int)32: " <> (int)32) << std::endl; //warning here return EXIT_SUCCESS; } 

Productos:

 foo(1, 32): 1 // Should be 0 (but I guess I'm missing something) bar(1, 32): 0 1 >> 32: 0 (int)1 >> (int)32: 0 

¿Qué pasa con la función foo() ? Entiendo que la única diferencia entre lo que hace y las últimas 2 líneas es que las últimas dos líneas se evalúan en tiempo de comstackción. ¿Y por qué “funciona” si uso un entero de 64 bits?

Cualquier luz con respecto a esto será muy apreciada!


Seguramente relacionado, esto es lo que da g++ :

 > g++ -o test test.cpp test.cpp: In function 'int main(int, char**)': test.cpp:20:36: warning: right shift count >= width of type test.cpp:21:56: warning: right shift count >= width of type 

Es probable que la CPU esté realmente computando

 a >> (b % 32) 

en foo ; Mientras tanto, el 1 >> 32 es una expresión constante, por lo que el comstackdor doblará la constante en tiempo de comstackción, que de alguna manera da 0.

Dado que el estándar (C ++ 98 §5.8 / 1) establece que

El comportamiento no está definido si el operando derecho es negativo, o mayor o igual que la longitud en bits del operando izquierdo promovido.

no hay contradicción al tener foo(1,32) y 1>>32 dando diferentes resultados.

Por otro lado, en la bar proporcionó un valor sin signo de 64 bits, ya que 64> 32 garantiza que el resultado debe ser 1/2 32 = 0. Sin embargo, si escribe

 bar(1, 64); 

aún puede obtener 1.


Editar: El desplazamiento lógico a la derecha (SHR) se comporta como a >> (b % 32/64) en x86 / x86-64 (Intel # 253667, Página 4-404):

El operando de destino puede ser un registro o una ubicación de memoria. El operando de recuento puede ser un valor inmediato o el registro CL. El recuento se enmascara a 5 bits (o 6 bits si está en modo de 64 bits y se utiliza REX.W). El rango de conteo está limitado a 0 a 31 (o 63 si se usa el modo de 64 bits y REX.W). Se proporciona una encoding de código de operación especial para un recuento de 1.

Sin embargo, en ARM (armv6 y 7, al menos), el desplazamiento a la derecha lógico (LSR) se implementa como (Página A2-6 de ARMISA)

 (bits(N), bit) LSR_C(bits(N) x, integer shift) assert shift > 0; extended_x = ZeroExtend(x, shift+N); result = extended_x; carry_out = extended_x; return (result, carry_out); 

donde (Página ARMISA AppxB-13)

 ZeroExtend(x,i) = Replicate('0', i-Len(x)) : x 

Esto garantiza un desplazamiento correcto de ≥32 producirá cero. Por ejemplo, cuando este código se ejecuta en el iPhone, foo(1,32) dará 0.

Estos muestran que cambiar un entero de 32 bits por ≥32 no es portátil.

DE ACUERDO. Entonces está en 5.8.1:

Los operandos serán de tipo integral o de enumeración y se realizarán promociones integrales. El tipo de resultado es el del operando izquierdo promovido. El comportamiento no está definido si el operando derecho es negativo, o mayor o igual que la longitud en bits del operando izquierdo promovido.

Entonces tienes un Comportamiento Indefinido ™.

Lo que sucede en foo es que el ancho del desplazamiento es mayor o igual que el tamaño de los datos que se desplazan. En el estándar C99 que da como resultado un comportamiento indefinido. Probablemente sea el mismo en cualquier estándar de C ++ en el que se haya creado MS VC ++.

La razón de esto es permitir que los diseñadores de comstackdores aprovechen cualquier soporte de hardware de CPU para turnos. Por ejemplo, la architecture i386 tiene una instrucción para cambiar una palabra de 32 bits por un número de bits, pero el número de bits se define en un campo en la instrucción que tiene 5 bits de ancho. Lo más probable es que el comstackdor genere la instrucción tomando la cantidad de cambio de bit y enmascarándola con 0x1F para obtener el cambio de bit en la instrucción. Esto significa que desplazar por 32 es lo mismo que cambiar por 0.

Lo compilé en ventanas de 32 bits usando el comstackdor VC9. Me dio la siguiente advertencia . Como sizeof(int) tiene 4 bytes en mi sistema, el comstackdor indica que el desplazamiento a la derecha por 32 bits da como resultado un comportamiento indefinido. Como no está definido, no puedes predecir el resultado. Solo para revisar, cambié a la derecha con 31 bits y todas las advertencias desaparecieron y el resultado también fue el esperado (es decir, 0).

Supongo que la razón es que el tipo int contiene 32 bits (para la mayoría de los sistemas), pero se usa un bit para firmar, ya que es un tipo firmado. Entonces solo se usan 31 bits para el valor real.

¡La advertencia lo dice todo!

Pero para ser justos una vez fui mordido por el mismo error.

 int a = 1; cout << ( a >> 32); 

está completamente indefinido De hecho, el comstackdor generalmente da resultados diferentes a los del tiempo de ejecución en mi experiencia. Lo que quiero decir con esto es que si el comstackdor puede ver para evaluar la expresión de cambio en tiempo de ejecución, puede darle un resultado diferente a la expresión evaluada en tiempo de ejecución.

foo (1,32) realiza una rotación de mierda, por lo que los bits que deberían desaparecer a la derecha vuelven a aparecer a la izquierda. Si lo hace 32 veces, el único bit configurado en 1 vuelve a su posición original.

bar (1,32) es lo mismo, pero el bit está en 64-32 + 1 = 33th bit, que está por encima de los números representables para un int de 32 bits. Solo se toman los 32 bits más bajos, y todos son 0.

1 >> 32 es realizado por el comstackdor. No tengo idea de por qué gcc utiliza un cambio no giratorio aquí y no en el código generado.

Lo mismo para ((int) 1 >> (int) 32)

    Intereting Posts