C++ 为什么要将xor与文字而不是反转一起使用(按位不)

C++ 为什么要将xor与文字而不是反转一起使用(按位不),c++,c,bitwise-operators,crc,C++,C,Bitwise Operators,Crc,我曾经遇到过,并且很好奇为什么作者会选择使用 crc = crc ^ ~0U; 而不是 crc = ~crc; 据我所知,它们是等价的 我甚至在VisualStudio2010中分解了这两个版本 未优化构建: crc = crc ^ ~0U; 009D13F4 mov eax,dword ptr [crc] 009D13F7 xor eax,0FFFFFFFFh 009D13FA mov dword ptr [crc],e

我曾经遇到过,并且很好奇为什么作者会选择使用

crc = crc ^ ~0U;
而不是

crc = ~crc;
据我所知,它们是等价的

我甚至在VisualStudio2010中分解了这两个版本

未优化构建:

    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 = 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  
因为我刚刚意识到
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;
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  
因为有人提出了一个事实,一个优化的构建将是有趣的,我已经做了一个,并包括在下面

优化构建:

    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  
请注意,整个函数(包括在下面最后一次编辑中)是内联的

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

我怀疑有什么深层次的原因。也许这就是作者对它的想法(“我将对所有的进行异或运算”),或者它在算法定义中的表达方式。

简单的回答是:因为它允许对所有CRC有一个统一的算法 原因如下:CRC有很多变体。每一个都依赖于用于欧几里得除法的Z/Z2多项式。通常使用所描述的算法来实现。现在,根据您使用的多项式,在算法的末尾有一个最终的XOR,它取决于多项式,其目标是消除一些角点情况。对于CRC32,这与全局not相同,但并非所有CRC都是如此。作为证据,您可以阅读(我的重点):

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

crc余数=初始余数

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

返回值(余数^最终的异或值)

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


我认为这与有些人写作的原因相同

const int zero = 0;
还有人写

const int zero = 0x00000000;

不同的人有不同的想法。即使是一个基本操作。

为了增加我自己的猜测,
x^0x0001
保留最后一位并翻转其他位;要关闭最后一位,请使用
x&0xFFFE
x&~0x0001
;要无条件打开最后一位,请使用
x | 0x0001
。也就是说,如果你在做大量的小游戏,你的手指可能知道这些习语,并且不需要太多思考就可以把它们展开。

一些还没有人提到的东西;如果此代码是在具有16位
无符号int
的计算机上编译的,则这两个代码段不同

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

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


注意。很抱歉将此作为“答案”发布,因为它不是一个答案,但它太大了,不能只放在一个注释中:)

请您解释一下,不要@nonensickle搜索编译器?C是一种可移植语言。将它编译成一个特定的指令集并不是讨论它的一种有用的方式。这可能与某些体系结构没有精确的按位not有关吗?(例如,MIPS)也许作者想用xor来表示,这样他们就不必依赖于编译器,但编译器决定不进行模拟。xor更通用,因此他们可能更喜欢它,以使代码更易于移植。因为您的反汇编代码是为x86编写的,所以值得指出的是,
xor
将设置/清除零标志,而
NOT
将不设置/清除零标志(如果希望执行逐位操作而不影响依赖于先前操作的标志的跳转条件,则有时非常有用).现在,考虑到您不是直接编写程序集,您确实无法以有意义的方式访问此标志,因此我怀疑这是否是偏爱其中一个的原因。编译时是否启用了优化?我认为它不应将eax写回[crc]在优化版本中。如果不进行测试,我不确定没有什么深层次的原因,因为编译器在过去20年中取得了长足的进步。@Puciek我可以想象一个编译器为xor生成的代码比不为xor生成的代码更差,但反之亦然