Уровни оптимизации в gcc, изменяющие поведение программы - PullRequest
20 голосов
/ 11 января 2012

Я вижу поведение, которого не ожидаю при компиляции этого кода с разными уровнями оптимизации в gcc.

Функциональный тест должен заполнить 64-разрядное целое число без знака единицами, сдвинуть их биты shift_size влево и вернуть 32 младших бита в виде 32-разрядного целого числа без знака.

Когда я компилирую с -O0, я получаю ожидаемые результаты.

Когда я компилирую с -O2, я не делаю, если я пытаюсь сместиться на 32 или более бит.

Фактически, я получаю именно те результаты, которые ожидаю, если бы я сдвигал 32-битное целое число на сдвиги, большие или равные ширине бит на x86, что является сдвигом, использующим только 5 младших битов размера сдвига.

Но я сдвигаю 64-битное число, поэтому сдвиги <64 должны быть законными, верно? </p>

Я предполагаю, что это ошибка в моем понимании, а не в компиляторе, но я не смог ее выяснить.

Моя машина: 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

Ответы [ 5 ]

5 голосов
/ 11 января 2012

"%u" (или "%lu") и uint32_t не обязательно совместимы. Попробуйте

    #include <inttypes.h>

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

Печать значения uint32_t с помощью спецификатора "%u" (или "%lu") может вызвать неопределенное поведение.

4 голосов
/ 11 января 2012

Мне удалось это повторить. Вот соответствующий бит сгенерированного кода с -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 голосов
/ 11 января 2012

Похоже, что может быть для меня 32-битной ошибкой компилятора.С помощью кода из вопроса и gcc 4.2.1 я могу воспроизвести ошибку, пока я компилирую с gcc -m32 -O2 ....

Однако, если я добавлю отладку 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
}

тогда проблема исчезнет.

Следующим шагом будет просмотр сгенерированного кода, чтобы попытаться выявить / подтвердить ошибку.

1 голос
/ 11 января 2012

Похоже, ошибка. Моя догадка заключается в том, что компилятор сложил последние две строки из:

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

в

return ((uint32_t)res) << shift_size;

Последний теперь четко определен для 32 или больше.

0 голосов
/ 11 января 2012

Я не помню, что говорит C99, но кажется, что в gcc uint32_t содержит не менее 32 бит и может содержать больше, поэтому при оптимизации его использует 64-битная переменная, которая быстрее на 64-битной машине.

...