这是编译器优化错误还是未定义的行为?

时间:2012-10-08 09:05:20

标签: c++ visual-studio-2010 visual-studio compiler-optimization undefined-behavior

我们有一个令人烦恼的错误,我无法解释这段代码:

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. 上发生)
  5. 仅在“最小化大小(/ O1)”设置
  6. 的版本构建中不会发生
  7. 仅当我们__forceinline getData函数时才会出现在Visual C ++ 2008上(默认情况下,VC ++ 2008不会内联该函数,而VC ++ 2010会这样做)
  8. 它发生在下面给出的代码片段上,可能是因为循环中的大量内联
  9. 如果我们删除循环并直接设置有趣的值(18)
  10. ,则不会发生这种情况

    奖金信息:

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

    2-如果我们在Test / Set / ResetBit函数中向unsigned char添加一个强制转换,那么该bug也会消失

    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位上,它们具有相同的大小)
    4. 如果需要,我会尽快添加一些信息(例如,为两种配置生成汇编程序,更新g ++如何处理问题)。

2 个答案:

答案 0 :(得分:30)

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

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

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

答案 1 :(得分:11)

附录2 OP代码中可能的最小部分如下所示。此片段导致VS2010中的所述​​优化程序错误 - 取决于内联扩展GetData()的内容。即使将GetData()中的两个返回值合并为一个,该错误也会“消失”。此外,如果仅在第一个字节中组合位(例如char bitmap[1]; - 您需要两个字节),它不会导致错误。

VS2012下不会出现此问题。这感觉很糟糕,因为MS显然在2012年而不是在2010年解决了.WTF?

顺便说一句:

  • g ++ 4.6.2 x64(-O3) - ok
  • 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) ) ; 
}

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