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

pae*_*bal 48 c++ visual-studio-2010 compiler-optimization undefined-behavior visual-studio

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

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
    }
}
Run Code Online (Sandbox Code Playgroud)
  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函数中添加一个强制转换,那么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))) ; }
Run Code Online (Sandbox Code Playgroud)

问题是:

是否发生此错误是因为我们的代码依赖于未定义的行为,或者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;
}
Run Code Online (Sandbox Code Playgroud)

一些背景信息:最初:

  1. Test/Set/ResetBit函数是宏.
  2. 常量是定义的
  3. 索引是long或者int(在Windows 32位上,它们具有相同的大小)

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

Han*_*ant 30

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

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

__declspec(noinline)
size_t getData(size_t p_value)
{ 
    // etc..
}
Run Code Online (Sandbox Code Playgroud)

  • "Connect的所有旧反馈都被删除了" - 这对他们来说很好.那么现在人们仍在使用2010年,但不再能够获取有关已知问题的信息吗? (5认同)
  • "这个错误在VS2012中得到修复" - 出于兴趣,您是否知道由于错误报告您无法/不链接,这已在VS2012中修复?或者你是否凭经验知道它,因为(正如提问者所报告的那样)代码就是这样做的? (3认同)

rub*_*ots 11

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

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

BTW:

  • 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;
}
Run Code Online (Sandbox Code Playgroud)

经过一些检查后,可以看到初始化/归零部分不是此特定问题的一部分.

饭后又看了一遍.似乎是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) ) ; 
}
Run Code Online (Sandbox Code Playgroud)

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

  • @MatthieuM:8.5.1/7:"如果列表中的初始值设定项少于聚合中的成员,则未明确初始化的每个成员都应进行值初始化(8.5)".8.5说,"对T类型的对象进行值初始化意味着...... [在`unsigned char`的情况下]对象被零初始化"并且还说"要对零类型的对象进行零初始化意味着: - 如果T是标量类型(3.9),对象设置为0(零)转换为T的值;".因此,除非代码中的其他地方有UB,否则`bitmap`*必须*归零以符合. (5认同)