我遇到了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
答案 0 :(得分:10)
还没有人提到过的东西;如果在16位unsigned int
的计算机上编译此代码,则这两个代码段不同。
crc
被指定为32位无符号整数类型。 ~crc
将反转所有位,但如果unsigned int
为16位,则crc = crc ^ ~0U
将仅反转低16位。
我对CRC算法知之甚少,不知道这是故意还是错误,也许hivert可以澄清;虽然查看OP发布的示例代码,但它肯定会对随后的循环产生影响。
NB。很抱歉发布这个"答案"因为它不是一个答案,但它太大而不适合评论:)
答案 1 :(得分:6)
原因如下: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 & 0xFFFE
或x & ~0x0001
;无条件使用x | 0x0001
打开最后一位。也就是说,如果你做了很多苦恼,你的手指可能会知道那些成语,只是不经过深思熟虑就把它们推出来。
答案 3 :(得分:0)
我怀疑有什么深刻的理由。也许这就是作者对它的看法(“我只是与所有的一起”),或者也许是如何在算法定义中表达的。
答案 4 :(得分:0)
我认为出于同样的原因,有些人写
const int zero = 0;
和其他人写
const int zero = 0x00000000;
不同的人会想到不同的方式。即使是基本的操作。