2012-01-11 23 views
20

Estoy viendo un comportamiento que no espero al compilar este código con diferentes niveles de optimización en gcc.Niveles de optimización en gcc cambiando el comportamiento del programa c

La prueba de función debe llenar un entero sin signo de 64 bits con unos, desplazar los bits de desplazamiento a la izquierda y devolver los 32 bits bajos como un entero sin signo de 32 bits.

Cuando compilo con -O0 obtengo los resultados que espero.

Cuando compilo con -O2 no lo hago, si intento cambiar 32 bits o más.

De hecho, obtengo exactamente los resultados que esperaría si estuviera cambiando un entero de 32 bits por cambios mayores o iguales que el ancho de bits en x86, que es un cambio usando solo los 5 bits bajos del tamaño de desplazamiento .

Pero estoy cambiando un número de 64 bits, por lo que los cambios < 64 deberían ser legales ¿no?

Supongo que es un error en mi comprensión y no en el compilador, pero no he podido descifrarlo.

Mi máquina: gcc (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5 i686-linux-gnu

#include <stdint.h> 
#include <stdio.h> 
#include <inttypes.h> 

uint32_t test(unsigned int shift_size) { 
    uint64_t res = 0; 
    res = ~res; 
    res = res << shift_size; //Shift size < uint64_t width so this should work 
    return res; //Implicit cast to uint32_t 
} 

int main(int argc, char *argv[]) 
{ 
    int dst; 
    sscanf(argv[1], "%d", &dst); //Get arg from outside so optimizer doesn't eat everything 
    printf("%" PRIu32 "l\n", test(dst)); 
    return 0; 
} 

Uso:

$ gcc -Wall -O0 test.c 
$ ./a.out 32 
0l 
$ gcc -Wall -O2 test.c 
$ ./a.out 32 
4294967295l 

gcc -S -Wall -O0 test.c

gcc -S -Wall -O2 test.c

+1

¿Podría también publicar las partes relevantes del código ensamblador generado por su compilador? ('-S' para gcc) –

+0

FWIW da el resultado correcto (' 0l') para mí en todos los niveles de optimización de '-O0' a' -O3' usando gcc 4.2.1, así que sospecho que * might * have encontró un error de gcc. –

+0

hm, el código funciona bien en ambos niveles de optimización para mí ... (gcc versión 4.5.0 20100604, openSUSE 11.3 (x86_64)) – Bort

Respuesta

1

Parece una error. Mi conjetura es que el compilador ha doblado las dos últimas líneas de:

res = res << shift_size 
return (uint32_t)res; 

en:

return ((uint32_t)res) << shift_size; 

Este último está ahora bien definidas de 32 o mayor.

3

Parece que podría ser ser un error del compilador específico de 32 bits para mí. Con el código de la pregunta y gcc 4.2.1 puedo reproducir el error siempre que compile con gcc -m32 -O2 ....

Sin embargo, si añado un printf depuración:

uint32_t test(unsigned int shift_size) { 
    uint64_t res = 0; 
    res = ~res; 
    res = res << shift_size; //Shift size < uint64_t width so this should work 
    printf("res = %llx\n", res); 
    return res; //Implicit cast to uint32_t 
} 

entonces el problema desaparece.

El siguiente paso sería mirar el código generado para tratar de identificar/confirmar el error.

+0

Como una solución temporal, quizás uno podría imprimir a '/ dev/null':' FILE * null; null = fopen ("/ dev/null", "w"); fprintf (null, "res =% llx \ n", res); fclose (null); ' –

+1

Como una solución temporal, hacer' res' 'volátil' funciona igual de bien y no requiere operaciones de archivos. – Fanael

5

"%u" (o "%lu") y uint32_t no son necesariamente compatibles. Trate

#include <inttypes.h> 

    //printf("%ul\n", test(dst)); 
    printf("%" PRIu32 "l\n", test(dst)); 

Impresión de un valor uint32_t con un especificador "%u" (o "%lu") puede invocar un comportamiento indefinido.

+0

Buena llamada, uint32_t no puede estar sin firmar int. – dreamlax

+0

@dreamlax: Creo que el punto es que el OP utilizó "% ul" por error en lugar de "% lu", que termina siendo tomado por printf como "% u". El uso de PRIu32 elimina la posibilidad de utilizar un especificador incorrecto. – tinman

+1

El mismo punto se aplica a ambos '"% u "' y '"% lu "'. – pmg

-2

No recuerdo lo que dice C99, pero parece que en gcc, uint32_t contienen al menos 32 bits, y puede contener más, así que cuando optimizarlo, que utilizan 64 bits var, que es más rápido en Máquina de 64 bits

+2

No, uint32_t, si está disponible, es exactamente 32 bits. (uint_fast32_t o uint_least32_t puede ser más grande, pero no uint32_t) – nos

+0

uint64_t no es en todos los casos más rápido que uint32_t: es más rápido para indexar en matrices iff tiene un espacio de direcciones de 64bit en máquinas intel/amd64, porque los índices de 16 bits y 32 bits tienen que ser convertido mientras que 8bit y 64bit son compensaciones legales en ese lenguaje ensamblador. pero la multiplicación y la división son más lentas si usa tamaños de bits más grandes. – comonad

4

Pude reproducir esto. Aquí está la parte pertinente del código generado con -O2:

movl $-1, %eax 
    movl $-1, %edx 
    sall %cl, %eax 
    xorl %edx, %edx 
    testb   $32, %cl 
    cmovne  %eax, %edx 
    cmovne  %edx, %eax ; This appears to be the instruction in error. 
          ; It looks as though gcc thought that %edx might still 
          ; be zero at this point, because if the shift count is 
          ; >= 32 then %eax should be zero after this. 

y aquí es el bit equivalente con -O0:

movl -16(%ebp), %eax 
    movl -12(%ebp), %edx 
    shldl %cl,%eax, %edx 
    sall %cl, %eax 
    testb   $32, %cl 
    je      L3 
    movl    %eax, %edx 
    xorl    %eax, %eax   ; correctly zeros %eax if shift count >= 32 
L3: 
    movl %eax, -16(%ebp) 
    movl %edx, -12(%ebp) 

Compiler es:

i686-apple-darwin11-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5666) (dot 3) 

Gracias por publicar su salida gcc -S. Eché un vistazo y aunque es un poco diferente, la parte crítica tiene el mismo error que el que vi en mi máquina.

Cuestiones relacionadas