看起来我们已经得到 CRC-32 算法的最终形式了,可是、可是在实际的应用中,数据传输时是低位先行的;对于一个字节 Byte 来讲,传输将是按照 b1,b2,...,b8 的顺序。而我们上面的算法是按照高位在前的约定,不管是 reg 还是 G(x) , g32,g31,...,g1 ; b8,b7,...,b1 ; r32,r31,...,r1 。
先来看看前面从 bit 转换到 Byte 一节中 for 循环的逻辑:
sum_poly = reg&0xFF000000; for(int j = 0; j < 8; j++) { int hi = sum_poly&0x80000000; // 测试reg最高位 sum_poly <<= 1; if(hi) sum_poly = sum_poly^POLY; } // 计算步骤2 reg = (reg<<8)|p[i]; reg = reg ^ sum_poly;
在这里的计算中, p[i] 是按照 p8,p7,...,p1 的顺序;如果 p[i] 在这里变成了 p1,p2,...,p8 的顺序;那么 reg 也应该是 r1,r2,...,r32 的顺序,同样 G(x) 和 sum_poly 也要逆转顺序。转换后的 G(x) = POLY = 0xEDB88320 。
于是取 reg 的最高位的 sum_poly 的初值就从 sum_poly = reg & 0xFF000000 变成了 sum_poly = reg & 0xFF ,测试 reg 的最高位就从 sum_poly & 0x80000000 变成了 sum_poly&0x01 ;
移出最高位也就从 sum_poly<<=1 变成了 sum_poly>>=1 ;于是上面的代码就变成了如下的形式:
sum_poly = reg&0xFF; for(int j = 0; j < 8; j++) { int hi = sum_poly&0x01; // 测试reg最高位 sum_poly >>= 1; if(hi) sum_poly = sum_poly^POLY; } // 计算步骤2 reg = (reg<<8)|p[i]; reg = reg ^ sum_poly;
为了清晰起见,给出完整的代码:
// 以4 byte数据为例 #define POLY 0xEDB88320L // CRC32生成多项式 unsigned int CRC32_2(unsigned int data) { unsigned char p[8]; memset(p, 0, sizeof(p)); memcpy(p, &data, 4); unsigned int reg = 0, sum_poly = 0; for(int i = 0; i < 8; i++) { // 计算步骤1 sum_poly = reg&0xFF; for(int j = 0; j < 8; j++) { int hi = sum_poly&0x01; // 测试reg最高位 sum_poly >>= 1; if(hi) sum_poly = sum_poly^POLY; } // 计算步骤2 reg = (reg<<8)|p[i]; reg = reg ^ sum_poly; } return reg; }
依旧像上面的思路,把计算 sum_poly 的代码段提取出来,生成 256 个元素的 CRC 校验表,再修改追加 0 的逻辑,最终的代码版本就完成了,为了对比;后面给出了字节序逆转前的完整代码段。
// 字节逆转后的CRC32算法,字节序为b1,b2,…,b8 #define POLY 0xEDB88320L // CRC32生成多项式 static unsigned int crc_table[256]; unsigned int get_sum_poly(unsigned char data) { unsigned int sum_poly = data; for(int j = 0; j < 8; j++) { int hi = sum_poly&0x01; // 取得reg的最高位 sum_poly >>= 1; if(hi) sum_poly = sum_poly^POLY; } return sum_poly; } void create_crc_table() { for(int i = 0; i < 256; i++) { crc_table[i] = get_sum_poly(i&0xFF); } } unsigned int CRC32_4(unsigned char* data, int len) { unsigned int reg = 0; // 0xFFFFFFFF,见后面解释 for(int i = 0; i < len; i++) { reg = (reg<<8) ^ crc_table[(reg&0xFF) ^ data[i]]; return reg; } } // 最终生成的校验表将是: // {0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, // 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, // … …} // 字节逆转前的CRC32算法,字节序为b8,b7,…,b1 #define POLY 0x04C11DB7L // CRC32生成多项式 static unsigned int crc_table[256]; unsigned int get_sum_poly(unsigned char data) { unsigned int sum_poly = data; sum_poly <<= 24; for(int j = 0; j < 8; j++) { int hi = sum_poly&0x80000000; // 取得reg的最高位 sum_poly <<= 1; if(hi) sum_poly = sum_poly^POLY; } return sum_poly; } void create_crc_table() { for(int i = 0; i < 256; i++) { crc_table[i] = get_sum_poly(i&0xFF); } } unsigned int CRC32_4(unsigned char* data, int len) { unsigned int reg = 0;// 0xFFFFFFFF,见后面解释 for(int i = 0; i < len; i++) { reg = (reg<<8) ^ crc_table[(reg>>24)&0xFF ^ data[i]]; return reg; } }
到这里长征终于结束了, 事实上,还有最后的一小步,那就是 reg 初始值的问题,上面的算法中 reg 初始值为 0 。在一些传输协议中,发送端并不指出消息长度,而是采用结束标志,考虑下面的这几种可能的差错:
1 )在消息之前,增加 1 个或多个 0 字节;
2) 在消息 ( 包括校验码 ) 之后,增加 1 个或多个 0 字节;
显然,这几种差错都检测不出来,其原因就是如果 reg=0 ,处理 0 消息字节 ( 或位 ) , reg 的值保持不变。解决这种问题也很简单,只要使 reg 的初始值非 0 即可,一般取 0Xffffffff ,就像你在很多 CRC32 实现中发现的那样。
到这里终于可以松一口气了, CRC32 并不是像想象的那样容易的算法啊!事实上还真不容易!这就叫做“简单的前面是优雅,背后是复杂”!