1. 为什么会有 CRC 校验码?
答:
数据有可能被更改,需要确认是否被更改,且不能占用太多字节,于是有了校验码。
而对一个字节(8位)一个字节的进行循环计算,从而核对数据是否被更改。
2. 修改了一定能被 CRC 校验出来吗?
答:
不是,而是一定概率可以校验出来。
奇偶校验就是属于 CRC 校验一种特例。
所以,为了更好的校验,就有了多项式。
更优的多项式,更高概率检查数据被更改。
经常提到的进行二进制触发计算 CRC,被除数,就是多项式(polynomial)。
CCITT | CRC16 | CRC32 | |
校验和位宽W | 16 | 16 | 32 |
生成多项式 | x16+x12+x5+1 | x16+x15+x2+1 | x32+x26+x23+x22+x16+ x12+x11+x10+x8+x7+x5+ x4+x2+x1+1 |
除数(多项式) | 0x1021 | 0x8005 | 0x04C11DB7 |
余数初始值 | 0xFFFF | 0x0000 | 0xFFFFFFFF |
结果异或值 | 0x0000 | 0x0000 | 0xFFFFFFFF |
3. crc16 算法代码范 表查询法,是怎样的?
1 #include <stdio.h> 2 #include <stdint.h> 3 4 #define POLY 0x1021 5 6 uint16_t crc16(unsigned char *addr, int num, uint16_t crc) 7 { 8 int i; 9 10 while (num--) { 11 crc = crc ^ (*addr++ << 8); 12 for (i = 0; i < 8; i++) { 13 if (crc & 0x8000) { 14 crc = (crc << 1) ^ POLY; 15 } else { 16 crc <<= 1; 17 } 18 } 19 crc &= 0xFFFF; 20 } 21 return crc; 22 } 23 24 int main(void) 25 { 26 uint8_t data[] = {0x02, 0x05}; 27 28 printf("%04X\n", crc16(data, 2, 0xFFFF)); 29 30 return 0; 31 }
4. crc16 是处理部分的逻辑是怎样的?
a、11 行操作,crc 的低 8 位不变,仅仅是 crc 的高 8 位被修改了
b、12 行到 18 行:高 8 位决定了会进行多少次与固定数字 POLY(多项式) 的异或操作。
5. 表查询法,是怎么回事呢?
问:为什么会有表查询法?
答:因为上面是一个一个 bit 位进行操作。所以在要计算的数据比较多的时候,就很影响效率。
问:表查询法是如何做到优化的?
答:
a、异或操作满足结合律、交换律。与+操作类似。
b、假设一个8位的数字,分别以 H1、 H2、 H3、 H4、 L1、 L2、 L3、 L4来表示。
V1、 V2、 V3、 V4、 V5、 V6、 V7、V8 表示 POLY(多项式)的二进制。
上下进行异或操作,高4位决定了低4位会和 POLY 进行多少次异或。
因异或操作满足结合律、交换律。上面的异或操作与下面的异或操作等价。
所以,当重复计算比较多的时候,下面的 H1 与 Vx 之间的计算结果可以保存到一个表中,后续直接查询来使用。
因 CRC 校验是一次处理一字节(8位),所以表格就有 2 的 8 次方 = 256 个。
6、查表法的范例:
1 #include <stdio.h> 2 3 #include <stdint.h> 4 5 #define POLY 0x1021 6 #define INIT 0xFFFF 7 #define FINAL 0x0000 8 9 typedef uint16_t width_t; 10 11 #define WIDTH (8 * sizeof(width_t)) 12 #define TOPBIT (1 << (WIDTH - 1)) 13 14 uint16_t crc16(unsigned char *addr, int num, uint16_t crc) 15 { 16 int i; 17 18 while (num--) { 19 crc = crc ^ (*addr++ << 8); 20 for (i = 0; i < 8; i++) { 21 if (crc & 0x8000) { 22 crc = (crc << 1) ^ POLY; 23 } else { 24 crc <<= 1; 25 } 26 } 27 crc &= 0xFFFF; 28 } 29 return crc; 30 } 31 32 33 34 static width_t crc_table[256]; 35 36 void crc_init(void) 37 { 38 width_t reg; 39 width_t i, j; 40 41 for (i = 0; i < 256; i++) { 42 reg = i << (WIDTH - 8); 43 for(j = 0; j < 8; j++) { 44 if(reg & TOPBIT) { 45 reg = (reg << 1) ^ POLY; 46 } else { 47 reg = reg << 1; 48 } 49 } 50 crc_table[i] = reg; 51 } 52 } 53 54 width_t crc_compute(unsigned char *addr, unsigned int num) 55 { 56 unsigned int i; 57 unsigned int high; 58 width_t reg = INIT; 59 60 for (i = 0; i < num; i++) { 61 high = (reg >> (WIDTH - 8)) ^ addr[i]; 62 reg = crc_table[high] ^ (reg << 8); 63 } 64 65 return (reg ^ FINAL); 66 } 67 68 69 int main(void) 70 { 71 uint8_t data[] = {0x02, 0x05}; 72 73 printf("%04X\n", crc16(data, 2, 0xFFFF)); 74 75 crc_init(); 76 printf("%04X\n", crc_compute(data, 2)); 77 78 return 0; 79 }
参考:
【循环冗余校验(CRC)算法入门引导】https://blog.csdn.net/liyuanbhu/article/details/7882789
【CRC查找表法推导及代码实现比较】https://blog.csdn.net/huang_shiyang/article/details/50881305
【A PAINLESS GUIDE TO CRC ERROR DETECTION ALGORITHMS】https://wenku.baidu.com/view/6e700a3b31126edb6f1a1084.html
【On-line CRC calculation and free library】https://www.lammertbies.nl/comm/info/crc-calculation.html