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

我遇到过这个CRC32代码 ,很好奇为什么作者会选择使用它

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 

我也无法通过考虑每条指令所需的周期数来certificate代码的合理性,因为两条指令都需要完成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:

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

优化构建:

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

 // 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 

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

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

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

NB。 很抱歉将此作为“答案”发布,因为它不是答案,但它太大而不适合评论:)

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

原因如下:CRC有很多变种。 每一个都依赖于Z / Z2多项式,该多项式用于欧几里德分裂。 通常是使用Aram Perez在本文中描述的算法实现的。 现在,根据您使用的多项式, 算法末尾有一个最终的XOR,它取决于多项式,其目标是消除某些角点情况。 碰巧对于CRC32,这与全局不同,但对于所有CRC都不是这样。 作为此网页上的证据,您可以阅读(强调我的):

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

crc remainder = INITIAL_REMAINDER;

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

return(余数^ FINAL_XOR_VALUE);

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

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

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

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

 const int zero = 0; 

和其他人写

 const int zero = 0x00000000; 

不同的人认为不同的方式。 即使是基本的操作。