为什么使用xor和文字而不是反转(按位不是)

时间:2014-03-11 20:32:11

标签: c++ c bitwise-operators crc

我遇到了this CRC32 code并且好奇为什么作者会选择使用

crc = crc ^ ~0U;

而不是

crc = ~crc;

据我所知,它们是等价的。

我甚至在Visual Studio 2010中反汇编了两个版本。

未优化构建:

    crc = crc ^ ~0U;
009D13F4  mov         eax,dword ptr [crc]  
009D13F7  xor         eax,0FFFFFFFFh  
009D13FA  mov         dword ptr [crc],eax 

    crc = ~crc;
011C13F4  mov         eax,dword ptr [crc]  
011C13F7  not         eax  
011C13F9  mov         dword ptr [crc],eax  

我也无法通过考虑每条指令所需的周期数来证明代码的合理性,因为两条指令都需要完成1个周期。事实上, xor 可能因为必须从某处加载文字而受到惩罚,但我不确定这一点。

所以我认为它可能只是描述算法的首选方式,而不是优化......这是正确的吗?

修改1:

因为我刚刚意识到crc变量的类型可能很重要,我在这里包括整个代码(少了查找表,太大了)所以你不必关注链接

uint32_t crc32(uint32_t crc, const void *buf, size_t size)
{
    const uint8_t *p;

    p = buf;
    crc = crc ^ ~0U;

    while (size--)
    {
        crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8);
    }

    return crc ^ ~0U;
}

编辑2:

由于有人提出了一个优化构建会引起关注的事实,我已经制作了一个并将其包含在下面。

优化构建:

请注意,整个功能(包括在下面的最后一个编辑中)都是内联的。

// crc = crc ^ ~0U;
    zeroCrc = 0;
    zeroCrc = crc32(zeroCrc, zeroBufferSmall, sizeof(zeroBufferSmall));
00971148  mov         ecx,14h  
0097114D  lea         edx,[ebp-40h]  
00971150  or          eax,0FFFFFFFFh  
00971153  movzx       esi,byte ptr [edx]  
00971156  xor         esi,eax  
00971158  and         esi,0FFh  
0097115E  shr         eax,8  
00971161  xor         eax,dword ptr ___defaultmatherr+4 (973018h)[esi*4]  
00971168  add         edx,ebx  
0097116A  sub         ecx,ebx  
0097116C  jne         main+153h (971153h)  
0097116E  not         eax  
00971170  mov         ebx,eax  

// crc = ~crc;
    zeroCrc = 0;
    zeroCrc = crc32(zeroCrc, zeroBufferSmall, sizeof(zeroBufferSmall));
01251148  mov         ecx,14h  
0125114D  lea         edx,[ebp-40h]  
01251150  or          eax,0FFFFFFFFh  
01251153  movzx       esi,byte ptr [edx]  
01251156  xor         esi,eax  
01251158  and         esi,0FFh  
0125115E  shr         eax,8  
01251161  xor         eax,dword ptr ___defaultmatherr+4 (1253018h)[esi*4]  
01251168  add         edx,ebx  
0125116A  sub         ecx,ebx  
0125116C  jne         main+153h (1251153h)  
0125116E  not         eax  
01251170  mov         ebx,eax  

5 个答案:

答案 0 :(得分:10)

还没有人提到过的东西;如果在16位unsigned int的计算机上编译此代码,则这两个代码段不同

crc被指定为32位无符号整数类型。 ~crc将反转所有位,但如果unsigned int为16位,则crc = crc ^ ~0U将仅反转低16位。

我对CRC算法知之甚少,不知道这是故意还是错误,也许hivert可以澄清;虽然查看OP发布的示例代码,但它肯定会对随后的循环产生影响。

NB。很抱歉发布这个"答案"因为它不是一个答案,但它太大而不适合评论:)

答案 1 :(得分:6)

简短的回答是:因为它允许为所有CRC提供统一的算法

原因如下:CRC有很多变种。每一个都依赖于Z / Z2多项式,该多项式用于欧几里德分裂。通常是使用In this paper by Aram Perez描述的算法实现的。现在,根据您使用的多项式,在算法结束时存在最终的XOR,它取决于多项式,其目标是消除某些极端情况。碰巧对CRC32来说,这与全局不一样,但对于所有CRC都不是这样。作为This web page上的证据,你可以阅读(强调我的):

  

考虑以一些零位开头的消息。除非将消息中的第一个移入其中,否则余数将永远不会包含除零之外的任何内容。 这是一种危险的情况,因为以一个或多个零开头的数据包可能完全合法,并且CRC不会注意到丢弃或添加的零。(在某些应用中,甚至是一个包全零可能是合法的!)消除这个弱点的简单方法是从非零余数开始。名为initial remaining的参数告诉您用于特定CRC标准的值。 crcSlow()和crcFast()函数只需要进行一次小的更改:

     

crc remainder = INITIAL_REMAINDER;

     

由于类似原因,最终的XOR值存在。要实现此功能,只需更改crcSlow()和crcFast()返回的值,如下所示:

     

return(余数^ FINAL_XOR_VALUE);

     

如果最终的XOR值由全部值组成(如在CRC-32标准中那样),则此额外步骤将与补充最终余数具有相同的效果。但是,实现它方式允许在您的特定应用程序中使用任何可能的值。

答案 2 :(得分:1)

只是为了添加我自己的猜测,x ^ 0x0001保持最后一点并且忽略其他人;关闭最后一位使用x & 0xFFFEx & ~0x0001;无条件使用x | 0x0001打开最后一位。也就是说,如果你做了很多苦恼,你的手指可能会知道那些成语,只是不经过深思熟虑就把它们推出来。

答案 3 :(得分:0)

我怀疑有什么深刻的理由。也许这就是作者对它的看法(“我只是与所有的一起”),或者也许是如何在算法定义中表达的。

答案 4 :(得分:0)

我认为出于同样的原因,有些人写

const int zero = 0;

和其他人写

const int zero = 0x00000000;

不同的人会想到不同的方式。即使是基本的操作。