48 votes

S'agit-il d'un bogue d'optimisation du compilateur ou d'un comportement non défini?

Nous avons un bug gênant, je ne peux pas l'expliquer autour de ce morceau de code:

unsigned char bitmap[K_BITMAP_SIZE] = {0} ;
SetBit(bitmap, K_18); // Sets the bit #18 to 1

for(size_t i = 0; i < K_END; ++i)
{
    if(TestBit(bitmap, i)) // true for 18
    {
        size_t i2 = getData(i); // for 18, will return 15
        SetBit(bitmap, i2); // BUG: IS SUPPOSED TO set the bit #15 to 1
    }
}
  1. Il arrive sur Visual C++ 2010
  2. Il arrive à la fois sur 32 bits et les versions 64 bits
  3. Il ne se produit que sur les versions Release (avec "Optimiser la Vitesse (/O2)" ensemble
  4. Il ne se passe pas uniquement sur les versions Release avec "Réduire la Taille (/O1)" ensemble
  5. Il arrive sur Visual C++ 2008 seulement si nous __forceinline la fonction getData (par défaut, VC++2008 n'a pas inline cette fonction, tandis que VC++2010)
  6. Il arrive sur le morceau de code donné ci-dessous, probablement parce que massive inline l'intérieur de la boucle
  7. Il ne se passe pas si nous enlever la boucle et de définir directement l'intéressantes (18)

Bonus info:

1 - BenJ, a commenté le problème n'apparaît pas sur Visual C++ 2012, ce qui signifie que ce pourrait bien être un bug du compilateur

2 - Si l'on ajoute un cast unsigned char dans le Test/Régler/ResetBit fonctions, le bug disparaît, trop

size_t TestBit(const unsigned char * bits, size_t pos) { return (((bits)[(pos) >> 3]) &   (1 << (unsigned char)((pos) & 7))) ; }
size_t SetBit(unsigned char * bits, size_t pos)        { return (((bits)[(pos) >> 3]) |=  (1 << (unsigned char)((pos) & 7))) ; }
size_t ResetBit(unsigned char * bits, size_t pos)      { return (((bits)[(pos) >> 3]) &= ~(1 << (unsigned char)((pos) & 7))) ; }

La question est:

Ne ce bug se produit parce que notre code s'appuie sur un comportement indéterminé, ou est-il un bug dans le VC++2010 compilateur?

La source suivante est auto-suffisante, et peut être compilé en tant que tel dans vos favoris compilateur:

#include <iostream>


const size_t K_UNKNOWN              = (-1) ;
const size_t K_START                = (0) ;
const size_t K_12                   = (K_START + 12) ;
const size_t K_13                   = (K_START + 13) ;
const size_t K_15                   = (K_START + 15) ;
const size_t K_18                   = (K_START + 18) ;
const size_t K_26                   = (K_START + 26) ;
const size_t K_27                   = (K_START + 27) ;
const size_t K_107                  = (K_START + 107) ;
const size_t K_128                  = (K_START + 128) ;
const size_t K_END                  = (K_START + 208) ;
const size_t K_BITMAP_SIZE          = ((K_END/8) + 1) ;


size_t TestBit(const unsigned char * bits, size_t pos) { return (((bits)[(pos) >> 3]) &   (1 << ((pos) & 7))) ; }
size_t SetBit(unsigned char * bits, size_t pos)        { return (((bits)[(pos) >> 3]) |=  (1 << ((pos) & 7))) ; }
size_t ResetBit(unsigned char * bits, size_t pos)      { return (((bits)[(pos) >> 3]) &= ~(1 << ((pos) & 7))) ; }


size_t getData(size_t p_value)
{
    size_t value = K_UNKNOWN;

    switch(p_value)
    {
        case K_13:      value = K_12;        break;
        case K_18:      value = K_15;        break;
        case K_107:     value = K_15;        break;
        case K_27:      value = K_26;        break;
        case K_128:     value = K_12;        break;
        default:        value = p_value;     break;
    }

    return value;
}


void testBug(const unsigned char * p_bitmap)
{
    const size_t byte = p_bitmap[1] ;
    const size_t bit  = 1 << 7 ;
    const size_t value = byte & bit ;

    if(value == 0)
    {
        std::cout << "ERROR : The bit 15 should NOT be 0" << std::endl ;
    }
    else
    {
        std::cout << "Ok : The bit 15 is 1" << std::endl ;
    }
}


int main(int argc, char * argv[])
{
    unsigned char bitmap[K_BITMAP_SIZE] = {0} ;
    SetBit(bitmap, K_18);

    for(size_t i = 0; i < K_END; ++i)
    {
        if(TestBit(bitmap, i))
        {
            size_t i2 = getData(i);
            SetBit(bitmap, i2);
        }
    }

    testBug(bitmap) ;

    return 0;
}

Certaines des informations de base: dans un premier temps:

  1. le Test/Régler/ResetBit fonctions étaient des macros.
  2. les constantes ont été définit
  3. les indices ont été soit long ou int (sur Windows 32 bits, ils ont la même taille)

Si nécessaire, je vais ajouter un peu plus d'info (par exemple, l'assembleur généré pour les deux configurations, la mise à jour sur la façon g++ traiter le problème), dès que possible.

30voto

Hans Passant Points 475940

C'est un bogue d'optimisation de code. Il insère à la fois getData () et SetBit (). La combinaison semble être fatale, elle perd la trace de la valeur 1 << ((pos) & 7) et produit toujours zéro.

Ce bogue ne se produit pas sur VS2012. Une solution de contournement consiste à forcer l'une des fonctions à ne pas être en ligne. Étant donné le code, vous voudrez probablement le faire pour getData ():

 __declspec(noinline)
size_t getData(size_t p_value)
{ 
    // etc..
}
 

11voto

rubber boots Points 6562

Addendum 2 La plus faible possible dans le cadre de l'OP du code est donné ci-dessous. Cet extrait mène à la dite optimiseur de bug dans VS2010 - dependend sur le contenu de inline-élargi GetData(). Même si l'on combine les deux rendements en GetData() dans une le bug est "parti". Aussi, il ne conduit pas à un bug si vous combinez les bits que dans le premier octet (comme char bitmap[1]; - vous avez besoin de deux octets).

Le problème ne se produit pas sous VS2012. C'est horrible parce que MS fixe que de toute évidence, en 2012, mais pas en 2010. WTF?

BTW:

  • g++ 4.6.2 x64 (-O3) -- ok
  • la cisp-12.1.0 x64 (-O3) -- ok

VS2010 optimiseur de bug de vérification:

#include <iostream>
const size_t B_5=5, B_9=9;

size_t GetBit(unsigned char * b, size_t p) { return b[p>>3]  & (1 << (p & 7)); }
void   SetBit(unsigned char * b, size_t p) {        b[p>>3] |= (1 << (p & 7)); }

size_t GetData(size_t p) {
   if (p == B_5) return B_9;
   return 0;
}
/* SetBit-invocation will fail (write 0) 
   if inline-expanded in the vicinity of the GetData function, VS2010 */

 int main(int argc, char * argv[])
{
 unsigned char bitmap[2] = { 0, 0 };
 SetBit(bitmap, B_5);

 for(size_t i=0; i<2*8; ++i) {
    if( GetBit(bitmap, i) )         // no difference if temporary variable used,
        SetBit(bitmap, GetData(i)); // the optimizer will drop it anyway
 }

 const size_t byte=bitmap[1], bit=1<<1, value=byte & bit;
 std::cout << (value == 0 ? "ERROR: The bit 9 should NOT be 0" 
                          : "Ok: The bit 9 is 1") << std::endl;
 return 0;
}

Après quelques inspection, on peut voir que l'initialisation/réinitialisation de la partie ne fait pas partie de ce problème spécifique.

Regardé de nouveau après le repas. Semble être un char/int propagation de l'erreur. Peut être guérie par la modification du masque de fonctions (comme cela a déjà été constaté par l'OP):

size_t TestBit  (const unsigned char * bits, size_t pos) { 
 return (bits)[pos >> 3] &   (1 << ( char(pos) & 7) ) ; 
}
size_t SetBit   (unsigned char * bits, size_t pos)       { 
 return (bits)[pos >> 3] |=  (1 << ( char(pos) & 7) ) ; 
}
size_t ResetBit (unsigned char * bits, size_t pos)       { 
 return (bits)[pos >> 3] &= ~(1 << ( char(pos) & 7) ) ; 
}

par la coulée de l'int de la taille de la position pos pour un char de taille. Cela conduira à l'optimiseur dans VS2010 de faire la bonne chose. Peut-être quelqu'un peut commenter.

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