C 因特网校验和中的位移位

C 因特网校验和中的位移位,c,bit-manipulation,checksum,C,Bit Manipulation,Checksum,这几乎肯定是一个非常愚蠢的问题,但出于某种原因,我在互联网校验和计算方面遇到了麻烦。所有算法基本上如下所示: WORD chksm(WORD *startpos, WORD checklen){ ulong sum = 0; WORD answer = 0; while (checklen > 1) { sum += *startpos++; checklen -= 2; } if (checklen == 1) { *(BYTE *)(&answer)

这几乎肯定是一个非常愚蠢的问题,但出于某种原因,我在互联网校验和计算方面遇到了麻烦。所有算法基本上如下所示:

WORD chksm(WORD *startpos, WORD checklen){
ulong sum = 0;
WORD answer = 0;

while (checklen > 1)
{
    sum += *startpos++;
    checklen -= 2;
}

if (checklen == 1)
{
    *(BYTE *)(&answer) = *(BYTE *)startpos;
    sum += answer;
}

sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;

return answer;}
我什么都清楚,除了那句话:

sum += (sum >> 16);
它看起来像是将前16位添加到下16位之前的一行,在前16位中保留所有零。如果是这样的话,那么现在的和>>16不是等于零吗?如果是这样的话,为什么会有这条线

或者我(很可能)今天只是精神完全衰竭

你几乎是对的

由于进位,高16位可能是1


例如,
FFFF+FFFF=>1ffe
,或者可能
FFFF+1=>10000
,我认为ulong是32位宽的,这意味着:

sum = (sum >> 16) + (sum & 0xffff)
sum += (sum >> 16);

将顶部sicteen位和底部sisteen位相加。然后下一行对前16位的结果求和;由于进位运算,其中可能有一个1。

这是1的补码和定义的一部分。您获取任何溢出位,并将其添加回较低的16位。将它们加回去可能会导致进一步的溢出,因此重复此操作,直到高位全部为零。所以,从概念上讲,是这样的:

while (sum >> 16 != 0) {
    sum = (sum >> 16) + (sum & 0xffff);
}
但是,此循环最多只执行两次,因此不需要显式循环。在第一次加法之后,可能会或可能不会出现溢出,进位结束于上16位。在这种情况下,上面的16位将是
0x0001
,您必须再做一次加法才能将进位添加回

想象一下最坏的情况,在初始while循环之后,sum结束为
0xffffff
。然后,添加操作将按以下步骤进行:

sum = (0xffffffff >> 16) + (0xffffffff & 0xffff)
    = 0xffff + 0xffff
    = 0x1fffe

sum = (0x1fffe >> 16) + (0x1fffe & 0xffff)
    = 0x1 + 0xfffe
    = 0xffff
加上两个加法,我们就完成了,因为上面的16位现在已经清楚了。这是最坏的情况,因此循环可以展开为两个加法

(然后,你把最后一个和的一个补码取出来,导致这个名字非常混乱:一个补码和的一个补码。在我第一次实现它的时候,我花了很长时间才意识到这一点,特别是一个补码和不涉及
~
补码歌剧。)托尔。)