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

在这段代码中我们无法解释一个烦人的错误:

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. 它只发生在Visual C ++ 2008上,如果我们__forceinline getData函数(默认情况下,VC ++ 2008不内联函数,而VC ++ 2010)
  6. 它发生在下面给出的代码片段上,可能是因为循环内部有大量内联
  7. 如果我们删除循环,则不会发生,直接设置有趣的值(18)

奖励信息:

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

2-如果我们在Test / Set / ResetBit函数中将一个强制types转换为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函数是macros。
  2. 常数是定义的
  3. 指数是longint (在Windows 32位,它们具有相同的大小)

如果需要的话,我会尽快添加一些信息(例如生成的两种configuration的汇编程序,更新g ++如何处理这个问题)。

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

VS2012上不会发生这个错误。 解决方法是强制其中一个函数不能内联。 给定代码,你可能想为getData()做到这一点:

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

附录2 OP代码尽可能最小的部分如下。 这段代码导致VS2010中的优化器错误 – 依赖于内联扩展的GetData() 。 即使将GetData()的两个返回值合并成一个,bug也“消失”了。 另外,如果只将第一个字节的位结合起来,就不会导致错误(如char bitmap[1]; – 您需要两个字节)。

这个问题在VS2012下不会发生。 这感觉很可怕,因为MS在2012年明显地修复了这个问题,但在2010年却没有修复。

BTW:

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

VS2010优化器缺陷validation:

 #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传播错误。 可以通过更改掩码function(如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转换为字符大小。 这将导致VS2010中的优化器做正确的事情。 也许有人可以评论。