2012-01-30 29 views
5

mirada a este código de ejemplo C (extraído de un caso de prueba como ejemplo):No se puede entender el comportamiento del operador de desplazamiento en el código C

main() { 
    unsigned long a, b; 
    int c; 
    c = 32; 
    a = 0xffffffff << 32; 
    b = 0xffffffff << c; 
    printf ("a=%x, b=%x\n", a, b); 
} 

Lienzo: a=0, b=ffffffff

No puedo entender por qué B no es cero, solo como a. Probé esto en Microsoft C y GCC.

actualización: He arreglado el error tipográfico estúpida (debería haber sido < < C y no < < b por supuesto). Pero mi pregunta sigue en pie, p. el resultado sigue siendo el mismo

Respuesta

3

En C es un comportamiento indefinido para desplazamiento a la izquierda más que la anchura del operando promovido.

(C99, 6.5.7p3) "Si el valor del operando derecho es negativo o es mayor o igual que el ancho del operando izquierdo promocionado, el comportamiento no está definido."

I asumen en los ejemplos siguientes int y unsigned int tipos son de 32 bits

El tipo de una constante entera hexadecimal unsuffixed es el primero en la lista correspondiente en el que su valor puede ser representado:. int , unsigned int, long, unsigned long, long long, unsigned long long.

Así que aquí 0xFFFFFFFF es de tipo unsigned int y 0xFFFFFFFF << 32 es un comportamiento indefinido.

+0

Downvoter, ¿podrías explicar por qué bajaste tu voto? – ouah

+0

Ofreces una buena cantidad de detalles para la parte a de la pregunta, pero nada para explicar la parte b. – BitBank

+1

@BitBank porque la parte 'a' y la parte' b' es el mismo problema (se realiza el mismo desplazamiento a la izquierda) con la misma explicación. Ambas expresiones en el RHS de la asignación de 'a' y' b' son un comportamiento indefinido. Como es un comportamiento indefinido, el resultado de 'a' y' b' puede ser el mismo o puede ser diferente o el programa puede 'rm -rf' la partición raíz. Por supuesto, podemos observar diferentes heurísticas de compilación porque en este primer caso se usa una constante como el operando correcto del cambio, pero esto es irrelevante. – ouah

6

Nunca se inicializan b a cualquier cosa antes de usarlo aquí:

b = 0xffffffff << b; 

por lo que puede ser cualquier cosa. (Creo que en realidad quería decir para cambiar por c.)


Aparte de eso:

El principal problema es que el desplazamiento por el # de bits en el tipo de datos o más es comportamiento indefinido.
Así que si el literal 0xffffffff es un entero de 32 bits, entonces la línea:

a = 0xffffffff << 32; 

no está definido por la norma. (Véanse los comentarios.)


También debería estar recibiendo una advertencia del compilador:

warning C4293: '<<' : shift count negative or too big, undefined behavior 
+0

¿por qué * si no está anotado es de solo 32 bits *? si 'unsigned int' es de 32 bits, entonces' 0xffffffff << 32' es un comportamiento indefinido. – ouah

+0

@ouah, ah sí, buen punto. Reparar ... – Mysticial

1

Tienes b = 0xffffffff << b;, pero tal vez te referías b = 0xffffffff << c;

0

Por qué debería ser cero? Lo desplaza por un número de basura (el primer valor de b) que puede ser 0.

0

No inicializó B, por lo que tendrá el valor (aleatorio) en esa ubicación de memoria en particular en el momento el programa se ejecuta

0

Cuando llega a la línea b = 0xffffffff << b;, no se ha asignado el valor de b. Parece que su valor es cero, por lo que la instrucción b = 0xffffffff << b;. Que expande la respuesta dada.

0

La buena práctica es Y después del turno. En este caso, sabes con rigor qué sucede.

examply:

b << = 4; 
b &= 0xfffffff0; 
2

Apuesto a que después de corregir tu error tipográfico obtienes 2 ceros como resultado. He aquí por qué: Si su programa está (apuesto) compilado como versión, tiene las optimizaciones activadas. Esto significa que el compilador utilizará el llamado "plegado constante" y la CPU ni siquiera realizará estos cambios. Debajo del código del cofre habrá 2 constantes cero que se insertarán en la pila e invocarán printf. Los resultados de estas operaciones (turnos) se convertirán esencialmente en constantes en el programa. Así que no hay comportamientos indefinidos etc. - su ayb se convierte en valores constantes y obtendrá algo gustaría:

push 0 
push 0 
push offset to printf format string 
call to printf 
+0

Bueno, obviamente este código es solo una muestra que golpeé para analizar un problema en un código de producción, pero tienes razón: con 'gcc -O3' obtengo dos ceros, y con' gcc -O0' obtengo 0 y 0xffffffff . – haimg

+0

Entonces en el modo de depuración (sin optimizaciones) se obtiene el primer caso 0 (el compilador lo hace constante, por lo tanto 0). El segundo caso muestra que está usando x86 cpu donde el número de bits a desplazar a la izquierda está enmascarado (con 31) así que esencialmente siempre cambia a la izquierda por (operando << (recuento y 0x1F)) y por lo tanto (0xFFFFFFFF << 0) no cambia segundo'. (Puede verificar eso en "Intel Architecture Software Developer's Volume 2: Instruction Set Reference" (información muy útil) – Artur

2

Nadie ha mencionado otro aspecto interesante del problema. En las computadoras x86, la cantidad de cambio se usa módulo 32, por lo que un cambio por 32 realmente será un cambio por 0. En máquinas ARM, el valor de cambio (si viene de un registro) no se altera, por lo que cualquier valor de cambio de 32 o Mayor siempre dará como resultado 0 en el registro de destino.

Específicamente en su problema, el compilador es inteligente acerca de los valores constantes, por lo que convierte (0xffffffff < < 32) en el valor correcto (0). En su segundo ejemplo, el compilador no puede calcular directamente la cantidad de desplazamiento ya que proviene de una variable. Cuando se ejecuta en una máquina Intel, al desplazar a la izquierda en 32 resultados, queda un desplazamiento 0.

+0

Esto es totalmente irrelevante. –

+0

Esta es la respuesta correcta. – xiaokaoy

Cuestiones relacionadas