48

このコードの周りで説明できない厄介なバグがあります:

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. Visual C++2010で発生します
  2. 32ビットビルドと64ビットビルドの両方で発生します
  3. これは、リリースビルドでのみ発生します(「最大速度(/ O2)」が設定されている場合)
  4. 「サイズの最小化(/ O1)」が設定されたリリースビルドでのみ発生しません
  5. これは、getData関数の場合にのみVisual C ++ 2008で発生し__forceinlineます(デフォルトでは、VC ++ 2008はその関数をインライン化しませんが、VC ++ 2010はインライン化します)
  6. これは、おそらくループ内の大規模なインライン化が原因で、以下に示すコードで発生します
  7. ループを削除して、興味深い値を直接設定した場合は発生しません(18)

ボーナス情報:

1-BenJは、この問題はVisual C ++ 2012には表示されない、つまりこれはコンパイラのバグである可能性があるとコメントしました

unsigned char2- Test / Set / ResetBit関数にキャストを追加すると、バグもなくなります

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))) ; }

質問は:

このバグは、コードが未定義の動作に依存しているために発生しますか、それともVC ++ 2010コンパイラにバグがありますか?

次のソースは自給自足であり、お気に入りのコンパイラでそのままコンパイルできます。

#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;
}

いくつかの背景情報:最初は:

  1. Test / Set/ResetBit関数はマクロでした。
  2. 定数は定義されました
  3. インデックスはlongまたはint(Windows 32ビットでは同じサイズ)のいずれかでした

必要に応じて、できるだけ早くいくつかの情報を追加します(たとえば、両方の構成用に生成されたアセンブラー、g ++が問題を処理する方法を更新します)。

4

2 に答える 2

30

これはコードオプティマイザのバグです。getData()とSetBit()の両方をインライン化します。この組み合わせは致命的であるように見え、1 <<((pos)&7)の値を見失い、常にゼロを生成します。

このバグはVS2012では発生しません。回避策は、関数の1つを強制的にインライン化しないようにすることです。コードを考えると、おそらくgetData()に対してそれを実行する必要があります。

__declspec(noinline)
size_t getData(size_t p_value)
{ 
    // etc..
}
于 2012-10-08T10:53:56.070 に答える
11

補遺2OP のコードの可能な最小部分を以下に示します。このスニペットは、VS2010の前述のオプティマイザーのバグにつながります-インライン展開された内容に依存しGetData()ます。2つのリターンGetData()を1つにまとめたとしても、バグは「なくなりました」。また、最初のバイトのみのビットを組み合わせてもバグは発生しません(たとえば、char bitmap[1];2バイトが必要です)。

この問題はVS2012では発生しません。MSがそれを明らかに2012年に修正したが、2010年には修正しなかったので、これは恐ろしいと感じます。WTF?

ところで:

  • g ++ 4.6.2 x64(-O3)-わかりました
  • icpc 12.1.0 x64(-O3)-わかりました

VS2010オプティマイザーのバグ検証:

#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;
}

いくつかの検査の後、初期化/ゼロ化の部分はこの特定の問題の一部ではないことがわかります。

食事の後でもう一度見ました。char/int伝搬エラーのようです。マスク機能(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) ) ; 
}

posintサイズの位置をcharサイズにキャストします。これにより、VS2010のオプティマイザーが正しいことを実行できるようになります。多分誰かがコメントすることができます。

于 2012-10-08T09:36:53.000 に答える