2010-08-03 9 views
21

Recientemente tuve un comportamiento extraño al usar el operador de desplazamiento a la derecha.Comportamiento extraño del operador de desplazamiento a la derecha (1 >> 32)

El siguiente programa:

#include <cstdio> 
#include <cstdlib> 
#include <iostream> 
#include <stdint.h> 

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 << "1 >> 32: " << (1 >> 32) << std::endl; //warning here 
    std::cout << "(int)1 >> (int)32: " << ((int)1 >> (int)32) << std::endl; //warning here 

    return EXIT_SUCCESS; 
} 

Salidas:

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é ocurre 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 compilación. ¿Y por qué "funciona" si uso un entero de 64 bits?

¡Todas las luces con respecto a esto serán muy apreciadas!


duda relacionada, aquí 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 
+1

Me pregunto, ¿por qué la "pregunta no es verdadera" cierra la votación? – ereOn

+1

pregunta muy interesante ... incluso pensé que rotar por un gran número resultaría en 0. Nunca supe que era UB. – Naveen

+3

odiadores que odien. :) Es la única explicación para downvotes drive-by también. – tzaman

Respuesta

35

Es probable que la CPU es en realidad el cálculo de

a >> (b % 32) 

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

Dado que la norma (C++ 98 § 5,8/1) establece que

el comportamiento no está definido si el operando de la derecha es negativo, o mayor que o igual a la longitud en bits del operando de la izquierda promovido.

no existe contradicción con foo(1,32) y 1>>32 dar resultados diferentes.

 

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

bar(1, 64); 

todavía se puede conseguir 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 destino puede ser un registro o una posició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 utiliza el modo de 64 bits y REX.W). Se proporciona una codificación de código de operación especial para un recuento de 1.

Sin embargo, en ARM (ARMv6 & 7, por lo menos), la lógica de cambio derecho (LSR) se implementa como (Armisa Página A2-6)

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

donde (Armisa Página AppxB- 13)

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

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

Esto muestra que desplazar un entero de 32 bits por ≥32 no es portátil.

+1

Gracias. Siempre pensé que cambiar "demasiado" resultó en cero el valor, no en UB. Entonces, supongo que usar un valor de 64 bits aquí (con '32' como el operando de desplazamiento a la derecha) ¿es un comportamiento correcto y definido? – ereOn

+0

@ereOn: Correcto. – kennytm

+3

'b% 32' parece correcto; Intenté un 'foo (16, 33)' y obtuve '8' como resultado. Buena investigación! – tzaman

0

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

+0

La firma es una cuestión de interpretación en el nivel del idioma. La CPU "ve" bits, no valores o signos. – DevSolar

6

OK. Por lo tanto, está en 5.8.1:

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

Tiene un Comportamiento indefinido (tm).

0

¡La advertencia lo dice todo!

Pero, para ser justos, me picó el mismo error una vez.

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

es completamente indefinido. De hecho, el compilador generalmente da resultados diferentes a los del tiempo de ejecución en mi experiencia. Lo que quiero decir con esto es que si el compilador 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.

1

Lo compilé en ventanas de 32 bits utilizando el compilador VC9. Me dio el siguiente warning. Dado que sizeof(int) tiene 4 bytes en mi sistema, el compilador 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).

+0

indefinido! = Impredecible. * Puede * significar eso, pero no es necesariamente así. –

+0

@Nathan Sin embargo, por razones prácticas indefinido en general se debe tratar como impredecible. De lo contrario, uno podría acoplar el código a un entorno de compilación/tiempo de ejecución muy específico. – foraidt

+0

Es por eso que tanto Intel como Microsoft tienen tantos problemas con la compatibilidad con versiones anteriores. El software a menudo (o con la frecuencia suficiente) hace algo indefinido, solo para descubrir que se rompe en una CPU o SO futuro. Los usuarios no saben que el software está equivocado y asumen que Intel o Microsoft lo rompieron, lo que provocó una mala impresión. Microsoft e Intel hacen todo lo posible para no romper el código heredado, incluso si está mal escrito. –

-5

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 compilador. 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)

+4

'>>' no es _ un cambio rotativo, de lo contrario '1 >> 1' arrojaría' INT_MIN', lo que probablemente no ocurra. El problema aquí es que cambiar por tantos o más bits como los hay en un tipo es U.B. Que en este caso se manifestó como resultado equivalente a un cambio rotativo es una coincidencia. –

3

Lo que sucede en foo es que la anchura de cambio es mayor que o igual que el tamaño de los datos que están siendo desplazado. 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++.

El motivo es permitir que los diseñadores de compiladores aprovechen cualquier soporte de hardware de CPU para turnos. Por ejemplo, la arquitectura 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 compilador 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 cambiar por 32 es lo mismo que cambiar por 0.

Cuestiones relacionadas