学习CRC32、CRC16、CRC原理和算法的总结

我学习CRC32、CRC16、CRC原理和算法的总结(与WINRAR结果一致)发布时间:2010-09-02 11:26:15  

整理好的PDF可以在BAIDU免费下载:


http://wenku.baidu.com/view/fb791c0203d8ce2f006623f5.html


 

http://bbs.ednchina.com/BLOG_ARTICLE_1828885.HTM

 


我学习CRC32、CRC16、CRC原理和算法的总结(与WINRAR结果一致)


 


wxleasyland(wxlwww@gmail.com)


2010年9月2日


 


 


比较愚钝,学了CRC校验好几天,很痛苦的过程,现终于有眉目了,总结一下。


国外版的“轻松无痛苦学习CRC指南”,在


http://www.repairfaq.org/filipg/LINK/F_crc_v31.html


(为什么好的资料都是老外写的?)我的英文有限,这种专业性太强的文章,很多都看不太明白,所以没办法翻译,靠参考国内的翻译和自己瞎琢磨的。


国内的翻译比较不全,而且有点误导,能看英文的还是看英文吧,国内版资料比较零散,可参考:


http://www.q.cc/2001/12/08/10190.html


http://www.360doc.com/content/10/0703/12/1317564_36621098.shtml


http://www.yuanma.org/data/2006/1010/article_1637.htm


我结合国内资料和英文原版进行总结,达到和WINRAR一样的CRC32计算结果。


 


 


一、           CRC原理


可参考http://www.luocong.com/articles/show_article.asp?Article_ID=15


计算CRC的过程,就是用一个特殊的“除法”,来得到余数,这个余数就是CRC。


它不是真正的算术上的除法!过程和算术除法过程一样,只是加减运算变成了XOR(异或)运算!


 


 


算术上的除法:


120÷9=13 余 3,120是被除数,9是除数,13是商,3是余数。念作120除以9,或者9除120,或者9去除120!(除法的过程就不写了)


这个除法计算机当然会做,但是做起来很麻烦,因为减法有借位,很耗时间和指令!


所以,计算CRC也是除法,但是用XOR来代替减法,这就简单多了!


 


 


 


CRC的除法:


120÷9=14 余 6,商、余数和算术除法不一定相同!!因为除法用的是XOR,而不是真正的减法。


以二进制模拟这个计算过程:


 


        1110        商为1110,即14,商有4位,表示进行了4次XOR


     ________


1001/1111000        被除数120是1111000,除数9是1001


     1001    ^


     ----


      1100     第一次XOR后得到011,加入下一位0。最高位的0可以消掉了,这样最高位是1,所以下个商是1


      1001    ^


      ----


       1010    第二次XOR后得到0101,加入下一位0。最高位的0可以消掉了,这样最高位是1,所以下个商是1


       1001    ^


       ----


        0110   第三次XOR后得到0011,加入下一位0。最高位的0可以消掉了,这样最高位是0,所以下个商是0


        0000    ^


        ----


         110 ->  最后一次XOR后得到0110,最高位的0可以消掉了,得到余数为110,即6


                 注意,余数不是0110,而是110,因为最前面那个0已经被XOR后消掉了!


 


可见,除法(XOR)的目的是逐步消掉最高位的1或0!


由于过程是XOR的,所以商是没有意义的,我们不要。我们要的是余数。


 


余数110是1111000的CRC吗?不是!


余数110是1111(即十进制15)的CRC!!!


为什么?因为CRC是和数据一起传送的,所以数据后面要加上CRC。


数据1111加上CRC110后,变成1111110,再传送。接收机收到1111110后,除以除数1001,余数为000,正确;如果余数不为0,则说明传送的数据有误!这样完成CRC校验。


即发送端要发送1111,先在1111后加000,变成1111000,再除以1001得到余数110,这个110就是CRC,将110加到数据后面,变成1111110,发送出去。


接收端收到1111110,用它除以1001,计算得余数为000,就说明收到的数据正确。


所以原始数据后面要先扩展出3位0,以容纳CRC值!


会发现,在上面的除法过程中,这3位0,能保证所有的4个数据位在除法时都能够被处理到!不然做一次除法就到结果了,那是不对的。这个概念后面要用到。


 


所以,实际上,数据是1111,CRC是110。


对于除数1001,我们叫它生成多项式,即生成项,或POLY,即g(x)。


数据1111根据POLY1001,计算得到CRC110。


如果POLY不是1001,而是1011,那得到的CRC也是不同的!


所以生成项不同,得到的CRC也不同。要预先定义好POLY,发送端和接收端要用一样的POLY!



 


 


二、           生成项


上面例子中,生成项是1001,共4位比特,最高位的1,实际上在除法的每次XOR时,都要消掉,所以这个1可不做参考,后3位001才是最重要的!001有3位,所以得到的余数也是3位,因为最后一次除法XOR时,最高位消掉了。所以CRC就是3位比特的。


CRC是3比特,表示它的宽度W=3。也就是说,原始数据后面要加上W=3比特的0进行扩展!


生成项的最低位也必须是1,这是规定的。


生成项1001,就等效于g(x)=x2+1


生成项也可以倒过来写,即颠倒过来,写成1001,这里倒过来的值是一样的。


 


再如CRC32的生成项是:


1 0000 0100 1100 0001 0001 1101 1011 0111  (33个比特)


即g(x)= x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1


颠倒过来,就可以写成1110 1101 1011 1000 1000 0011 0010 0000 1


一般生成项简写时不写最高位的1,故生成项是0x04C11DB7,颠倒后的生成项是0xEDB88320


CRC32的生成项是33比特,最高位是消掉的,即CRC值是32比特(4个字节),即宽度W=32,就是说,在计算前,原始数据后面要先扩展W=32个比特0,即4个0x00字节。


 


注意:我看到网上CRC32的POLY有0x04C10DB7这个值的,它和正规的POLY值不同,需要注意!


 


颠倒过来,即是镜像,为什么要颠倒,后述。


 



 


 


三、           直接计算法   Straightforward CRC Implementation


 


“直接计算法”就是直接模拟上面的除法的过程,来得到余数即CRC!


上面的例子中,除数是4位,但最高位是要一直消掉的,所以我们只需要一个3位的寄存器就好了。


计算过程:


待测数据后扩展W=3个比特0,变成1111000;


寄存器初始化置0;


先在寄存器中移入数据111;


寄存器左移一位,并且右边移入下一位数据1。这样最高位1移出,由于最高位是1,故本次的商是1,要用除数1001来进行XOR,最高位肯定XOR得0,故不管它,只要用低3位001来进行XOR就可以,即001对寄存器进行XOR,寄存器中得到110,即第一次XOR后的结果(相当于是数据1111与生成项1001进行了一次XOR,并把最高位0消掉了)。 如果移出的最高位是0,则用0000来进行XOR(0 XOR 后,得到的还是原值)。


一直重复这个过程,就能得到最后余数了。


 


总共处理次数=商的位数=待测数据的位数-生成项位数+1+宽度W=待测数据的位数=4次。


 


我们假设待测数据是1101 0110 11,生成项是10011,假设有一个4 bits的寄存器,通过反复的移位和进行CRC的除法,最终该寄存器中的值就是我们所要求的余数。


             3   2   1   0   Bits


           +---+---+---+---+


 Pop   <-- |   |   |   |   | <----- Augmented message(已加0扩张的原始数据)


           +---+---+---+---+


        1    0   0   1   1   = The Poly 生成项


 


依据这个模型,我们得到了一个最最简单的算法:


   把register中的值置0.


   把原始的数据后添加w个0.


   While (还有剩余没有处理的数据)


      Begin


      把register中的值左移一位,读入一个新的数据并置于register最低位的位置。


      If (如果上一步的左移操作中的移出的一位是1)


         register = register XOR Poly.


      End


 


实际上就是模拟XOR除法的过程,即被测数据一位一位放到寄存器中来做除法。


比如生成项是10011,则生成的余数是4位XXXX,所以寄存器是4位。


待测数据是1101 0110 11,后面加上0000,即扩张4位,以容纳余数。


只要与生成项的0011做XOR就好了,最高位经过XOR肯定出0,可不用最高位。


过程如下:


待测数据先移4位即1101到寄存器中,准备开始除法。


第1次除法:寄存器中是1101,先从寄存器移出最高位1,移进下一位待测数据位0,则寄存器中是1010,由于移出的位是1,则需要与生成项的0011做XOR,得到1001,即做了第1次除法后,寄存器中是1001,这个就是余数。


第2次除法:寄存器中是1001,从寄存器移出最高位1,移进下一位待测数据位1,则寄存器中是0011,由于移出的位是1,则需要与生成项的0011做XOR,得到0000,即做了第2次除法后,寄存器中是0000,这个就是余数。


第3次除法:寄存器中是0000,从寄存器移出最高位0,移进下一位待测数据位1,则寄存器中是0001,由于移出的位是0,则需要不做XOR,直接下一步移位。也可以等同于:本次的商是0,0*生成项=0,即是0000与寄存器做XOR,得到寄存器的数不变,还是0001,即做了第3次除法后,寄存器中是0001,这个就是余数。


第4次除法:寄存器中是0001,从寄存器移出最高位0,移进下一位待测数据位0,则寄存器中是0010,由于移出的位是0,则需要不做XOR,直接下一步移位。


第5次除法:移位,不用做XOR,得到寄存器中是0101


第6次除法:移位,不用做XOR,得到寄存器中是1011


第7次除法:移位,移出的位是1,又要与生成项做XOR了


一直做下去。。。。。。直到最后,寄存器中的就是整个计算后的余数了。即CRC值。


 


注意:


这个算法,计算出的CRC32值,与WINRAR计算出来的不一样,为什么?算法是正确的,不用怀疑!只是CRC32正式算法还涉及到数据颠倒和初始化预置值等,后述。


 


程序实现:


程序1:


(注:网上下的程序是有错的,我有修改了,这里是正确的)


 


   //网上的程序经修改


BYTE POLY=0x13;                      //生成项,13H=10011,这样CRC是4比特


unsigned short data = 0x035B;    //待测数据是35BH,12比特,注意,数据不是16比特


unsigned short regi = 0x0000;    // load the register with zero bits


 


// augment the data by appending W(4) zero bits to the end of it.


//按CRC计算的定义,待测数据后加入4个比特0,以容纳4比特的CRC;


//这样共有16比特待测数据,从第5比特开始做除法,就要做16-5+1=12次XOR


data <<= 4;


 


// we do it bit after bit


for ( int cur_bit = 15; cur_bit >= 0; -- cur_bit )   //处理16次,前4次实际上只是加载数据


{


         // test the highest bit which will be poped later.


         ///     in fact, the 5th bit from right is the hightest bit here


    if ( ( ( regi >> 4 ) & 0x0001 ) == 0x1 )     regi = regi ^ POLY;


 


    regi <<= 1;   // shift the register


         // reading the next bit of the augmented data


    unsigned short tmp = ( data >> cur_bit ) & 0x0001;  //加载待测数据1比特到tmp中,tmp只有1比特


    regi |= tmp;       //这1比特加载到寄存器中


}


if ( ( ( regi >> 4 ) & 0x0001 ) == 0x1 )   regi = regi ^ POLY;        //做最后一次XOR


//这时, regi中的值就是CRC


 


 


程序2:我做的通用CRC计算程序:


 


_int64  POLY = 0x104C11DB7;      //生成项,需要含有最高位的"1",这样CRC是32比特


int crcbitnumber=32;             //crc是32比特


 


_int64  data = 0x31323334;           //待测数据,为字串"1234"


int  databitnumber=32;           //数据是32比特


 


_int64  regi = 0x0;                  // load the register with zero bits


 


// augment the data by appending W zero bits to the end of it.


//按CRC计算的定义,加入32个比特0,以容纳32比特的CRC;


//这样共有64比特待测数据,从第33比特开始做除法,就要做64-33+1=32次XOR


data <<= crcbitnumber;


 


// we do it bit after bit


for ( int cur_bit = databitnumber+crcbitnumber-1; cur_bit >= 0; -- cur_bit )   //处理64次(32比特待测数据+32比特扩展0),前32次是加载数据


{


         // test the highest bit which will be poped later.


         ///     in fact, the 5th bit from right is the hightest bit here


    if ( ( ( regi >> crcbitnumber ) & 0x0001 ) == 0x1 )     regi = regi ^ POLY;


 


    regi <<= 1;   // shift the register


         // reading the next bit of the augmented data


    unsigned short tmp = ( data >> cur_bit ) & 0x0001;       //加载待测数据1比特到tmp中,tmp只有1比特


    regi |= tmp;       //这1比特加载到寄存器中


}


if ( ( ( regi >> crcbitnumber ) & 0x0001 ) == 0x1 )   regi = regi ^ POLY;      //做最后一次XOR


//这时, regi中的值就是CRC


 



 


 


四、           驱动表法   Table-Driven Implementation


 


上面的“直接计算法”很直观,却非常的低效。为了加快它的速度,我们使它一次能处理大于4 bit的数据。一次能处理一个字节的数据的话,那就方便多了。


我们想要实现的32 bit的CRC校验。我们还是假设有和原来一样的一个4 "bit"的register,但它的每一位是一个8 bit的字节。


             3    2    1    0   Bytes


          +----+----+----+----+


 Pop! <-- |    |    |    |    | <----- Augmented message(扩展0后的数据)


          +----+----+----+----+


 


        1 <------32 bits------>  (生成项,暗含了一个最高位的“1”)


 


根据同样的原理我们可以得到如下的算法:


  While (还有剩余没有处理的数据)


      Begin


      检查register头字节,并取得它的值


      求不同偏移处多项式的XOR


      register左移一个字节,最右处存入新读入的一个字节


      把register的值 和 多项式的XOR结果 进行XOR运算


      End


 


可是为什么要这样作呢? 同样我们还是以一个简单的例子说明问题:


为了简单起见,我们假设一次只移出4个比特!而不是8个比特。


生成多项式为: 1 0101 1100,即宽度W=8,即CRC8,这样寄存器为8位


待测数据是1011 0100 1101


 


按正常的算法做:


将1011 0100放入寄存器中,然后开始计算CRC。


先将高4位移出寄存器:


当前register中的值:        0100 1101


4 bit应该被移出的值:  1011


生成多项式为:          1010 1110 0


 


 


第一步:


  Top  Register   (top指移出的数据)


  ---- --------


  1011 0100 1101               待测数


  1010 1110 0   + (CRC XOR)    POLY


  -------------


  0001 1010 1101               第一次XOR后的值


第二步:


这时,首4 bits 不为0说明没有除尽,要继续除:


  0001 1010 1101 


     1 0101 1100 + (CRC XOR)    将POLY右移3位后,再做XOR


  -------------


  0000 1111 0001                第二次XOR后的值


  ^^^^


这时,首4 bits 全0说明不用继续除了,结果满足要求了。


也就是说:待测数据与POLY相XOR,得到的结果再与POLY相XOR,POLY要适当移位,以消掉1。重复进行,直到结果满足要求。


 


 


下面,我们换一种算法,来达到相同的目的:


POLY与POLY自已先进行XOR,当然POLY要进行适当移位。使得得到的结果值的高4位与待测数据相同。


第一步:


 1010 1110 0           POLY


    1 0101 1100 +      右移3位后的POLY


 -------------


 1011 1011 1100        POLY与POLY自已进行XOR后得到的值


 



 


第二步:


 1011 1011 1100       POLY相XOR后得到的值


 1011 0100 1101+      待测数据


 -------------


 0000 11110001        得到的结果值和上面是一样的(说明可以先把POLY预先XOR好,再与待测数据XOR,就能得到结果)


 


 


结论:


现在我们看到,这二种算法计算的结果是一致的!这是基于XOR的交换律,即(a XOR b) XOR c = a XOR (b XOR c)。而后一种算法可以通过查表来快速完成,叫做“驱动表法”算法。


也就是说,根据4 bit被移出的值1011,我们就可以知道要用POLY自身XOR后得到的 1011 1011 1100 来对待测数据1011 0100 1101进行XOR,这样一次就能消掉4BIT待测数据。(注意蓝色的最高4位要一样,这样XOR后才能得0000,就能消掉了)


即1011对应1011 1011 1100,实际只需要用到后8位,即1011对应1011 1100


用查表法来得到,即1011作为索引值,查表,得到表值1011 1100。


表格可以预先生成。


 


这里是每次移出4位,则POLY与POLY进行XOR的组合有2^4=16种,即从0000到1111。


注意,POLY自身与自身相XOR时,要先对齐到和寄存器一样的长度,再XOR。相当于有12位进行XOR。


组合后的结果有16种:(黑色的0表示对齐到和寄存器一样的长度)


1.    0000 0000 0000        即表示待测数据移出的4位都是0,不需要与POLY相XOR,即相当于待测数据移出的4位后,与0000 0000 0000相XOR


2.    0001 0101 1100   即表示待测数据移出的4位是0001,需要与右移过3位的POLY相XOR


3.    0010 1011 1000 


4.    0010 1011 1000 与 0001 0101 1100  相XOR,XOR后前4位为0011  即表示待测数据移出的4位后,需要与POLY进行二次相XOR,结果才能满足要求。


5.    0101 0111 0000 与 0001 0101 1100相XOR,                  XOR后前4位为0100


6.    0101 0111 0000 ,                                     前4位为      0101


7.    0101 0111 0000 与 0010 1011 1000、0001 0101 1100 相XOR, XOR后前4位为0110


8.    0101 0111 0000 与 0010 1011 1000,                       XOR后前4位为0111


9.    1010 1110 0000 与 0010 1011 1000相XOR,                 XOR后前4位为1000


10.1010 1110 0000 与 0010 1011 1000、0001 0101 1100相XOR,  XOR后前4位为1001


11.1010 1110 0000,                                     前4位为      1010


12.1010 1110 0000 与 0001 0101 1100相XOR,                 XOR后前4位为1011


13.1010 1110 0000 与 0101 0111 0000、0010 1011 1000、0001 0101 1100相XOR, XOR后前4位为1100


14.1010 1110 0000 与 0101 0111 0000、0010 1011 1000相XOR,  XOR后前4位为1101


15.1010 1110 0000 与 0101 0111 0000、0001 0101 1100相XOR,  XOR后前4位为1110


16.1010 1110 0000 与 0101 0111 0000相XOR,                 XOR后前4位为1111


 


以XOR后得到的结果的前4位做为索引值,以XOR后得到的结果的后8位做为表值,生成一张表,即:


TABLE[0]=0000 0000B;


TABLE[1]=0101 1100B;


TABLE[2]=1011 1000B;


TABLE[3]=[(0010 1011 1000B ^ 0001 0101 1100B) >> 4 ] & 0xff


....


这张表我叫它为“直接查询表”。


 


就是说,一次移出的待测数据的4位bit,有2^4个值,即0000,0001,0010,....,1111,根据这个值来查表,找到相应的表值,再用表值来XOR寄存器中的待测数据。


 


所以,如果一次移出待测数据的8位bit,即一次进行一个字节的计算,则表格有2^8=256个表值。


CRC16和CRC32都是一次处理一个字节的,所以它们的查询表有256个表值。


 


“驱动表法”算法为:


              3    2    1    0   Bytes


           +----+----+----+----+


    +-----<|    |    |    |    | <----- Augmented message(扩展0后的数据)


    |      +----+----+----+----+


    |      MSB       ^        LSB


    |                |


    |               XOR


    |                |


    |     0+----+----+----+----+


查表v      +----+----+----+----+


    |      +----+----+----+----+


    |      +----+----+----+----+


    |      +----+----+----+----+


    |      +----+----+----+----+


    |      +----+----+----+----+


    +----->+----+----+----+----+


           +----+----+----+----+


           +----+----+----+----+


           +----+----+----+----+


           +----+----+----+----+


        255+----+----+----+----+


 


描述:


1:register左移一个字节,从原始数据中读入一个新的字节.


2:利用刚从register移出的字节作为下标定位 table 中的一个32位的值


3:把这个值XOR到register中。


4:如果还有未处理的数据则回到第一步继续执行。


用C可以写成这样:


   r=0;            //r是寄存器,先初始化为0


   while (len--)   //len是已扩展0之后的待测数据的长度


     {


      byte t = (r >> 24) & 0xFF;


      r = (r << 8) | *p++;     //p是指向待测数据的指针(待测数据需已经扩展过0)


      r^=table[t];        //table是查询表


     }


这个代码可以优化为:


  r=0;           //r是寄存器,先初始化为0


   while (len--) //len是已扩展0之后的待测数据的字节长度


          r = ((r << 8) | *p++) ^ t[(r >> 24) & 0xFF]; //p是指向待测数据的指针(待测数据需已经扩展过0),t是查询表


 


注意:


这个“驱动表法”算法和“直接计算法”是完全一样的,不仅结果完全一样,处理方式也是完全一样的,所以“驱动表法”可以完全替代直接计算法”


原始数据都需要先用0扩展W位;最开始的几次循环的实质都只是先将待测数据移动到寄存器中去而已;


会发现,这个算法用到的“直接查询表”的表值,和网上公开的查询表(我叫它“正规查询表”)的表值不一样!为什么?因为网上公开的正规查询表是用于“颠倒”算法的!后述。


会发现,这个算法,计算出的CRC32值,同样与WINRAR计算出来的不一样。


 


生成的“直接查询表”的内容是:


CRC16直接查询表


00H    0000  8005  800F  000A


04H    801B  001E  0014  8011


08H    8033  0036  003C  8039


0CH    0028  802D  8027  0022


10H    8063  0066  006C  8069


14H    0078  807D  8077  0072


18H    0050  8055  805F  005A


1CH    804B  004E  0044  8041


20H    80C3  00C6  00CC  80C9


24H    00D8  80DD  80D7  00D2


28H    00F0  80F5  80FF  00FA


2CH    80EB  00EE  00E4  80E1


30H    00A0  80A5  80AF  00AA


34H    80BB  00BE  00B4  80B1


38H    8093  0096  009C  8099


3CH    0088  808D  8087  0082


40H    8183  0186  018C  8189


44H    0198  819D  8197  0192


48H    01B0  81B5  81BF  01BA


4CH    81AB  01AE  01A4  81A1


50H    01E0  81E5  81EF  01EA


54H    81FB  01FE  01F4  81F1


58H    81D3  01D6  01DC  81D9


5CH    01C8  81CD  81C7  01C2


60H    0140  8145  814F  014A


64H    815B  015E  0154  8151


68H    8173  0176  017C  8179


6CH    0168  816D  8167  0162


70H    8123  0126  012C  8129


74H    0138  813D  8137  0132


78H    0110  8115  811F  011A


7CH    810B  010E  0104  8101


80H    8303  0306  030C  8309


84H    0318  831D  8317  0312


88H    0330  8335  833F  033A


8CH    832B  032E  0324  8321


90H    0360  8365  836F  036A


94H    837B  037E  0374  8371


98H    8353  0356  035C  8359


9CH    0348  834D  8347  0342


A0H    03C0  83C5  83CF  03CA


A4H    83DB  03DE  03D4  83D1


A8H    83F3  03F6  03FC  83F9


ACH    03E8  83ED  83E7  03E2


B0H    83A3  03A6  03AC  83A9


B4H    03B8  83BD  83B7  03B2


B8H    0390  8395  839F  039A


BCH    838B  038E  0384  8381


C0H    0280  8285  828F  028A


C4H    829B  029E  0294  8291


C8H    82B3  02B6  02BC  82B9


CCH    02A8  82AD  82A7  02A2


D0H    82E3  02E6  02EC  82E9


D4H    02F8  82FD  82F7  02F2


D8H    02D0  82D5  82DF  02DA


DCH    82CB  02CE  02C4  82C1


E0H    8243  0246  024C  8249


E4H    0258  825D  8257  0252


E8H    0270  8275  827F  027A


ECH    826B  026E  0264  8261


F0H    0220  8225  822F  022A


F4H    823B  023E  0234  8231


F8H    8213  0216  021C  8219


FCH    0208  820D  8207  0202


 


 


CRC32直接查询表


00H    00000000  04C11DB7  09823B6E  0D4326D9


04H    130476DC  17C56B6B  1A864DB2  1E475005


08H    2608EDB8  22C9F00F  2F8AD6D6  2B4BCB61


0CH    350C9B64  31CD86D3  3C8EA00A  384FBDBD


10H    4C11DB70  48D0C6C7  4593E01E  4152FDA9


14H    5F15ADAC  5BD4B01B  569796C2  52568B75


18H    6A1936C8  6ED82B7F  639B0DA6  675A1011


1CH    791D4014  7DDC5DA3  709F7B7A  745E66CD


20H    9823B6E0  9CE2AB57  91A18D8E  95609039


24H    8B27C03C  8FE6DD8B  82A5FB52  8664E6E5


28H    BE2B5B58  BAEA46EF  B7A96036  B3687D81


2CH    AD2F2D84  A9EE3033  A4AD16EA  A06C0B5D


30H    D4326D90  D0F37027  DDB056FE  D9714B49


34H    C7361B4C  C3F706FB  CEB42022  CA753D95


38H    F23A8028  F6FB9D9F  FBB8BB46  FF79A6F1


3CH    E13EF6F4  E5FFEB43  E8BCCD9A  EC7DD02D


40H    34867077  30476DC0  3D044B19  39C556AE


44H    278206AB  23431B1C  2E003DC5  2AC12072


48H    128E9DCF  164F8078  1B0CA6A1  1FCDBB16


4CH    018AEB13  054BF6A4  0808D07D  0CC9CDCA


50H    7897AB07  7C56B6B0  71159069  75D48DDE


54H    6B93DDDB  6F52C06C  6211E6B5  66D0FB02


58H    5E9F46BF  5A5E5B08  571D7DD1  53DC6066


5CH    4D9B3063  495A2DD4  44190B0D  40D816BA


60H    ACA5C697  A864DB20  A527FDF9  A1E6E04E


64H    BFA1B04B  BB60ADFC  B6238B25  B2E29692


68H    8AAD2B2F  8E6C3698  832F1041  87EE0DF6


6CH    99A95DF3  9D684044  902B669D  94EA7B2A


70H    E0B41DE7  E4750050  E9362689  EDF73B3E


74H    F3B06B3B  F771768C  FA325055  FEF34DE2


78H    C6BCF05F  C27DEDE8  CF3ECB31  CBFFD686


7CH    D5B88683  D1799B34  DC3ABDED  D8FBA05A


80H    690CE0EE  6DCDFD59  608EDB80  644FC637


84H    7A089632  7EC98B85  738AAD5C  774BB0EB


88H    4F040D56  4BC510E1  46863638  42472B8F


8CH    5C007B8A  58C1663D  558240E4  51435D53


90H    251D3B9E  21DC2629  2C9F00F0  285E1D47


94H    36194D42  32D850F5  3F9B762C  3B5A6B9B


98H    0315D626  07D4CB91  0A97ED48  0E56F0FF


9CH    1011A0FA  14D0BD4D  19939B94  1D528623


A0H    F12F560E  F5EE4BB9  F8AD6D60  FC6C70D7


A4H    E22B20D2  E6EA3D65  EBA91BBC  EF68060B


A8H    D727BBB6  D3E6A601  DEA580D8  DA649D6F


ACH    C423CD6A  C0E2D0DD  CDA1F604  C960EBB3


B0H    BD3E8D7E  B9FF90C9  B4BCB610  B07DABA7


B4H    AE3AFBA2  AAFBE615  A7B8C0CC  A379DD7B


B8H    9B3660C6  9FF77D71  92B45BA8  9675461F


BCH    8832161A  8CF30BAD  81B02D74  857130C3


C0H    5D8A9099  594B8D2E  5408ABF7  50C9B640


C4H    4E8EE645  4A4FFBF2  470CDD2B  43CDC09C


C8H    7B827D21  7F436096  7200464F  76C15BF8


CCH    68860BFD  6C47164A  61043093  65C52D24


D0H    119B4BE9  155A565E  18197087  1CD86D30


D4H    029F3D35  065E2082  0B1D065B  0FDC1BEC


D8H    3793A651  3352BBE6  3E119D3F  3AD08088


DCH    2497D08D  2056CD3A  2D15EBE3  29D4F654


E0H    C5A92679  C1683BCE  CC2B1D17  C8EA00A0


E4H    D6AD50A5  D26C4D12  DF2F6BCB  DBEE767C


E8H    E3A1CBC1  E760D676  EA23F0AF  EEE2ED18


ECH    F0A5BD1D  F464A0AA  F9278673  FDE69BC4


F0H    89B8FD09  8D79E0BE  803AC667  84FBDBD0


F4H    9ABC8BD5  9E7D9662  933EB0BB  97FFAD0C


F8H    AFB010B1  AB710D06  A6322BDF  A2F33668


FCH    BCB4666D  B8757BDA  B5365D03  B1F740B4


 


 


 


 


“驱动表法”的程序:


 


// 注意:因生成项POLY最高位一定为“1”,故略去最高位的"1",


unsigned short cnCRC_16 = 0x8005;         // CRC-16 = X16 + X15 + X2 + X0


unsigned short cnCRC_CCITT = 0x1021; // CRC-CCITT = X16 + X12 + X5 + X0,据说这个 16 位 CRC 多项式比上一个要好


unsigned long cnCRC_32 = 0x04C11DB7; //采用正规的CRC32的POLY


unsigned long Table_CRC16[256];           // CRC16 表


unsigned long Table_CRC32[256];           // CRC32 表


 


 


// 构造 16 位 CRC 表 "直接查询表"


unsigned short i16, j16;


unsigned short nData16;


unsigned short nAccum16;


for ( i16 = 0; i16 < 256; i16++ )


{


     nData16 = ( unsigned short )( i16 << 8 );


     nAccum16 = 0;


     for ( j16 = 0; j16 < 8; j16++ )


     {


         if ( ( nData16 ^ nAccum16 ) & 0x8000 )


         nAccum16 = ( nAccum16 << 1 ) ^ cnCRC_16;   //也可以用cnCRC_CCITT


         else


         nAccum16 <<= 1;


         nData16 <<= 1;


     }


     Table_CRC16[i16] = ( unsigned long )nAccum16;


}


 


// 构造 32 位 CRC 表 "直接查询表"


unsigned long i32, j32;


unsigned long nData32;


unsigned long nAccum32;


for ( i32 = 0; i32 < 256; i32++ )


{


     nData32 = ( unsigned long )( i32 << 24 );


     nAccum32 = 0;


     for ( j32 = 0; j32 < 8; j32++ )


     {


         if ( ( nData32 ^ nAccum32 ) & 0x80000000 )


              nAccum32 = ( nAccum32 << 1 ) ^ cnCRC_32;


         else


              nAccum32 <<= 1;


         nData32 <<= 1;


     }


     Table_CRC32[i32] = nAccum32;


}


 


 


unsigned char aData[512]={0x31,0x32,0x33,0x34};              //待测数据,为字串"1234"


unsigned long aSize;


unsigned long i;


unsigned char *point;


 


// 计算 16 位 CRC 值,CRC-16 或 CRC-CCITT


//Table-Driven驱动表法,需要用到“直接查询表”(不能用“正规查询表”);待测数据需扩展0


unsigned short CRC16_1;


aSize=4;               //数据长度字节(不包含扩展0)


CRC16_1 = 0;           //寄存器归0


point=aData;


while (aSize--)  


     CRC16_1 = ((CRC16_1 << 8) | *point++) ^ Table_CRC16[(CRC16_1 >> 8) & 0xFF]; 


for ( i = 0; i < 2; i++ )  


     CRC16_1 = ((CRC16_1 << 8) ) ^ Table_CRC16[(CRC16_1 >> 8) & 0xFF];  //加入2字节的扩展0


//这时, CRC16_1中的值就是CRC


 


 


 


// 计算 32 位 CRC-32 值


//Table-Driven驱动表法,需要用到“直接查询表”(不能用“正规查询表”);待测数据需扩展0


unsigned long CRC32_1;


aSize=4;               //数据长度字节(不包含扩展0)


CRC32_1=0x0;           //寄存器归0


point=aData;


while (aSize--)  


     CRC32_1 = ((CRC32_1 << 8) | *point++) ^ Table_CRC32[(CRC32_1 >> 24) & 0xFF]; 


for ( i = 0; i < 4; i++ )   CRC32_1 = ((CRC32_1 << 8) ) ^ Table_CRC32[(CRC32_1 >> 24) & 0xFF];//加入4字节的扩展0


//这时, CRC32_1中的值就是CRC


 


 


 


打印查询表的语句:(在TC中实现)


for ( i16 = 0; i16 < 256; i16++ )


{


printf("%02xh    %04x  %04x  %04x  %04x\n",i16,( unsigned short)Table_CRC16[i16],( unsigned short)Table_CRC16[i16+1],( unsigned short)Table_CRC16[i16+2],( unsigned short)Table_CRC16[i16+3]);


i16++;


i16++;


i16++;


}


 


for ( i16 = 0; i16 < 256; i16++ )


{


printf("%02xh    %08lx  %08lx  %08lx  %08lx\n",i16,Table_CRC32[i16],Table_CRC32[i16+1],Table_CRC32[i16+2],Table_CRC32[i16+3]);


i16++;


i16++;


i16++;


}


 



 


 


五、           直驱表法  DIRECT TABLE ALGORITHM


 


对于上面的算法:


1.对于尾部的w/8个扩展0字节,事实上它们的作用只是确保所有的原始数据都已被送入register,并且被算法处理。


2.如果register中的初始值是0,那么开始的4次循环,作用只是把原始数据的头4个字节送入寄存器,而没有进行真正的除法操作。就算初始值不是0(我注:这里并没有说是“寄存器的初始值”,而是指算法开始时的“初始化值”),开始的4次循环也只是把原始数据的头4个字节移入到register中,然后再把它们和一个特定常数相XOR(我注:即这时进行初始化操作,后述)。


3.因为有交换律:(A xor B) xor C = A xor (B xor C)


这些信息意味着,上面提到的算法可以被优化,待测数据不需要先循环几次进入到寄存器中后再进行处理,而是数据直接就可以处理到寄存器中。


可以这样:数据可以先与刚从寄存器移出的字节相XOR,用得到的结果值进行查表,再用表值XOR寄存器。这引出了以下“直驱表法”算法:


    +-----<Message (non augmented) 待测数据(不用扩展0)


    |


    v         3    2    1    0   Bytes


    |      +----+----+----+----+


   XOR----<|    |    |    |    |


    |      +----+----+----+----+


    |      MSB       ^        LSB


    |                |


    |               XOR


    |                |


    |     0+----+----+----+----+


查表v      +----+----+----+----+


    |      +----+----+----+----+


    |      +----+----+----+----+


    |      +----+----+----+----+


    |      +----+----+----+----+


    |      +----+----+----+----+


    +----->+----+----+----+----+


           +----+----+----+----+


           +----+----+----+----+


           +----+----+----+----+


           +----+----+----+----+


        255+----+----+----+----+


 


 


“直驱表法”算法:


1.          Shift the register left by one byte, reading in a new message byte.(我注:老外的这句话有问题,应只是Shift the register left by one byte,而不将新的信息字节读入register中。所以翻译为:寄存器左移一个字节)


2.          XOR the top byte just rotated out of the register with the next message byte to yield an index into the table ([0,255]). 将刚从register移出的字节与新的信息字节相XOR,结果值作为定位索引,从查询表中取得相应的表值。


3.          XOR the table value into the register. 把表值XOR到register中


4.          Goto 1 iff more augmented message bytes. 如果还有未处理的数据则回到第一步继续执行。


用C可以写成这样:


   r=0;               //r是寄存器,先初始化为0


   while (len--)             //len是待测数据(不用扩展0)的字节长度


          r = (r<<8) ^ t[(r >> 24) ^ *p++];  //p是指向待测数据的指针,t是查询表


算法相当于:


寄存器左移出1字节,右边补0;


移出的字节与待测信息字节进行XOR,根据结果值查表,得表值


表值与寄存器进行XOR


 


注意:


这个“直驱表法”算法的数学原理我也不明白,但它肯定是从“驱动表法”算法推导出来的,得到的CRC结果值是完全一样的!只是它们的处理方式是不一样的。


这个算法很方便,原始数据不需要先用0扩展W位;


这个算法很方便,第一次循环就能够处理待测数据并在寄存器中生成结果,而不单纯只是将数据移动到寄存器中去而已;


这个算法,用的是和“驱动表法”同样的“直接查询表”,而不是“正规查询表”。


正是由于处理方式不一样,所以如果寄存器初始化预置值不为0,那么本算法可不受影响,而“驱动表法”则需要将预置值再另外XOR到寄存器中。后述。


 


 


 


 


 


“直驱表法”的程序:


 


// 注意:因生成项POLY最高位一定为“1”,故略去最高位的"1",


unsigned short cnCRC_16 = 0x8005;         // CRC-16 = X16 + X15 + X2 + X0


unsigned short cnCRC_CCITT = 0x1021; // CRC-CCITT = X16 + X12 + X5 + X0,据说这个 16 位 CRC 多项式比上一个要好


unsigned long cnCRC_32 = 0x04C11DB7; //采用正规的CRC32的POLY


unsigned long Table_CRC16[256];           // CRC16 表


unsigned long Table_CRC32[256];           // CRC32 表


 


 


// 构造 16 位 CRC 表 "直接查询表"


unsigned short i16, j16;


unsigned short nData16;


unsigned short nAccum16;


for ( i16 = 0; i16 < 256; i16++ )


{


     nData16 = ( unsigned short )( i16 << 8 );


     nAccum16 = 0;


     for ( j16 = 0; j16 < 8; j16++ )


     {


         if ( ( nData16 ^ nAccum16 ) & 0x8000 )


         nAccum16 = ( nAccum16 << 1 ) ^ cnCRC_16;   //也可以用cnCRC_CCITT


         else


         nAccum16 <<= 1;


         nData16 <<= 1;


     }


     Table_CRC16[i16] = ( unsigned long )nAccum16;


}


 


// 构造 32 位 CRC 表 "直接查询表"


unsigned long i32, j32;


unsigned long nData32;


unsigned long nAccum32;


for ( i32 = 0; i32 < 256; i32++ )


{


     nData32 = ( unsigned long )( i32 << 24 );


     nAccum32 = 0;


     for ( j32 = 0; j32 < 8; j32++ )


     {


         if ( ( nData32 ^ nAccum32 ) & 0x80000000 )


              nAccum32 = ( nAccum32 << 1 ) ^ cnCRC_32;


         else


              nAccum32 <<= 1;


         nData32 <<= 1;


     }


     Table_CRC32[i32] = nAccum32;


}


 


 


unsigned char aData[512]={0x31,0x32,0x33,0x34};              //待测数据,为字串"1234"


unsigned long aSize;


unsigned long i;


unsigned char *point;


 


// 计算 16 位 CRC 值,CRC-16 或 CRC-CCITT


//DIRECT TABLE直驱表法,需要用到“直接查询表”(不能用“正规查询表”);待测数据不需要扩展0


unsigned short CRC16_2;


aSize=4;               //数据长度字节(数据不用扩展0了)


CRC16_2 = 0;           //寄存器中预置初始值


point=aData;          


for ( i = 0; i < aSize; i++ )


     CRC16_2 = ( CRC16_2 << 8 ) ^ ( unsigned short ) Table_CRC16[( CRC16_2 >> 8 ) ^ *point++];


//这时, CRC16_2中的值就是CRC


 


 


// 计算 32 位 CRC-32 值


//DIRECT TABLE直驱表法,需要用到“直接查询表”(不能用“正规查询表”);待测数据不需要扩展0


unsigned long CRC32_2;


aSize=4;               //数据长度字节(数据不用扩展0了)


CRC32_2 = 0x0;              //寄存器中预置初始值


point=aData;


for ( i = 0; i < aSize; i++ )


     CRC32_2 = ( CRC32_2 << 8 ) ^ Table_CRC32[( CRC32_2 >> 24 ) ^ *point++];


//这时, CRC32_2中的值就是CRC


 


 



 


 


六、           CRC的参数模型


 


实际上,这时已经可以计算出与WINRAR相同的CRC32值了。但是会发现,算出的结果和WINRAR是不一样的,为什么?因为不仅仅是生成项POLY会影响到CRC值,还有很多参数会影响到最终的CRC值!


CRC计算,需要有CRC参数模型,比如CRC32的参数模型是:


   Name   : "CRC-32"


   Width  : 32


   Poly   : 04C11DB7


   Init   : FFFFFFFF


   RefIn  : True


   RefOut : True


   XorOut : FFFFFFFF


   Check  : CBF43926


解释:


NAME


名称


WIDTH


宽度,即CRC比特数


POLY


生成项的简写。以16进制表示,即是0x04C11DB7。忽略了最高位的"1",即完整的生成项是0x104C11DB7。


重要的一点是,这是“未颠倒”的生成项!前面说过,“颠倒的”生成项是0xEDB88320。


 


INIT


这是算法开始时寄存器的初始化预置值,十六进制表示。


这个值可以直接赋值给“直驱表法”算法中的寄存器,作为寄存器的初始值!


而对于“驱动表法”算法及“直接计算法”,寄存器的初始值必须是0!前面几次循环先将待测数据移入到寄存器中,当寄存器装满后,再用这个初始化预置值去XOR寄存器,这样寄存器就被这个值初始化了!


这点很重要!!如果在“驱动表法”算法开始时,寄存器的初始值不为0,那么寄存器中的值就会相当于是待测数据了,这样算出的CRC结果就不对了!我们的目的是用预置值去初始化寄存器,而不是将预置值作为待测数据去处理!


REFIN


这个值是真TRUE或假FALSE。


如果这个值是FALSE,表示待测数据的每个字节都不用“颠倒”,即BIT7仍是作为最高位,BIT0作为最低位。


如果这个值是TRUE,表示待测数据的每个字节都要先“颠倒”,即BIT7作为最低位,BIT0作为最高位。


REFOUT


这个值是真TRUE或假FALSE。


如果这个值是FALSE,表示计算结束后,寄存器中的值直接进入XOROUT处理即可。


如果这个值是TRUE,表示计算结束后,寄存器中的值要先“颠倒”,再进入XOROUT处理。注意,这是将整个寄存器的值颠倒,因为寄存器的各个字节合起来表达了一个值,如果只是对各个字节各自颠倒,那结果值就错误了。


XOROUT


这是W位长的16进制数值。


这个值与经REFOUT后的寄存器的值相XOR,得到的值就是最终正式的CRC值!


CHECK


这不是定义值的一部分,这只是字串"123456789"用这个CRC参数模型计算后得到的CRC值,作为参考。


 


我们发现,CRC32模型的Init=0xFFFFFFFF,就是说寄存器要用0xFFFFFFFF进行初始化,而不是0。


为什么?因为待测数据的内容和长度是随机的,如果寄存器初始值为0,那么,待测字节是1字节的0x00,与待测字节是N字节的0x00,计算出来的CRC32值都是0,那CRC值就没有意义了!所以寄存器用0xFFFFFFFF进行初始化,就可以避免这个问题了!


 


RefIn=True,表示输入数据的每个字节需要“颠倒”!为什么要“颠倒”,因为很多硬件在发送时是先发送最低位LSB的!比如UART等。


字节顺序不用颠倒,只是每个字节内部的比特进行颠倒。例如待测的字串是"1234",这时也是一样先处理"1",再处理"2",一直到处理"4"。处理字符"1"时,它是0x31,即0011 0001,需要先将它颠倒,变成低位在前,即1000 1100,即0x8C,再进行处理。


也就是说,待处理的数据是0x31 32 33 34,颠倒后就变成0x8C 4C CC 2C,再进行CRC计算。


 


RefOut=True,表示计算完成后,要将寄存器中的值再颠倒。注意,这是将整个寄存器的值颠倒,即如果寄存器中的值是0x31 32 33 34,颠倒后就变成0x2C CC 4C 8C!


 


XorOut=FFFFFFFF,表示还需要将结果值与0xffffffff进行XOR,这样就得到最终的CRC32值了!


 


我们直接用“直驱表法”,计算字串"1234"的CRC32值。


 


程序如下:


要先做一个颠倒比特的子程序:


unsigned long int Reflect(unsigned long int ref, char ch)


{


unsigned long int value=0;


     // 交换bit0和bit7,bit1和bit6,类推


for(int i = 1; i < (ch + 1); i++)


{


     if(ref & 1)


         value |= 1 << (ch - i);


     ref >>= 1;


}


return value;


}


 


在主程序中的程序:


// 注意:因生成项POLY最高位一定为“1”,故略去最高位的"1",


unsigned long cnCRC_32 = 0x04C11DB7; //采用正规的CRC32的POLY


unsigned long Table_CRC32[256];           // CRC32 表


 


// 构造 32 位 CRC 表 "直接查询表"


unsigned long i32, j32;


unsigned long nData32;


unsigned long nAccum32;


for ( i32 = 0; i32 < 256; i32++ )


{


     nData32 = ( unsigned long )( i32 << 24 );


     nAccum32 = 0;


     for ( j32 = 0; j32 < 8; j32++ )


     {


         if ( ( nData32 ^ nAccum32 ) & 0x80000000 )


              nAccum32 = ( nAccum32 << 1 ) ^ cnCRC_32;


         else


              nAccum32 <<= 1;


         nData32 <<= 1;


     }


     Table_CRC32[i32] = nAccum32;


}


 


unsigned char aData[512]={0x31,0x32,0x33,0x34};              //待测数据,为字串"1234"


unsigned long aSize;


unsigned long i;


unsigned char *point;


unsigned char chtemp;


// 计算 32 位 CRC-32 值


//Table-Driven驱动表法,需要用到“直接查询表”(不能用“正规查询表”);待测数据需扩展0


unsigned long ii;


unsigned long CRC32_1;


aSize=4;               //数据长度字节(不包含扩展0)


CRC32_1=0x0;           //寄存器归0


point=aData;


ii=0;


while (aSize--)  


{


     chtemp=*point++;


     chtemp=(unsigned char)Reflect(chtemp, 8);      //将数据字节内部的比特进行颠倒


     CRC32_1 = ((CRC32_1 << 8) | chtemp) ^ Table_CRC32[(CRC32_1 >> 24) & 0xFF]; 


     ii++;


     if (ii==4) CRC32_1=CRC32_1^0xffffffff;//当寄存器装满4个字节后,用预置值0xffffffff去XOR寄存器,这样寄存器就被这个值初始化了!


}


for ( i = 0; i < 4; i++ )  


{


     CRC32_1 = ((CRC32_1 << 8) ) ^ Table_CRC32[(CRC32_1 >> 24) & 0xFF];  //加入4字节的扩展0


     ii++;


     if (ii==4) CRC32_1=CRC32_1^0xffffffff;//如果待测数据小于4字节,则只有在这里寄存器才会装满4个字节,才进行初始化


}


CRC32_1=Reflect(CRC32_1, 32);    //颠倒寄存器的值


CRC32_1=CRC32_1^0xffffffff;      //寄存器的值与0xffffffff异或


//这时, CRC32_1中的值就是CRC


 


 


//DIRECT TABLE直驱表法,需要用到“直接查询表”(不能用“正规查询表”);待测数据不需要扩展0


unsigned long CRC32_2;


aSize=4;               //数据长度字节(数据不用扩展0了)


CRC32_2 = 0xffffffff;            //寄存器中直接预置初始值0xffffffff即可


point=aData;


for ( i = 0; i < aSize; i++ )


{


     chtemp=*point++;


     chtemp=(unsigned char)Reflect(chtemp, 8);      //将数据字节内部的比特进行颠倒


     CRC32_2 = ( CRC32_2 << 8 ) ^ Table_CRC32[( CRC32_2 >> 24 ) ^ chtemp];


}


 


CRC32_2=Reflect(CRC32_2, 32);    //颠倒寄存器的值


CRC32_2=CRC32_2^0xffffffff;     //寄存器的值与0xffffffff异或


//这时, CRC32_2中的值就是CRC


 


 


得到的结果与WINRAR的计算结果是完全一样的!成功了!


 


 


 


其它的CRC参数模型:


   Name   : "CRC-16"


   Width  : 16


   Poly   : 8005


   Init   : 0000


   RefIn  : True


   RefOut : True


   XorOut : 0000


   Check  : BB3D


 


   Name   : "CRC-16/CITT"


   Width  : 16


   Poly   : 1021


   Init   : FFFF


   RefIn  : False


   RefOut : False


   XorOut : 0000


   Check  : ?


 


   Name   : "XMODEM"


   Width  : 16


   Poly   : 8408


   Init   : 0000


   RefIn  : True


   RefOut : True


   XorOut : 0000


   Check  : ?


 


   Name   : "ARC"


   Width  : 16


   Poly   : 8005


   Init   : 0000


   RefIn  : True


   RefOut : True


   XorOut : 0000


   Check  : ?


 



 


 


七、           最后的战斗-“颠倒的直驱表法”算法 "Reflected" Table-Driven Implementations


 


颠倒,也就是镜像!


CRC32要求输入的字节要颠倒,那么在程序中,在对每个字节处理前,还要先把这个字节先颠倒一下,再处理,那不是超级麻烦!


所以就把“直驱表法”算法颠倒一下(查询表颠倒),那么算法就可以直接处理不颠倒的字节了,就方便多了。


我们把算法照镜子:


         “直驱表法”                    镜子               “颠倒的直驱表法”


 



    +-----<Message (non augmented) 待测数据(要颠倒)


    |


    v         3    2    1    0   Bytes


    |      +----+----+----+----+


   XOR----<|    |    |    |    |


    |      +----+----+----+----+


    |      MSB       ^        LSB


    |                |


    |               XOR


    |                |


    |   00H +----+----+----+----+


查表v  01H +----+----+----+----+ 04C11DB7H


    |   02H +----+----+----+----+


    |   03H +----+----+----+----+


    |       +----+----+----+----+


    |       +----+----+----+----+


    |       +----+----+----+----+


    +-----> +----+----+----+----+


            +----+----+----+----+


        80H +----+----+----+----+ 690CE0EEH


            +----+----+----+----+


            +----+----+----+----+


        FFH +----+----+----+----+


          索引值和表值都不颠倒


 


待测数据(不颠倒)Message (non augmented) >-----+


                                      |


      Bytes   3    2    1    0        v


           +----+----+----+----+      |


           |    |    |    |    |>----XOR


           +----+----+----+----+      |


           MSB       ^        LSB     |


                     |                |


                    XOR               |


                     |                |


           +----+----+----+----+ 00H  |


 EDB88320H +----+----+----+----+ 80H  v查表


           +----+----+----+----+ C0H  |


           +----+----+----+----+ 20H  |


           +----+----+----+----+ A0H  |


           +----+----+----+----+      |


           +----+----+----+----+      |


           +----+----+----+----+<-----+


           +----+----+----+----+


77073096H +----+----+----+----+ 01H


           +----+----+----+----+


           +----+----+----+----+


           +----+----+----+----+255


                      索引值和表值都颠倒

 

 



 


“颠倒的直驱表法”用的查询表,是网上可以找到的查询表,我叫它“正规查询表”,实际就是“颠倒的直接查询表”。对应关系是:


“直接查询表”                            “正规查询表”(颠倒的查询表)


0000 0000(00H)                         0000 0000(00H)


0000 0001(01H)表值04C11DB7H           1000 0000(80H)表值EDB88320H


0000 0010(02H)                         0100 0000(C0H)


0000 0011(03H)                         1100 0000(20H)


0000 0100(04H)                         0010 0000(A0H)


...


1000 0000(80H)表值690CE0EEH            0000 0001(01H)表值77073096H


...


1111 1111(FFH)                          FFFF FFFF(FFH)


 


也就是说,将“直接查询表”的索引值和表值直接镜像就是“正规查询表”。


比如,直接查询表的[01H]= 04C11DB7H,因为01H镜像后是80H,04C11DB7H镜像后是EDB88320H,就得到正规查询表的[80H]= EDB88320H。


 


举例来说,假设待测的原始数据是10H,简单起见,不考虑寄存器移出的字节的影响(即假设它是00H):


“直驱表法”,原始数据先颠倒为01H,根据01H查表得04C11DB7H,寄存器移出的字节是向左移。


“颠倒的直驱表法”,    直接根据原始数据10H查表得EDB88320H,寄存器移出的字节是向右移。


可见,这时这二个方法本质上是一样的。


 


对于“直驱表法”,颠倒的数据用不颠倒的表索引值、得到不颠倒的表值寄存器进入寄存器,得到的寄存器结果值是不颠倒的,还要再颠倒,变成“颠倒的CRC”。


对于“颠倒的直驱表法”,不颠倒的数据用颠倒的表索引值、得到颠倒的表值寄存器进入寄存器,得到的寄存器结果值就已经是“颠倒的CRC”,不用再颠倒了。


可见,对于REFIN=TRUE并且REFOUT=TRUE的CRC模型来说(注意这个先决条件),就可以直接用“颠倒的直驱表法”来代替“直驱表法”,这样原始数据的比特不用镜像,处理起来就很简单。


 


那是不是说:不颠倒的数据用不颠倒的表索引值和表值,把得到的寄存器结果值颠倒一下,就能得到和颠倒的数据一样的计算结果?不可能的。颠倒的数据和不颠倒的数据是完全不一样的数据,得到的结果完全两码事。


 


注意:


先决条件是REFIN=TRUE并且REFOUT=TRUE的CRC参数模型。


颠倒的直驱表法”用的是“正规查询表”。


寄存器的初始化预置值也要颠倒。


待测数据每个字节的比特不用颠倒,因为算法的其他部分都做过颠倒处理了。


待测数据串肯定不用颠倒,即待测的字串是"1234",这时也是一样先处理"1",再处理"2",一直到处理"4"。


 


算法如下:


1. 将寄存器向右移动一个字节。


2. 将刚移出的那个字节与待测数据中的新字节做XOR运算,得到一个指向查询表的索引值。


3. 将索引所指的表值与寄存器做XOR运算。


4. 如数据没有全部处理完,则跳到步骤1。


 


用C可以写成这样:


   r=0;        //r是寄存器,先初始化为0


for(i=0;  i <len;  i++)   //len是待测数据(不用扩展0)的字节长度


{


   r = t[( r^(*(p+i)) ) & 0xff] ^ (r >> 8);  //p是指向待测数据的指针,t是查询表


}


 


 


 


“正规查询表”的内容是:


CRC16正规查询表


  00h   0000 C0C1 C181 0140 C301 03C0 0280 C241


  08h   C601 06C0 0780 C741 0500 C5C1 C481 0440


  10h   CC01 0CC0 0D80 CD41 0F00 CFC1 CE81 0E40


  18h   0A00 CAC1 CB81 0B40 C901 09C0 0880 C841


  20h   D801 18C0 1980 D941 1B00 DBC1 DA81 1A40


  28h   1E00 DEC1 DF81 1F40 DD01 1DC0 1C80 DC41


  30h   1400 D4C1 D581 1540 D701 17C0 1680 D641


  38h   D201 12C0 1380 D341 1100 D1C1 D081 1040


  40h   F001 30C0 3180 F141 3300 F3C1 F281 3240


  48h   3600 F6C1 F781 3740 F501 35C0 3480 F441


  50h   3C00 FCC1 FD81 3D40 FF01 3FC0 3E80 FE41


  58h   FA01 3AC0 3B80 FB41 3900 F9C1 F881 3840


  60h   2800 E8C1 E981 2940 EB01 2BC0 2A80 EA41


  68h   EE01 2EC0 2F80 EF41 2D00 EDC1 EC81 2C40


  70h   E401 24C0 2580 E541 2700 E7C1 E681 2640


  78h   2200 E2C1 E381 2340 E101 21C0 2080 E041


  80h   A001 60C0 6180 A141 6300 A3C1 A281 6240


  88h   6600 A6C1 A781 6740 A501 65C0 6480 A441


  90h   6C00 ACC1 AD81 6D40 AF01 6FC0 6E80 AE41


  98h   AA01 6AC0 6B80 AB41 6900 A9C1 A881 6840


  A0h   7800 B8C1 B981 7940 BB01 7BC0 7A80 BA41


  A8h   BE01 7EC0 7F80 BF41 7D00 BDC1 BC81 7C40


  B0h   B401 74C0 7580 B541 7700 B7C1 B681 7640


  B8h   7200 B2C1 B381 7340 B101 71C0 7080 B041


  C0h   5000 90C1 9181 5140 9301 53C0 5280 9241


  C8h   9601 56C0 5780 9741 5500 95C1 9481 5440


  D0h   9C01 5CC0 5D80 9D41 5F00 9FC1 9E81 5E40


  D8h   5A00 9AC1 9B81 5B40 9901 59C0 5880 9841


  E0h   8801 48C0 4980 8941 4B00 8BC1 8A81 4A40


  E8h   4E00 8EC1 8F81 4F40 8D01 4DC0 4C80 8C41


  F0h   4400 84C1 8581 4540 8701 47C0 4680 8641


  F8h   8201 42C0 4380 8341 4100 81C1 8081 4040


 


 


CRC32正规查询表


  00h   00000000 77073096 EE0E612C 990951BA


  04h   076DC419 706AF48F E963A535 9E6495A3


  08h   0EDB8832 79DCB8A4 E0D5E91E 97D2D988


  0Ch   09B64C2B 7EB17CBD E7B82D07 90BF1D91


  10h   1DB71064 6AB020F2 F3B97148 84BE41DE


  14h   1ADAD47D 6DDDE4EB F4D4B551 83D385C7


  18h   136C9856 646BA8C0 FD62F97A 8A65C9EC


  1Ch   14015C4F 63066CD9 FA0F3D63 8D080DF5


  20h   3B6E20C8 4C69105E D56041E4 A2677172


  24h   3C03E4D1 4B04D447 D20D85FD A50AB56B


  28h   35B5A8FA 42B2986C DBBBC9D6 ACBCF940


  2Ch   32D86CE3 45DF5C75 DCD60DCF ABD13D59


  30h   26D930AC 51DE003A C8D75180 BFD06116


  34h   21B4F4B5 56B3C423 CFBA9599 B8BDA50F


  38h   2802B89E 5F058808 C60CD9B2 B10BE924


  3Ch   2F6F7C87 58684C11 C1611DAB B6662D3D


  40h   76DC4190 01DB7106 98D220BC EFD5102A


  44h   71B18589 06B6B51F 9FBFE4A5 E8B8D433


  48h   7807C9A2 0F00F934 9609A88E E10E9818


  4Ch   7F6A0DBB 086D3D2D 91646C97 E6635C01


  50h   6B6B51F4 1C6C6162 856530D8 F262004E


  54h   6C0695ED 1B01A57B 8208F4C1 F50FC457


  58h   65B0D9C6 12B7E950 8BBEB8EA FCB9887C


  5Ch   62DD1DDF 15DA2D49 8CD37CF3 FBD44C65


  60h   4DB26158 3AB551CE A3BC0074 D4BB30E2


  64h   4ADFA541 3DD895D7 A4D1C46D D3D6F4FB


  68h   4369E96A 346ED9FC AD678846 DA60B8D0


  6Ch   44042D73 33031DE5 AA0A4C5F DD0D7CC9


  70h   5005713C 270241AA BE0B1010 C90C2086


  74h   5768B525 206F85B3 B966D409 CE61E49F


  78h   5EDEF90E 29D9C998 B0D09822 C7D7A8B4


  7Ch   59B33D17 2EB40D81 B7BD5C3B C0BA6CAD


  80h   EDB88320 9ABFB3B6 03B6E20C 74B1D29A


  84h   EAD54739 9DD277AF 04DB2615 73DC1683


  88h   E3630B12 94643B84 0D6D6A3E 7A6A5AA8


  8Ch   E40ECF0B 9309FF9D 0A00AE27 7D079EB1


  90h   F00F9344 8708A3D2 1E01F268 6906C2FE


  94h   F762575D 806567CB 196C3671 6E6B06E7


  98h   FED41B76 89D32BE0 10DA7A5A 67DD4ACC


  9Ch   F9B9DF6F 8EBEEFF9 17B7BE43 60B08ED5


  A0h   D6D6A3E8 A1D1937E 38D8C2C4 4FDFF252


  A4h   D1BB67F1 A6BC5767 3FB506DD 48B2364B


  A8h   D80D2BDA AF0A1B4C 36034AF6 41047A60


  ACh   DF60EFC3 A867DF55 316E8EEF 4669BE79


  B0h   CB61B38C BC66831A 256FD2A0 5268E236


  B4h   CC0C7795 BB0B4703 220216B9 5505262F


  B8h   C5BA3BBE B2BD0B28 2BB45A92 5CB36A04


  BCh   C2D7FFA7 B5D0CF31 2CD99E8B 5BDEAE1D


  C0h   9B64C2B0 EC63F226 756AA39C 026D930A


  C4h   9C0906A9 EB0E363F 72076785 05005713


  C8h   95BF4A82 E2B87A14 7BB12BAE 0CB61B38


  CCh   92D28E9B E5D5BE0D 7CDCEFB7 0BDBDF21


  D0h   86D3D2D4 F1D4E242 68DDB3F8 1FDA836E


  D4h   81BE16CD F6B9265B 6FB077E1 18B74777


  D8h   88085AE6 FF0F6A70 66063BCA 11010B5C


  DCh   8F659EFF F862AE69 616BFFD3 166CCF45


  E0h   A00AE278 D70DD2EE 4E048354 3903B3C2


  E4h   A7672661 D06016F7 4969474D 3E6E77DB


  E8h   AED16A4A D9D65ADC 40DF0B66 37D83BF0


  ECh   A9BCAE53 DEBB9EC5 47B2CF7F 30B5FFE9


  F0h   BDBDF21C CABAC28A 53B39330 24B4A3A6


  F4h   BAD03605 CDD70693 54DE5729 23D967BF


  F8h   B3667A2E C4614AB8 5D681B02 2A6F2B94


  FCh   B40BBE37 C30C8EA1 5A05DF1B 2D02EF8D


 


 


 


“颠倒的直驱表法”的程序:


 


同样要先做一个颠倒比特的子程序:


unsigned long int Reflect(unsigned long int ref, char ch)


{


unsigned long int value=0;


     // 交换bit0和bit7,bit1和bit6,类推


for(int i = 1; i < (ch + 1); i++)


{


     if(ref & 1)


         value |= 1 << (ch - i);


     ref >>= 1;


}


return value;


}


 


在主程序中的程序:


unsigned long int crc32_table[256];


unsigned long int ulPolynomial = 0x04c11db7;


unsigned long int crc,temp;


 


for(int i = 0; i <= 0xFF; i++)   // 生成CRC32“正规查询表”


{


     temp=Reflect(i, 8);


     crc32_table[i]= temp<< 24;


     for (int j = 0; j < 8; j++)


     {


         unsigned long int t1,t2;


         unsigned long int flag=crc32_table[i]&0x80000000;


         t1=(crc32_table[i] << 1);


         if(flag==0)


              t2=0;


         else


              t2=ulPolynomial;


         crc32_table[i] =t1^t2 ;


     }


     crc=crc32_table[i];


     crc32_table[i] = Reflect(crc32_table[i], 32);


}


 


 


   //计算CRC32值


unsigned   long   CRC32;


BYTE  DataBuf[512]={0x31,0x32,0x33,0x34};      //待测数据,为字串"1234"


unsigned   long   len;


unsigned   long   ii;


unsigned   long   m_CRC = 0xFFFFFFFF;     //寄存器中预置初始值


BYTE   *p;


 


len=4;             //待测数据的字节长度


p = DataBuf;


for(ii=0;  ii <len;  ii++)


{


     m_CRC = crc32_table[( m_CRC^(*(p+ii)) ) & 0xff] ^ (m_CRC >> 8);  //计算


}


CRC32= ~m_CRC;     //取反。经WINRAR对比,CRC32值正确!!


//这时, CRC32中的值就是CRC


 



 


 


八、           结束


以上程序均在VC6中测试成功,很多程序是直接抄网上的再修改。


CRC计算其实很简单,也就才4个算法,移来移去、优化来优化去而已。


但是就是这么简单的东西,国内网上好像没有文章能说得完全明白。搞得我这种笨人花了整整5天才整理出来。


书上的都是理论一大堆,这个式子那个式子的,一头雾水,我这个非专业人士更看不懂。


应该说,用程序实现和优化算法是容易的,创造出CRC方法的人才是真正的强人。

posted @ 2011-11-28 19:31  rookieeeeee  阅读(14911)  评论(2编辑  收藏  举报