CRC实现@TOC
CRC原理
crc原理是通过与固定的值(poly)异或运算得到余数实现的。具体推导过程可参考《A PAINLESS GUIDE TO CRC ERROR DETECTION ALGORITHMS》
如下所示:
数据:1010110011
固定值(poly):1001 x^3+1
计算如下:首先将数据向左移动3位,低位补零
#运算过程分析:可参考博客http://www.cnblogs.com/esestt/archive/2007/08/09/848856.html第一部分算法原理,写的很好;总之目的就是通过异或运算使发送数据从高位开始的首个1变为0,直到得到的数值小于poly为止。上述运算可以看成是一直左移poly,但运算过程中我们消掉了几个1无法确定,所以移动的位数也无从确定;既然我们可以移动poly那我们可不可以移动发送数据呢?具体分析可以参考上述链接博客,在此引用一段移动发送数据的分析:
通过示例,可以发现一些规律,依据这些规律调整算法:
- 每次迭代,根据 gk 的首位决定 b,b 是与 gk 进行运算的二进制码。若 gk 的首位是1,则 b=h;若
gk 的首位是0,则 b=0,或者跳过此次迭代,上面的例子中就是碰到0后直接跳到后面的非零位。
- 每次迭代,gk 的首位将会被移出,所以只需考虑第2位后计算即可。这样就可以舍弃 h 的首位,将 b
取 h 的后 m 位。比如 CRC-8的 h 是111010101,b 只需是11010101。
3. 每次迭代,受到影响的是 gk 的前 m 位,所以构建一个 m 位的寄存器 S,此寄存器储存 gk 的前 m 位。
每次迭代计算前先将 S 的首位抛弃,将寄存器左移一位,同时将 g 的后一位加入寄存器。若使用此种方
法,计算步骤如下:
通过上述分析,很容易可以通过C实现该单bit移动的算法:
#include <stdio.h>
int main()
{
int data = 0x2b3 ;
int poly = 0x1 ;
int crc = 0x0 ;
int index = 0 ;
data <<= 3 ;//13bit 左移3bit
poly <<= 10;//左移10bit将001与数据位对齐
//data 1_0101_1001_1000
//poly 0_0100_0000_0000
for(index = 0 ;index < 10; index++)
{
if((data & 0x1000)==0x1000)
{
data = (data << 1) & 0x1fff ;
data = (data ^ poly) & 0x1fff;
}
else
{
data = (data << 1) & 0x1fff ;
}
}
crc = data >> 10 ;
printf("crc is %#x\n",crc);
return 0 ;
}
##查表法
如上所述,单比特移动数据目的是消掉第一个出现的1,那么可不可以按照多比特移动数据将移动的多比特都消掉呢?在上述运算过程中每一次运算都与poly相异或得到结果再与poly异或循环往复,那么我们可不可以先把所有的poly异或掉再与数据异或呢?通过异或运算的结合律是可以的:A ^ B ^ C = A ^ (B ^ C) = (A ^ B) ^C ;再看上述运算可推导如下:
、
如上图所示,先将poly相异或再与原来的数据相异或可以消掉多位为1的bit,由此我们可以设想:通过先将poly移位异或,异或的值等于我们要移动位数的数据对应值时在与数据异或那么我们可以消掉移动的数据位中所有的1bit位,因为A ^ A = 0 ;如假设我们左移4位数据,对应的值为A,我们可以先将poly移动并异或得到高四位数据X结果为A,那么此时数据与X异或高4位就消掉了。那么我们选择多少位比较好呢?由于计算机处理时按字节比较方便,所以我们可以按照字节来计算,每次数据移动一个字节;接下来的问题是,我们怎么来预先计算出poly对应的X值呢?一个字节8bit对应0~255 256个数据,我们可以预先通过poly来算出这256个数值对应的X值存在一个表中。具体推导过程可以参考http://www.cnblogs.com/esestt/archive/2007/08/09/848856.html第二部分查表法,这里不做摘抄了。
我们接下来用标准的CRC-32来推导出计算:
CRC-32 POLY 0x04C11DB7
我们可以借用上述单bit运算,C实现如下:
void crc32_table(unsigned int *table)
{
unsigned int row = 0 ;//0~255数据
unsigned int column = 0 ;//每个数据的bit数量
unsigned int temp = 0 ;//数据移位后的值
for(row = 0; row < 255; row ++)
{
temp = (row << 24);//将数据移动到最高位,与poly对齐
for(column = 0;column<8;column++)
{
if((row & 0x80000000)==0x80000000)//判断数据最高位是否为1,是1就消掉
{
temp <<= 1 ;
temp ^= POLY ;
}
else //否则跳过
{
temp <<= 1 ;
}
}
*table = temp ;
table++ ;
}
}
通过上述运算在table中存储的数据就是消掉0~255对应的结果。
##接下来就通过上述表和移动1个字节来实现计算,C实现代码如下:
unsigned int crc32_calculate(char *datin, int len)
{
int loop = 0 ;
unsigned int index = 0 ;//查表索引值
unsigned int crc = 0 ;//crc计算结果
unsigned int reg = 0 ;//定义一个寄存器,初值为0
for(loop = 0; loop < len; loop++)
{
index = (reg >> 24) & 0xff ;
reg = (reg << 8) | *datin ;
reg = reg ^ table[index];
datin++;
}
}
但目前还面临着一个问题就是怎样将传进来的数据datin末尾要加32bit 的0;来解决该问题我们有两种方案:#第一个方案我们可以在最后补充上这32bit的0;C实现如下:
unsigned int crc32_calculate(char *datin, int len)
{
int loop = 0 ;
unsigned int index = 0 ;
unsigned int crc = 0 ;
unsigned int reg = 0 ;
for(loop = 0 ;loop < len ;loop++)
{
index = (reg >> 24) & 0xff ;
reg = (reg << 8) | *datin ;
reg = reg ^ table[index];
datin++;
}
for(loop = 0;loop < 4;loop++)//填充4byte的0
{
reg = (reg << 8) ^ table[(reg >> 24) & 0xFF];
}
crc = reg ;
return crc ;
}
#第二种方案我们可以调整运算顺序来实现不需要在数据末尾填充4Byte的0;具体分析如下:
1.填充0并不影响计算结果,因为任何数与0异或都保持原来的值,并且这4Byte 0不再会被移出寄存器了;
2.最后寄存器填充4Byte 0 的目的是使滞留在寄存器中的有效4Byte数据参与运算;
3.如果寄存器的初始值为0,那么前四次移位的作用只是将有效数据的前四个字节移入寄存器,未做任何运算;
4.即便是寄存器初始值不为0,那么前四次移位的作用是将有效数据前四个字节移入寄存器,并将整个结果异或了一个常量(初始值)。
综上所述:结合异或运算的交换律,我们可以不用将数据通过从右往左移入寄存器,而是将数据与寄存器的最高字节异或后作为查表的索引,查到的数据再与寄存器中的值异或。C实现如下所示:
unsigned int crc32_calculate(char *datin, int len)
{
int loop = 0 ;
unsigned int index = 0 ;
unsigned int crc = 0 ;
unsigned int reg = 0 ;
for(loop = 0 ;loop < len ;loop++)
{
index = (reg >> 24) ^ *datin ;
reg <<= 8 ;
reg = reg ^ table[index];
datin++;
}
crc = reg ;
return crc ;
}
但目前还是不能得到CRC-32网络数据包校验的正确结果,因为CRC校验对寄存器初始值和输出结果都有规定
待续。。。