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. 仅当我们__forceinline使用 getData 函数时,它才会在 Visual C++ 2008 上发生(默认情况下,VC++2008 不会内联该函数,而 VC++2010 会)
  6. 它发生在下面给出的一段代码上,可能是因为循环内的大量内联
  7. 如果我们去掉循环,就不会发生这种情况,直接设置有趣的值(18)

奖金信息:

1- BenJ 评论说这个问题没有出现在 Visual C++ 2012 上,这意味着这很可能是编译器中的一个错误

2- 如果我们unsigned char在 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. 索引是longint(在 Windows 32 位上,它们具有相同的大小)

如果需要,我会尽快添加更多信息(例如,为两种配置生成的汇编程序,更新 g++ 如何处理问题)。

4

2 回答 2

30

这是一个代码优化器错误。它内联了 getData() 和 SetBit()。该组合似乎是致命的,它丢失了 1 << ((pos) & 7) 的值,并且总是产生零。

VS2012 上不会出现此错误。一种解决方法是强制其中一个函数不被内联。给定代码,您可能希望对 getData() 执行此操作:

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

附录 2 OP 代码的最小可能部分如下所示。此代码段导致 VS2010 中的上述优化器错误 - 依赖于 inline-expanded 的内容GetData()。即使将两个返回合二为一GetData(),错误也“消失”了。char bitmap[1];此外,如果您仅在第一个字节中组合位(例如- 您需要两个字节),它不会导致错误。

VS2012下不会出现该问题。这感觉很可怕,因为 MS 显然在 2012 年解决了这个问题,但在 2010 年没有解决。WTF?

顺便提一句:

  • g++ 4.6.2 x64 (-O3) -- 好的
  • icpc 12.1.0 x64 (-O3) -- 好的

VS2010优化器bug验证:

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

通过将 int 大小的位置pos转换为 char 大小。这将引导 VS2010 中的优化器做正确的事情。也许有人可以发表评论。

于 2012-10-08T09:36:53.000 回答