20 votes

Est-ce un bug dans 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

Pourquoi est-ce que j'obtiens ces résultats ?

20voto

Stefan Points 655

Il semble que gcc inverse le décalage et l'applique à l'autre côté, et je suppose que c'est un bogue.

En C (au lieu de C++) la même chose se produit, et le C traduit en asm est plus facile à lire, donc j'utilise le C ici ; j'ai aussi réduit les cas de test (en laissant tomber les templates et le tableau k). foo() est la fonction originale buggy f(), foo1() est ce à quoi foo() ressemble avec gcc mais ne devrait pas, et bar() montre ce à quoi foo() devrait ressembler à part la lecture du pointeur.

Je suis sur 64-bit, mais 32-bit c'est pareil à part la manipulation des paramètres et la recherche de 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;
}

8voto

Alexey Frunze Points 37651

Si votre int est de 16 bits, vous vous heurtez à un comportement non défini et l'un ou l'autre résultat est "OK".

Le décalage des nombres entiers de N bits par N positions de bits ou plus vers la gauche ou la droite entraîne un comportement non défini.

Comme cela se produit avec des ints 32 bits, il s'agit d'un bogue dans le compilateur.

4voto

thang Points 2183

Voici d'autres points de données :

En fait, il semble que gcc optimise (même lorsque le drapeau -O est désactivé et que -g est activé) :

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

à

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

y

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

à

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

où [variable] doit être un accès à un tableau.

Je suppose que l'avantage ici est qu'il n'est pas nécessaire de charger le 1 littéral dans un registre, ce qui économise un registre.

Voici donc les points de données :

  • En remplaçant 1 par un nombre > 1, il est obligé d'implémenter la version correcte.
  • changer une des variables en un littéral le force à implémenter la version correcte
  • changer [variable] en un accès non tableau le force à implémenter la version correcte
  • [variable] > (type-cast)(1 << [variable2]) implémente la version correcte.

Je soupçonne que tout cela vise à sauver un registre. Lorsque [variable] est un accès à un tableau, elle doit également conserver un index. Quelqu'un a probablement pensé que c'était très intelligent, jusqu'à ce que ce soit faux.

En utilisant le code du rapport de bogue 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;
    }

compilé avec 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

compiler avec seulement 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

Je suppose que la prochaine étape est de creuser dans le code source de gcc.

-1voto

Mats Petersson Points 70074

Je suis presque sûr que nous parlons ici d'un comportement indéfini - convertir un "grand" entier en un plus petit, d'une valeur qui ne tient pas dans la taille de la nouvelle valeur, est indéfini pour autant que je sache. 131072 ne rentre définitivement pas dans un uint_8.

Bien qu'en regardant le code généré, je dirais que ce n'est probablement pas tout à fait correct, puisqu'il fait "sete" plutôt que "setb" ??? Cela me semble très suspect.

Si je retourne l'expression :

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

alors il utilise une instruction "seta", ce qui est ce que j'attendais. Je vais creuser un peu plus, mais il y a quelque chose qui cloche.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X