Подтвердить что ты не робот

Это ошибка в g++?

#include <stdint.h>
#include <iostream>

using namespace std;

uint32_t k[] = {0, 1, 17};

template <typename T>
bool f(T *data, int i) {
    return data[0] < (T)(1 << k[i]);
}

int main() {
    uint8_t v = 0;
    cout << f(&v, 2) << endl;
    cout << (0 < (uint8_t)(1 << 17)) << endl;
    return 0;
}


g++ a.cpp && ./a.out
1
0

Почему я получаю эти результаты?

4b9b3361

Ответ 1

Похоже, gcc отменяет сдвиг и применяет его к другой стороне, и я думаю, что это ошибка.

В C (вместо С++) происходит то же самое, и C, переведенный в asm, легче читать, поэтому я использую C здесь; также я уменьшил тестовые примеры (отбрасывая шаблоны и массив k). foo() является исходной функцией fug(), foo1() - это то, что foo() ведет себя как с gcc, но не должно, а bar() показывает, что должно выглядеть foo(), кроме прочитанного указателя.

Я нахожусь на 64-битном, но 32-разрядный - это то же самое, что и обработка параметров и поиск k.

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

uint32_t k = 17;
char foo(uint8_t *data) {
    return *data < (uint8_t)(1<<k);
/*
with gcc -O3 -S: (gcc version 4.7.2 (Debian 4.7.2-5))
    movzbl  (%rdi), %eax
    movl    k(%rip), %ecx
    shrb    %cl, %al
    testb   %al, %al
    sete    %al
    ret
*/
}
char foo1(uint8_t *data) {
    return (((uint32_t)*data) >> k) < 1;
/*
    movzbl  (%rdi), %eax
    movl    k(%rip), %ecx
    shrl    %cl, %eax
    testl   %eax, %eax
    sete    %al
    ret
*/
}
char bar(uint8_t data) {
    return data < (uint8_t)(1<<k);
/*
    movl    k(%rip), %ecx
    movl    $1, %eax
    sall    %cl, %eax
    cmpb    %al, %dil
    setb    %al
    ret
*/
}

int main() {
    uint8_t v = 0;
    printf("All should be 0: %i %i %i\n", foo(&v), foo1(&v), bar(v));
    return 0;
}

Ответ 2

Если ваш int имеет длину 16 бит, вы выполняете поведение undefined, и результат будет "ОК".

Смещение N-разрядных целых чисел на N или более позиций влево или вправо приводит к поведению undefined.

Так как это происходит с 32-битными int, это ошибка в компиляторе.

Ответ 3

Вот еще несколько точек данных:

в основном, он выглядит как gcc оптимизирует (даже когда флаг -O выключен и -g включен):

    [variable] < (type-cast)(1 << [variable2])

к

    ((type-cast)[variable] >> [variable2]) == 0

и

    [variable] >= (type-cast)(1 << [variable2])

к

    ((type-cast)[variable] >> [variable2]) != 0

где [variable] должен быть доступ к массиву.

Я предполагаю, что преимущество заключается в том, что ему не нужно загружать литерал 1 в регистр, который сохраняет 1 регистр.

Итак, вот точки данных:

  • изменение 1 на число > 1 заставляет его реализовать правильную версию.
  • изменение любой из переменных в литерал заставляет его реализовать правильную версию
  • изменение [variable] в доступе без массива заставляет его реализовать правильную версию
  • [variable] > (type-cast) (1 < < [переменная2]) реализует правильную версию.

Я подозреваю, что все это пытается сохранить регистр. Когда [variable] является доступом к массиву, он также должен содержать индекс. Кто-то, вероятно, думал, что это так умно, пока оно не ошибается.

Использование кода из отчета об ошибке http://gcc.gnu.org/bugzilla/show_bug.cgi?id=56051

    #include <stdio.h>

    int main(void)
    {
        int a, s = 8;
        unsigned char data[1] = {0};

        a = data[0] < (unsigned char) (1 << s);
        printf("%d\n", a);

        return 0;
    }

скомпилированный с помощью gcc -O2 -S

     .globl main
            .type   main, @function
    main:
    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl   -4(%ecx)
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ecx
    subl    $8, %esp
    pushl   $1                ***** seems it already precomputed the result to be 1
    pushl   $.LC0
    pushl   $1
    call    __printf_chk
    xorl    %eax, %eax
    movl    -4(%ebp), %ecx
    leave
    leal    -4(%ecx), %esp
    ret

скомпилировать только gcc -S

    .globl main
            .type   main, @function
    main:
    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl   -4(%ecx)
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ebx
    pushl   %ecx
    subl    $16, %esp
    movl    $8, -12(%ebp)
    movb    $0, -17(%ebp)
    movb    -17(%ebp), %dl
    movl    -12(%ebp), %eax
    movb    %dl, %bl
    movb    %al, %cl
    shrb    %cl, %bl                      ****** (unsigned char)data[0] >> s => %bl
    movb    %bl, %al                              %bl => %al
    testb   %al, %al                              %al = 0?
    sete    %dl
    movl    $0, %eax
    movb    %dl, %al
    movl    %eax, -16(%ebp)
    movl    $.LC0, %eax
    subl    $8, %esp
    pushl   -16(%ebp)
    pushl   %eax
    call    printf
    addl    $16, %esp
    movl    $0, %eax
    leal    -8(%ebp), %esp
    addl    $0, %esp
    popl    %ecx
    popl    %ebx
    popl    %ebp
    leal    -4(%ecx), %esp
    ret

Я предполагаю, что следующий шаг - выкачать исходный код gcc.

Ответ 4

Я уверен, что мы говорим о поведении undefined здесь - преобразование "большого" целого числа в меньшее, значения, которое не соответствует размеру нового значения, - это undefined как я знаю. 131072 определенно не вписывается в uint_8.

Хотя, глядя на код, сгенерированный, я бы сказал, что он, вероятно, не совсем прав, поскольку он делает "sete", а не "setb"??? Это кажется мне очень подозрительным.

Если я верну выражение:

return (T)(1<<k[i])  >  data[0];

то он использует инструкцию "seta", чего я ожидаю. Я сделаю немного больше копания, но что-то кажется немного неправильным.