Уровни оптимизации в gcc изменении c поведения программы
Я вижу поведение, которого я не ожидаю при компиляции этого кода с разными уровнями оптимизации в gcc.
Функциональный тест должен заполнить 64-битное беззнаковое целое число с единицами, сдвинуть бит shift_size влево и вернуть 32 младших бита в 32-разрядное целое без знака.
Когда я компилирую с -O0, я получаю ожидаемые результаты.
Когда я компилирую с -O2, я этого не делаю, если попытаюсь сдвинуться на 32 бит или более.
Фактически, я получаю точно результаты, которые я ожидал бы, если бы я смещал 32-битное целое по сдвигам, большим или равным ширине бита на x86, что является сдвигом, использующим только 5 младших бит сдвига.
Но я перекладываю 64-битное число, поэтому сдвиги < 64 должно быть законным?
Я предполагаю, что это ошибка в моем понимании, а не в компиляторе, но я не смог понять это.
Моя машина:
gcc (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5
i686-Linux-гну
#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;
}
Использование:
$ 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
"%u"
(или "%lu"
) и uint32_t
не обязательно совместимы.
Попробуйте
#include <inttypes.h>
//printf("%ul\n", test(dst));
printf("%" PRIu32 "l\n", test(dst));
Печать значения uint32_t
с помощью спецификатора "%u"
(или "%lu"
) может вызвать Undefined Поведение.
Ответ 2
Я смог воспроизвести это. Здесь соответствующий бит сгенерированного кода с -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.
и вот эквивалентный бит с -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)
Компилятор:
i686-apple-darwin11-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5666) (dot 3)
Спасибо, что опубликовали ваш вывод gcc -S
. Я посмотрел, и хотя он немного отличается, критическая часть имеет ту же ошибку, что и то, что я видел на своей машине.
Ответ 3
Похоже, это может быть ошибка компилятора для 32-битной специфики для меня. С кодом из вопроса и gcc 4.2.1 я могу воспроизвести ошибку до тех пор, пока я скомпилирую gcc -m32 -O2 ...
.
Однако, если я добавлю debug 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
}
тогда проблема исчезает.
Следующим шагом будет просмотр сгенерированного кода, чтобы попытаться определить/подтвердить ошибку.
Ответ 4
Похоже на ошибку. Я предполагаю, что компилятор сложил последние две строки из:
res = res << shift_size
return (uint32_t)res;
в
return ((uint32_t)res) << shift_size;
Последний теперь хорошо определен для 32 или более.
Ответ 5
Я не помню, что говорит C99, но похоже, что в gcc uint32_t содержит не менее 32 бита и может содержать больше, поэтому при его оптимизации он использует 64-битный var, который быстрее в 64-разрядной машине.