ejecutar - gcc wikipedia
Niveles de optimización en gcc cambiando el comportamiento del programa c (5)
Estoy viendo un comportamiento que no espero al compilar este código con diferentes niveles de optimización en gcc.
La prueba de función debe llenar un entero sin signo de 64 bits con unos, desplazar los bits de cambio de tamaño hacia 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 desplazarme en 32 bits o más.
De hecho, obtendré exactamente los resultados que esperaría si cambiara un entero de 32 bits por desplazamientos mayor o igual al ancho de bits en x86, que es un desplazamiento que utiliza 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 entendimiento y no en el compilador, pero no he podido resolverlo.
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
No recuerdo lo que dice C99, pero parece que en gcc, uint32_t contiene al menos 32 bits, y puede contener más, así que cuando lo optimizas, usa 64bit var, que es más rápido en una máquina de 64 bits.
Parece que podría ser un error de 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 agrego un debf printf:
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 intentar identificar / confirmar el error.
Parece un error. Mi conjetura es que el compilador ha plegado las dos últimas líneas de:
res = res << shift_size
return (uint32_t)res;
dentro:
return ((uint32_t)res) << shift_size;
Este último ahora está bien definido para 32 o más.
Pude reprochar esto. Aquí está el bit relevante 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í está 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)
El compilador es:
i686-apple-darwin11-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5666) (dot 3)
Gracias por publicar tu salida de gcc -S
. Eché un vistazo y aunque es un poco diferente, la parte crítica tiene el mismo error que lo que vi en mi máquina.
"%u"
(o "%lu"
) y uint32_t
no son necesariamente compatibles. Tratar
#include <inttypes.h>
//printf("%ul/n", test(dst));
printf("%" PRIu32 "l/n", test(dst));
La impresión de un valor uint32_t
con un uint32_t
"%u"
(o "%lu"
) puede invocar un comportamiento indefinido.