CRC校验汇总

CRC校验原理

1、循环校验码(CRC码):是数据通信领域中最常用的一种差错校验码,其特征是信息字段和校验字段的长度可以任意选定。

2、生成CRC码的基本原理:任意一个由二进制位串组成的代码都可以和一个系数仅为‘0’和‘1’取值的多项式一一对应。例如:代码1010111对应的多项式为x6+x4+x2+x+1,而多项式为x5+x3+x2+x+1对应的代码101111。

3、CRC码集选择的原则:若设码字长度为N,信息字段为K位,校验字段为R位(N=K+R),则对于CRC码集中的任一码字,存在且仅存在一个R次多项式g(x),使得

V(x)=A(x)g(x)=xRm(x)+r(x);

其中:    m(x)为K次信息多项式, r(x)为R-1次校验多项式,

        g(x)称为生成多项式:

g(x)=g0+g1x+g2x2+...+g(R-1)x(R-1)+gRxR

发送方通过指定的g(x)产生CRC码字,接收方则通过该g(x)来验证收到的CRC码字。

4、CRC校验码软件生成方法:

    借助于多项式除法,其余数为校验字段。

例如:信息字段代码为: 1011001;对应m(x)=x6+x4+x3+1 

      假设生成多项式为:g(x)=x4+x3+1;则对应g(x)的代码为: 11001

      x4m(x)=x10+x8+x7+x4对应的代码记为:10110010000

采用多项式除法得余数为: 1010     (即校验字段为:1010

发送方:发出的传输字段为1 0 1 1 0 0 11 0 10

                         信息字段      校验字段

接收方:使用相同的生成码进行校验:接收到的字段/生成码(二进制除法)

                 如果能够除尽,则正确,

 

CRC校验源码分析

这两天做项目,需要用到 CRC校验。以前没搞过这东东,以为挺简单的。结果看看别人提供的汇编源程序,居然看不懂。花了两天时间研究了一下 CRC校验,希望我写的这点东西能够帮助和我有同样困惑的朋友节省点时间。
 
 
先是在网上下了一堆乱七八遭的资料下来,感觉都是一个模样,全都是从 CRC的数学原理开始,一长串的表达式看的我头晕。第一次接触还真难以理解。这些东西不想在这里讲,随便找一下都是一大把。我想根据源代码来分析会比较好懂一些。
 
  
费了老大功夫,才搞清楚 CRC根据”(即多项表达式)的不同而相应的源代码也有稍许不同。以下是各种常用的权。
  CRC8=X8+X5+X4+1
  CRC-CCITT=X16+X12+X5+1
 CRC16=X16+X15+X5+1          
  CRC12=X12+X11+X3+X2+1
 CRC32=X32+X26+X23+X22+X16+X12+X11+X10+X8+X7+X5+X4+X2+X1+1    
 
以下的源程序全部以 CCITT为例。其实本质都是一样,搞明白一种,其他的都是小菜。
 
1,图 2说明了 CRC校验中 CRC 值是如何计算出来的,体现的多项式正是 X16+X12+X5+1 Serial Data即是需要校验的数据。从把数据移位开始计算,将数据位(从最低的数据位开始)逐位移入反向耦合移位寄存器(这个名词我也不懂,觉得蛮酷的,就这样写了,嘿)。当所有数据位都这样操作后,计算结束。此时,16位移位寄存器中的内容就是 CRC码。
 
 
图中进行 XOR 运算的位与多项式的表达相对应。
X5
代表 Bit5X12代表 Bit121自然是代表 Bit0X16比较特别,是指移位寄存器移出的数据,即图中的DATA OUT。可以这样理解,与数据位做XOR运算的是上次 CRC值的 Bit15
根据以上说明,可以依葫芦画瓢的写出以下程序。(程序都是在 keil C 7.10下调试的)
 
typedef    unsigned char     uchar;
typedef    unsigned int      uint;
 
code uchar crcbuff [] = { 0x00,0x00,0x00,0x00,0x06,0x0d,0xd2,0xe3};
uintcrc;                 // CRC

void main(void)
{
  uchar *ptr;
  crc =0;               // CRC 
初值
  ptr =crcbuff;             // 
指向第一个 Byte数据
  crc = crc16l(ptr,8);           
  while(1);
}
 
uint crc16l(uchar *ptr,ucharlen)        // ptr
为数据指针,len为数据长度
{
  uchar i;
  while(len--)
  {
      for(i=0x80; i!=0; i>>=1)
    {
        if((crc&0x8000)!=0){crc<<=1; crc^=0x1021;}       1-1  
          elsecrc<<=1;                    1-2
      if((*ptr&i)!=0)crc^=0x1021;                      1-3  
    }
    ptr++;
  }
  return(crc);
}
 
执行结果 crc =0xdbc0;
程序1-1,1-2,1-3可以理解成移位前crc  Bit15与数据对应的 Bit(*ptr&i) XOR运算,根据此结果来决定是否执行 crc^=0x1021。只要明白两次异或运算与原值相同,就不难理解这个程序。
 
很多资料上都写了查表法来计算,当时是怎么也没想通。其实蛮简单的。假设通过移位处理了 8 bit的数据,相当于把之前的 CRC码的高字节(8bit)全部移出,与一个 byte的数据做XOR运算,根据运算结果来选择一个值(称为余式),与原来的 CRC码再做一次 XOR运算,就可以得到新的 CRC码。

 
不难看出,余式有 256种可能的值,实际上就是 0~255 X16+X12+X5+1为权得到的 CRC码,可以通过函数 crc16l来计算。以1为例。
 
code test[]={0x01};
crc = 0;
ptr = test;
crc = crc16l(ptr,1);
 
执行结果 crc =1021,这就是1对应的余式。
 
进一步修改函数,我这里就懒得写了,可得到 X16+X12+X5+1的余式表。
 
code uintcrc_ta[256]={               // X16+X12+X5+1 
余式表
    0x0000, 0x1021,  0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
  0x8108, 0x9129, 0xa14a,0xb16b, 0xc18c,0xd1ad, 0xe1ce, 0xf1ef,
    0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
    0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c,0xf3ff, 0xe3de,
    0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
    0xa56a,0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac,0xd58d,
    0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
    0xb75b, 0xa77a,0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
    0x48c4,0x58e5, 0x6886, 0x78a7,0x0840, 0x1861, 0x2802, 0x3823,
    0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
    0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71,0x0a50, 0x3a33, 0x2a12,
    0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
    0x6ca6, 0x7c87,0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
    0xedae, 0xfd8f,0xcdec, 0xddcd, 0xad2a,0xbd0b, 0x8d68, 0x9d49,
     0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32,0x1e51, 0x0e70,
    0xff9f,0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a,0x9f59, 0x8f78,
    0x9188, 0x81a9,0xb1ca, 0xa1eb, 0xd10c,0xc12d, 0xf14e, 0xe16f,
    0x1080, 0x00a1,0x30c2, 0x20e3, 0x5004,0x4025, 0x7046, 0x6067,
    0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
    0x02b1, 0x1290, 0x22f3,0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
    0xb5ea, 0xa5cb, 0x95a8,0x8589, 0xf56e, 0xe54f,0xd52c, 0xc50d,
    0x34e2, 0x24c3,0x14a0, 0x0481, 0x7466,0x6447, 0x5424, 0x4405,
    0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
    0x26d3, 0x36f2,0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
    0xd94c,0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
    0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
    0xcb7d, 0xdb5c,0xeb3f, 0xfb1e, 0x8bf9,0x9bd8, 0xabbb, 0xbb9a,
     0x4a75,0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
    0xfd2e, 0xed0f,0xdd6c, 0xcd4d, 0xbdaa,0xad8b, 0x9de8, 0x8dc9,
    0x7c26, 0x6c07, 0x5c64, 0x4c45,0x3ca2, 0x2c83,0x1ce0, 0x0cc1,

     0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
     0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2,0x0ed1, 0x1ef0
};
 
根据这个思路,可以写出以下程序:
uint table_crc(uchar *ptr,uchar len)      // 
字节查表法求 CRC
{
  uchar da;
    while(len--!=0)
  {
      da=(uchar)(crc/256);        //
8位二进制数暂存 CRC的高 8   
     crc<<=8;                // 
左移 8
      crc^=crc_ta[da^*ptr];       // 
高字节和当前数据 XOR再查表
      ptr++;
  }
    return(crc);
}
 
本质上 CRC 计算的就是移位和异或。所以一次处理移动几位都没有关系,只要做相应的处理就好了。
下面给出半字节查表的处理程序。其实和全字节是一回事。
 
code uint crc_ba[16]={
     0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
  0x8108, 0x9129, 0xa14a,0xb16b, 0xc18c,0xd1ad, 0xe1ce, 0xf1ef,
};
 
uint ban_crc(uchar *ptr,uchar len)
{
  uchar da;
  while(len--!=0)
  {
    da = ((uchar)(crc/256))/16;
    crc <<= 4;
    crc ^=crc_ba[da^(*ptr/16)];
    da = ((uchar)(crc/256)/16);
    crc <<= 4;
    crc ^=crc_ba[da^(*ptr&0x0f)];
    ptr++;
  }
  return(crc);
}
 
crc_ba[16]
crc_ta[256]的前 16个余式是一样的。

 
其实讲到这里,就已经差不多了。反正当时我以为自己是懂了。结果去看别人的源代码的时候,也是说采用 CCITT,但是是反相的。如图 3
 
反过来,一切都那么陌生,faint.吐血,吐血。
仔细分析一下,也可以很容易写出按位异或的程序。只不过由左移变成右移。
 
uint crc16r(unsigned char *ptr, unsigned char len)
{
  unsigned char i;
  while(len--!=0)
  {
    for(i=0x01;i!=0;i <<= 1)
    {
      if((crc&0x0001)!=0) {crc >>= 1; crc ^=0x8408;}
      else crc >>= 1;
      if((*ptr&i)!=0) crc ^= 0x8408;
    }
      ptr++;
  }
  return(crc);
}
 
0x8408
就是 CCITT的反转多项式。
套用别人资料上的话
反转多项式是指在数据通讯时,信息字节先传送或接收低位字节,如重新排位影响 CRC计算速度,故设反转多项式。
 
  
code uchar crcbuff [] = { 0x00,0x00,0x00,0x00,0x06,0x0d,0xd2,0xe3};
反过来就是
  code uchar crcbuff_fan[] = {0xe3,0xd2,0x0d,0x06,0x00,0x00,0x00,0x00};
 
  crc = 0;
  ptr = crcbuff_fan;
  crc = crc16r(ptr,8);
 

  执行结果 crc = 0x5f1d;
 
 
如想验证是否正确,可改
  code uchar crcbuff_fan_result[] ={0xe3,0xd2,0x0d,0x06,0x00,0x00,0x00,0x00,0x1d,0x5f};
  ptr = crcbuff_fan_result;
  crc = crc16r(ptr,10);
 
 
执行结果 crc =0; 符合 CRC校验的原理。
 
请注意 0x5f1d在数组中的排列中低位在前,正是反相运算的特点。不过当时是把我搞的晕头转向。
 
 
在用半字节查表法进行反相运算要特别注意一点,因为是右移,所以 CRC移出的 4Bit与数据 XOR的操作是在 CRC的高位端。因此余式表的产生是要以下列数组通过修改函数crc16r产生。
  code uchar ban_fan[]=
{0,0x10,0x20,0x30,0x40,0x50,0x60,0x70,0x80,0x90,0xa0,0xb0,0xc0,0xd0,0xe0,0xf0};
 
 
得出余式表
code uint fan_yushi[16]={
    0x0000, 0x1081, 0x2102, 0x3183,
    0x4204, 0x5285, 0x6306, 0x7387,
    0x8408, 0x9489, 0xa50a,0xb58b,
    0xc60c,0xd68d, 0xe70e, 0xf78f
};
 
uint ban_fan_crc(uchar *ptr,uchar len)
{
  uchar da;
  while(len--!=0)
  {
    da = (uchar)(crc&0x000f);
    crc >>= 4;
    crc ^= fan_yushi [da^(*ptr&0x0f)];
    da = (uchar)(crc&0x000f);
    crc >>= 4;
    crc ^= fan_yushi [da^(*ptr/16)];
    ptr++;  
  }
  return(crc);
}
 
主程序中
  crc = 0;
  ptr = crcbuff_fan;

  crc = ban_fan_crc(ptr,8);
  
 
执行结果 crc = 0x5f1d;
 
反相运算的算法简单实现crc校验



 
前一段时间做协议转换器的时间用到CRC-16校验,查了不少资料发现都不理想。查表法要建表太麻烦,而计算法觉得那些例子太罗嗦。最后只好自己写了,最后发现原来挺简单嘛:)

两个子程序搞定。这里用的多项式为:
CRC-16    = X16 + X12 + X5 + X0 = 2^0+2^5+2^12+2^16=0x11021

因最高位一定为“1”,故略去计算只采用0x1021即可

CRC_Byte:计算单字节的CRC值
CRC_Data:计算一帧数据的CRC值
CRC_High  CRC_Low:存放单字节CRC值
CRC16_High  CRC16_Low:存放帧数据CRC值

;<>-------------------------------------------------------------
;      Function:       CRC one byte
;      Input:             CRCByte
;      Output:           CRC_High CRC_Low
;<>-------------------------------------------------------------

CRC_Byte:
       clrf         CRC_Low
       clrf         CRC_High
       movlw           09H
       movwf           v_Loop1
       movf              CRCByte, w
       movwf           CRC_High
CRC:
       decfsz            v_Loop1                              ;8次循环,每一位相应计算
       goto        CRC10
       goto        CRCend
CRC10
       bcf                STATUS, C
       rlf                  CRC_Low
       rlf                  CRC_High
       
       btfss              STATUS, C
       goto        CRC                                          ;为0不需计算
       movlw           10H                                    ;若多项式改变,这里作相应变化
       xorwf            CRC_High, f
       movlw           21H                                    ;若多项式改变,这里作相应变化
       xorwf            CRC_Low, f
       goto        CRC
CRCend:
       nop
       nop
       return
;<>-------------------------------------------------------------
;      CRC one byte end
;<>-------------------------------------------------------------
;<>-------------------------------------------------------------
;      Function:       CRC date
;      Input:             BufStart(A,B,C)(一帧数据的起始地址) v_Count (要做CRC的字节数)
;      Output:           CRC16_High CRC16_Low(结果)
;<>-------------------------------------------------------------
CRC_Data:

       clrf         CRC16_High
       clrf         CRC16_Low

CRC_Data10

       movf              INDF, w
       xorwf            CRC16_High,w

       movwf           CRCByte
       call         CRC_Byte
       incf         FSR
       decf        v_Count                       ;需计算的字节数
       
       movf              CRC_High, w
       xorwf            CRC16_Low, w
       movwf           CRC16_High

       movf              CRC_Low, w
       movwf           CRC16_Low

       movf              v_Count, w                                          ;计算结束?
       btfss              STATUS, Z
       goto        CRC_Data10

       return

;<>-------------------------------------------------------------
;             CRC date end
;<>-------------------------------------------------------------



说明: CRC 的计算原理如下(一个字节的简单例子)
    11011000 00000000 00000000  <- 一个字节数据, 左移 16b
   ^10001000 00010000 1         <- CRC-CCITT 多项式, 17b
    --------------------------
     1010000 00010000 10        <- 中间余数
    ^1000100 00001000 01
     -------------------------
       10100 00011000 1100
      ^10001 00000010 0001
       -----------------------
         101 00011010 110100
        ^100 01000000 100001
         ---------------------
           1 01011010 01010100
          ^1 00010000 00100001
           -------------------
             01001010 01110101  <- 16b CRC

仿此,可推出两个字节数据计算如下:d 为数据,p 为项式,a 为余数
    dddddddd dddddddd 00000000 00000000 <- 数据 D ( D1, D0, 0, 0 )
   ^pppppppp pppppppp p                 <- 多项式 P
    -----------------------------------
    ...
             aaaaaaaa aaaaaaaa 0        <- 第一次的余数 A’ ( A’1, A’0 )
            ^pppppppp pppppppp p
             --------------------------
             ...
                      aaaaaaaa aaaaaaaa <- 结果 A ( A1, A0 )

由此与一字节的情况比较,将两个字节分开计算如下:
先算高字节:
    dddddddd 00000000 00000000 00000000 <- D1, 0, 0, 0
   ^pppppppp pppppppp p                 <- P
    -----------------------------------
    ...
             aaaaaaaa aaaaaaaa          <- 高字节部分余数 PHA1, PHA0

此处的部分余数与前面两字节算法中的第一次余数有如下关系,即 A’1 = PHA1 ^ D0, A’0 = PHA0:
             aaaaaaaa aaaaaaaa          <- PHA1, PHA0
            ^dddddddd                   <- D0
             -----------------
             aaaaaaaa aaaaaaaa          <- A’1, A’0

低字节的计算:
             aaaaaaaa 00000000 00000000 <- A’1, 0, 0
            ^pppppppp pppppppp p        <- P
             --------------------------
             ...
                      aaaaaaaa aaaaaaaa <- 低字节部分余数 PLA1, PLA0
                     ^aaaaaaaa          <- A’0 , 即 PHA0
                      -----------------
                      aaaaaaaa aaaaaaaa <- 最后的 CRC ( A1, A0 )

总结以上内容可得规律如下:
设部分余数函数
    PA = f( d )
其中 d 为一个字节的数据(注意,除非 n = 0 ,否则就不是原始数据,见下文)
第 n 次的部分余数
    PA( n ) = ( PA( n - 1 ) << 8 ) ^ f( d )
其中的
    d = ( PA( n - 1 ) >> 8 ) ^ D( n )
其中的 D( n ) 才是一个字节的原始数据。

公式如下:
    PA( n ) = ( PA( n - 1 ) << 8 ) ^ f( ( PA( n - 1 ) >> 8 ) ^ D( n ) )

可以注意到函数 f( d ) 的参数 d 为一个字节,对一个确定的多项式 P, f( d ) 的返回值 是与 d 一一对应的,总数为 256 项,将这些数据预先算出保存在表里,f( d )就转换为一个查表的过程,速度也就可以大幅提高,这也就是查表法计算 CRC 的原理。

再来看 CRC 表是如何计算出来的,即函数 f( d ) 的实现方法。分析前面一个字节数据的计算过程可发现,d 对结果的影响只表现为对 P 的移位异或,看计算过程中的三个 8 位 的列中只低两个字节的最后结果是余数,而数据所在的高 8 位列最后都被消去了,因其中的运算均为异或,不产生进位或借位,故每一位数据只影响本列的结果,即 d 并不直接 影响结果。再将前例变化一下重列如下:
    11011000
    --------------------------
    10001000 00010000 1        // P
   ^ 1000100 00001000 01       // P
   ^  000000 00000000 000      // 0
   ^   10001 00000010 0001     // P
   ^    0000 00000000 00000    // 0
   ^     100 01000000 100001   // P
   ^      00 00000000 0000000  // 0
   ^       1 00010000 00100001 // P
           -------------------
             01001010 01110101

现在的问题就是如何根据 d 来对 P 移位异或了,从上面的例子看,也可以理解为每步移位,但根据 d 决定中间余数是否与 P 异或。从前面原来的例子可以看出,决定的条件是中间余数的最高位为0,因为 P 的最高位一定为1,即当中间余数与 d 相应位异或的最高位为1时,中间余数移位就要和 P 异或,否则只需移位即可。其方法如下例(上例的变形,注意其中空格的移动表现了 d 的影响如何被排除在结果之外):

    d --------a--------
    1 00000000 00000000 <- HSB = 1
      0000000 000000000 <- a <<= 1
      0001000 000100001 <-不含最高位的 1
      -----------------
    1 0001000 000100001
      001000 0001000010
      000100 0000100001
      -----------------
    0 001100 0001100011 <- HSB = 0
      01100 00011000110
      -----------------
    1 01100 00011000110 <- HSB = 1
      1100 000110001100
      0001 000000100001
      -----------------
    1 1101 000110101101 <- HSB = 0
      101 0001101011010
      -----------------
    0 101 0001101011010 <- HSB = 1
      01 00011010110100
      00 01000000100001
      -----------------
    0 01 01011010010101 <- HSB = 0
      1 010110100101010
      -----------------
    0 1 010110100101010 <- HSB = 1
       0101101001010100
       0001000000100001
      -----------------
       0100101001110101 <- CRC

结合这些,前面的程序就好理解了。

循环冗余校验码

CRCCyclic Redundancy Check)循环冗余校验码

  是常用的校验码,在早期的通信中运用广泛,因为早期的通信技术不够可靠(不可靠性的来源是通信技术决定的,比如电磁波通信时受雷电等因素的影响),不可靠的通信就会带来确认信息的困惑,书上提到红军和蓝军通信联合进攻山下的敌军的例子,第一天红军发了条信息要蓝军第二天一起进攻,蓝军收到之后,发一条确认信息,但是蓝军担心的是确认信息如果也不可靠而没有成功到达红军那里,那自己不是很危险?于是红军再发一条对确认的确认信息,但同样的问题还是不能解决,红军仍然不敢贸然行动。

  对通信的可靠性检查就需要校验,校验是从数据本身进行检查,它依靠某种数学上约定的形式进行检查,校验的结果是可靠或不可靠,如果可靠就对数据进行处理,如果不可靠,就丢弃重发或者进行修复。

CRC码是由两部分组成,前部分是信息码,就是需要校验的信息,后部分是校验码,如果CRC码共长nbit,信息码长kbit,就称为(n,k)码。它的编码规则是:

1、首先将原信息码(kbit)左移r(k+r=n)

2、运用一个生成多项式g(x)(也可看成二进制数)用模2除上面的式子,得到的余数就是校验码。

  非常简单,要说明的:模2除就是在除的过程中用模2加,模2加实际上就是我们熟悉的异或运算,就是加法不考虑进位,公式是:

0+0=1+1=0,1+0=0+1=1

  即则真,非异则假。

  由此得到定理:a+b+b=a也就是22直值表完全相同。

  有了加减法就可以用来定义模2除法,于是就可以用生成多项式g(x)生成CRC校验码。

  例如:g(x)=x4+x3+x2+1,(7,3),信息码110产生的CRC码就是:

  对于g(x)=x4+x3+x2+1的解释:(都是从右往左数)x4就是第五位是1,因为没有x1所以第2位就是0

11101| 110,0000(设a=11101b=1100000

  取b的前511000a异或得到101

101加上b没有取到的00得到10100

  然后跟a异或得到01001

  也就是余数1001

  余数是1001,所以CRC码是110,1001

  标准的CRC码是,CRC-CCITTCRC-16,它们的生成多项式是:

CRC-CCITT=x^16+x^12+x^5+1

CRC-16=x^16+x^15+x^2+1

 

 

 

 

 

 

 

 

 

CRC校验原理及其在通信系统中的作用
在通信系统的数据传输过程中,由于信道中各种复杂因素的影响,往往使传输的信号受到干扰,造成误码的出现。接收方为了检查所接收的数据是否有误码,可采用多种检测方法。差错控制编码是目前数据传输过程中普遍采用的一种提高数据通信可靠性的方法,而CRC是一种在实际通信中应用很广泛的差错控制编码,具有很强的检错能力。
CRC校验将输入比特( )表示为下列多项式的系数:

式中,D可以看成一个时延因子,表示相应的比特所处的位置。
校验比特( )可由下式求得:

式中,表示取余数, 是生成多项式。式中的除法与普通的多项式长除法相同,其差别是系数是二进制,其运算以模2为基础。常用的几个L阶CRC生成多项式为:



其中,和产生的校验比特为16比特, 产生的校验比特为32比特。
在实际数据传输过程中,发送方将CRC校验码附加在所传数据流的尾部一并传送;接收方用同样的生成多项式去除接收到的数据流,若余数为零,则可判断所接收到的数据是正确的。否则,可判断数据在传输过程中产生了误码。

2 CRC校验的C语言实现
下面我们以CRC-16为例来说明任意长度数据流的CRC校验码生成过程。我们采用将数据流分成若干个8bit字符,并由低字节到高字节传送的并行方法来求CRC校验码。具体计算过程为:用一个16bit的寄存器来存放CRC校验值,且设定其初值为0x0000;将数据流的第一个8bit与16bit的CRC寄存器的高字节相异或,并将结果存入CRC寄存器高字节;CRC寄存器左移一位,最低1bit补零,同时检查移出的最高1bit,若移出的最高1bit为0,则继续按上述过程左移,若最高1bit为1,则将CRC寄存器中的值与生成多项式码相异或,结果存入CRC寄存器值;继续左移并重复上述处理方法,直到将8bit数据处理完为止,则此时CRC寄存器中的值就是第一个8bit数据对应的CRC校验码;然后将此时CRC寄存器的值作为初值,用同样的处理方法重复上述步骤来处理下一个8bit数据流,直到将所有的8bit字符都处理完后,此刻CRC寄存器中的值即为整个数据流对应的CRC校验码。
下面示出了其计算过程的流程图:

在用C语言编写CRC校验码的实现程序时我们应该注意,生成多项式 对应的十六进制数为0x18005,由于CRC寄存器左移过程中,移出的最高位为1时与 相异或,所以与16bit的CRC寄存器对应的生成多项式的十六进制数可用0x8005表示。下面给出并行处理8bit数据流的C源程序:
unsigned short crc_dsp(unsigned short reg, unsigned char data_crc)
//reg为crc寄存器,data_crc为将要处理的8bit数据流
{
unsigned short msb; //crc寄存器将移出的最高1bit
unsigned short data;
unsigned short gx = 0x8005, i = 0; //i为左移次数, gx为生成多项式

data = (unsigned short)data_crc;
data = data << 8;
reg = reg ^ data;
do
{
msb = reg & 0x8000;
reg = reg << 1;
if(msb == 0x8000)
{
reg = reg ^ gx;
}
i++;
}
while(i < 8);
return (reg);
}
以上为处理每一个8bit数据流的子程序,在计算整个数据流的CRC校验码时,我们只需将CRC_reg的初值置为0x0000,求第一个8bit的CRC值,之后,即可将上次求得的CRC值和本次将要处理的8bit数据作为函数实参传递给上述子程序的形参进行处理即可,最终返回的reg值便是我们所想得到的整个数据流的CRC校验值。

3 结论
本文根据CRC校验的基本原理,提出了一种并行计算任意长度CRC校验值的算法,并给出了源程序,且该源程序可作为一子函数来直接被调用,可灵活的应用在不同的工程当中。此算法已应用与某系统当中,实践证明,该算法正确可靠。

 

 

 

 

 

 

 

 

其实CRC可以用查表的方法。
/*
CRC校验算法实现 C语言代码实现
文件 CRC16.h
*/
#ifndef _CRC16_h
#define _CRC16_h
static unsigned char auchCRCHi[] = {
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
};
/* CRC低位字节值表*/
static char auchCRCLo[] = {
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
0x1E, 0xDE, 0xDF, 0x1F,0xDD, 0x1D, 0x1C,0xDC, 0x14, 0xD4,
0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
0x3C, 0xFC, 0xFD, 0x3D,0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
0xEB, 0x2B, 0x2A,0xEA, 0xEE, 0x2E, 0x2F,0xEF, 0x2D, 0xED,
0xEC, 0x2C, 0xE4, 0x24, 0x25,0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
0xA5, 0x65, 0x64, 0xA4, 0x6C,0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
0x6E, 0xAE, 0xAA, 0x6A,0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A,0xBA, 0xBE, 0x7E,
0x7F, 0xBF, 0x7D, 0xBD,0xBC, 0x7C, 0xB4, 0x74, 0x75,0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A,0x9B, 0x5B,
0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F,0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
0x43, 0x83, 0x41, 0x81, 0x80, 0x40
};
/* 要进行CRC校验的消息 */ /* 消息中字节数 */
unsigned short CRC16(unsigned char *puchMsg,unsigned short usDataLen )
{
unsigned char uchCRCHi = 0xFF ; /* 高CRC字节初始化 */
unsigned char uchCRCLo = 0xFF ; /* 低CRC 字节初始化 */
unsigned uIndex ; /* CRC循环中的索引 */
while (usDataLen--) /* 传输消息缓冲区 */
{
uIndex = uchCRCHi ^ *puchMsg++ ; /* 计算CRC */
uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex];
uchCRCLo = auchCRCLo[uIndex] ;
}
return (uchCRCHi < < 8 | uchCRCLo) ;
}
#endif //_CRC16_h

/*
CRC16.cpp
生成 CRC码实现
*/
#include <stdio.h>
#include "CRC16.h"
void main()
{
unsigned char ppp[13] = {0x12,0x34};
    unsigned short result; 
    // 计算CRC
    result = CRC16(ppp, 2);
printf("%x\n",result);

}


分割线//

学习 CRC32、CRC16、CRC 原理和算法的总结(与 WINRAR 结果一致)
wxleasyland(wxlwww@gmail.com)
2010 年 9 月 2 日
比较愚钝,学了 CRC 校验好几天,很痛苦的过程,现终于有眉目了,总结一下。
国外版的“轻松无痛苦学习 CRC 指南” ,在

(为什么好的资料都是老外写的?)我的英文有限,这种专业性太强的文章,很多都看不太明白,所
以没办法翻译,靠参考国内的翻译和自己瞎琢磨的。
国内的翻译比较不全,而且有点误导,能看英文的还是看英文吧,国内版资料比较零散,可参考:

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

计算 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 10111000 10000011 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= registerXOR 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 withzero bits
// augment the data by appending W(4)zero bits to theend of it.
//按 CRC 计算的定义,待测数据后加入 4 个比特 0,以容纳 4 比特的 CRC;
//这样共有 16 比特待测数据,从第 5 比特开始做除法,就要做 16-5+1=12 次 XOR
data <<= 4;
// wedo it bit afterbit
for (intcur_bit= 15; cur_bit >= 0;-- cur_bit )  //处理 16 次,前 4 次实际上只是加载数据
{
// test the highest bit whichwill bepoped later.
///    in fact, the 5th bit fromright is the hightest bithere
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 withzero 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;
// wedo it bit afterbit
for (intcur_bit= databitnumber+crcbitnumber-1;cur_bit >= 0;-- cur_bit )   //处理 64 次(32 比特待测数据+32 比
特扩展 0),前 32 次是加载数据
{
// test the highest bit whichwill bepoped later.
///    in fact, the 5th bit fromright is the hightest bithere
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-DrivenImplementation
上面的“直接计算法”很直观,却非常的低效。为了加快它的速度,我们使它一次能处理大于 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
生成多项式为:   10101110 0
第一步:
Top Register   (top 指移出的数据)
---- --------
10110100 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
-------------
10111011 1100  POLY 与 POLY 自已进行 XOR 后得到的值
第二步:
10111011 1100  POLY 相 XOR 后得到的值
10110100 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 11100000 与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 8005800F 000A
04H 801B 001E0014 8011
08H 8033 0036003C 8039
0CH 0028 802D8027 0022
10H 8063 0066006C 8069
14H 0078 807D8077 0072
18H 0050 8055805F 005A
1CH 804B 004E0044 8041
20H 80C3 00C600CC 80C9
24H 00D8 80DD80D7 00D2
28H 00F0 80F580FF 00FA
2CH 80EB 00EE00E4 80E1
30H 00A0 80A580AF 00AA
34H 80BB 00BE00B4 80B1
38H 8093 0096009C 8099
3CH 0088 808D8087 0082
40H 8183 0186018C 8189
44H 0198 819D8197 0192
48H 01B0 81B581BF 01BA
4CH 81AB 01AE01A4 81A1
50H 01E0 81E581EF 01EA
54H 81FB 01FE01F4 81F1
58H 81D3 01D601DC 81D9
5CH 01C8 81CD81C7 01C2
60H 0140 8145814F 014A
64H 815B 015E0154 8151
68H 8173 0176017C 8179
6CH 0168 816D8167 0162
70H 8123 0126012C 8129
74H 0138 813D8137 0132
78H 0110 8115811F 011A
7CH 810B 010E0104 8101
80H 8303 0306030C 8309
84H 0318 831D8317 0312
88H 0330 8335833F 033A
8CH 832B 032E0324 8321
90H 0360 8365836F 036A
94H 837B 037E0374 8371
98H 8353 0356035C 8359
9CH 0348 834D8347 0342
A0H 03C0 83C583CF 03CA
A4H 83DB 03DE03D4 83D1
A8H 83F3 03F603FC 83F9
ACH 03E8 83ED83E7 03E2
B0H 83A3 03A603AC 83A9
B4H 03B8 83BD83B7 03B2
B8H 0390 8395839F 039A
BCH 838B 038E0384 8381
C0H 0280 8285828F 028A
C4H 829B 029E0294 8291
C8H 82B3 02B602BC 82B9
CCH 02A8 82AD82A7 02A2
D0H 82E3 02E602EC 82E9
D4H 02F8 82FD82F7 02F2
D8H 02D0 82D582DF 02DA
DCH 82CB 02CE02C4 82C1
E0H 8243 0246024C 8249
E4H 0258 825D8257 0252
E8H 0270 8275827F 027A
ECH 826B 026E0264 8261
F0H 0220 8225822F 022A
F4H 823B 023E0234 8231
F8H 8213 0216021C 8219
FCH 0208 820D8207 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 longcnCRC_32 = 0x04C11DB7;  //采用正规的 CRC32 的 POLY
unsigned longTable_CRC16[256];      // CRC16 表
unsigned longTable_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] = ( unsignedlong )nAccum16;
}
// 构造 32 位CRC 表"直接查询表"
unsigned longi32, j32;
unsigned longnData32;
unsigned longnAccum32;
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 charaData[512]={0x31,0x32,0x33,0x34};      //待测数据,为字串"1234"
unsigned longaSize;
unsigned longi;
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 longCRC32_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],( unsignedshort)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 registerleft 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 theregister. 把表值 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 longcnCRC_32 = 0x04C11DB7;  //采用正规的 CRC32 的 POLY
unsigned longTable_CRC16[256];      // CRC16 表
unsigned longTable_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] = ( unsignedlong )nAccum16;
}
// 构造 32 位CRC 表"直接查询表"
unsigned longi32, j32;
unsigned longnData32;
unsigned longnAccum32;
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 charaData[512]={0x31,0x32,0x33,0x34};      //待测数据,为字串"1234"
unsigned longaSize;
unsigned longi;
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 longCRC32_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 3334,颠倒后就变成 0x8C 4C CC 2C,再进行 CRC 计算。
RefOut=True,表示计算完成后,要将寄存器中的值再颠倒。注意,这是将整个寄存器的值颠倒,即如
果寄存器中的值是 0x31 32 33 34,颠倒后就变成 0x2C CC 4C 8C!
XorOut=FFFFFFFF,表示还需要将结果值与 0xffffffff 进行 XOR,这样就得到最终的 CRC32 值了!
我们直接用“直驱表法”,计算字串"1234"的 CRC32 值。
程序如下:
要先做一个倒比特的子程序:
unsigned longintReflect(unsigned long int ref, char ch)
{
unsigned longintvalue=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 longcnCRC_32 = 0x04C11DB7;  //采用正规的 CRC32 的 POLY
unsigned longTable_CRC32[256];      // CRC32 表
// 构造 32 位CRC 表"直接查询表"
unsigned longi32, j32;
unsigned longnData32;
unsigned longnAccum32;
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 charaData[512]={0x31,0x32,0x33,0x34};      //待测数据,为字串"1234"
unsigned longaSize;
unsigned longi;
unsigned char*point;
unsigned charchtemp;
// 计算 32 位CRC-32 值
//Table-Driven 驱动表法,需要用到“直接查询表” (不用“正规查询表”) ;待测数据需扩展 0
unsigned longii;
unsigned longCRC32_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 longCRC32_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 +----+----+----+----+
索引值和表值都不颠倒
“颠倒的直驱表法”用的查询表,是网上可以找到的查询表,我叫它“正规查询表”,实际就是“倒
的直接查询表”。对应关系是:
“直接查询表”           “正规查询表”(颠倒的查询表)
0000 0000(00H)       0000 0000(00H)
0000 0001(01H)表值 04C11DB7H   10000000(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)
待测数据(不颠倒)Message(non augmented)>-----+
|
Bytes  3  2   1   0      v
+----+----+----+----+    |
|   |   |   |  |>----XOR
+----+----+----+----+    |
MSB      ^      LSB   |
|            |
XOR           |
|            |
+----+----+----+----+ 00H |
EDB88320H +----+----+----+----+ 80H v 查表
+----+----+----+----+ C0H |
+----+----+----+----+ 20H |
+----+----+----+----+ A0H |
+----+----+----+----+    |
+----+----+----+----+    |
+----+----+----+----+<-----+
+----+----+----+----+
77073096H+----+----+----+----+ 01H
+----+----+----+----+
+----+----+----+----+
+----+----+----+----+255
索引值和表值都颠倒
也就是说,将“直接查询表”的索引值和表值直接镜像就是“正规查询表”。
比如,直接查询表的[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 C0C1C181 0140 C301 03C0 0280 C241
08h C601 06C00780 C741 0500 C5C1 C481 0440
10h CC01 0CC00D80 CD41 0F00 CFC1 CE81 0E40
18h 0A00 CAC1CB81 0B40 C901 09C0 0880 C841
20h D801 18C01980 D941 1B00 DBC1 DA81 1A40
28h 1E00 DEC1DF81 1F40 DD01 1DC0 1C80 DC41
30h 1400 D4C1D581 1540 D701 17C0 1680 D641
38h D201 12C01380 D341 1100 D1C1 D081 1040
40h F001 30C03180 F141 3300 F3C1 F281 3240
48h 3600 F6C1F781 3740 F501 35C0 3480 F441
50h 3C00 FCC1FD81 3D40 FF01 3FC0 3E80 FE41
58h FA01 3AC03B80 FB41 3900 F9C1 F881 3840
60h 2800 E8C1E981 2940 EB01 2BC0 2A80 EA41
68h EE01 2EC02F80 EF41 2D00 EDC1 EC81 2C40
70h E401 24C02580 E541 2700 E7C1 E681 2640
78h 2200 E2C1E381 2340 E101 21C0 2080 E041
80h A001 60C06180 A141 6300 A3C1 A281 6240
88h 6600 A6C1A781 6740 A501 65C0 6480 A441
90h 6C00 ACC1AD81 6D40 AF01 6FC0 6E80 AE41
98h AA01 6AC06B80 AB41 6900 A9C1 A881 6840
A0h 7800 B8C1B981 7940 BB01 7BC0 7A80 BA41
A8h BE01 7EC07F80 BF41 7D00 BDC1 BC81 7C40
B0h B401 74C07580 B541 7700 B7C1 B681 7640
B8h 7200 B2C1B381 7340 B101 71C0 7080 B041
C0h 5000 90C19181 5140 9301 53C0 5280 9241
C8h 9601 56C05780 9741 5500 95C1 9481 5440
D0h 9C01 5CC05D80 9D41 5F00 9FC1 9E81 5E40
D8h 5A00 9AC19B81 5B40 9901 59C0 5880 9841
E0h 8801 48C04980 8941 4B00 8BC1 8A81 4A40
E8h 4E00 8EC18F81 4F40 8D01 4DC0 4C80 8C41
F0h 4400 84C18581 4540 8701 47C0 4680 8641
F8h 8201 42C04380 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 longintReflect(unsigned long int ref, char ch)
{
unsigned longintvalue=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 longintcrc32_table[256];
unsigned longintulPolynomial = 0x04c11db7;
unsigned longintcrc,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 longintt1,t2;
unsigned longintflag=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 方法的人才是真正的强人。


  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值