CRC 算法分类与选取:从 8 位到 64 位,怎么选才不坑?
说到 CRC,很多人第一反应就是「不就是算个校验码嘛」。嗯,话是没错,但 CRC 的世界远比你想象的要丰富。我刚开始接触通信协议时,也以为随便找个多项式就能用,结果被实际项目狠狠教育了一回。CRC 算法家族里有 CRC-8、CRC-16、CRC-32 甚至 CRC-64,它们各有脾气,用错了地方,误码漏检率会高得让你头疼。
这一节我们就来聊聊这些常用 CRC 算法的区别,以及在实际项目中该怎么选。你想想看,选错了算法就像给自行车装飞机引擎——不是不能用,但代价太大。
常用 CRC 算法一览
CRC 算法的核心差异在三个地方:多项式长度、计算宽度、以及初始值和异或结果的设置。我一般把主流算法分成四类:
| 算法 | 位数 | 典型多项式 | 查表大小 | 主要用途 |
|---|---|---|---|---|
| CRC-8 | 8 | 0x07 (x⁸ + x² + x + 1) | 256 字节 | 单字节传感器、1-Wire、小数据包 |
| CRC-16 | 16 | 0x8005 (x¹⁶ + x¹⁵ + x² + 1) | 512 字节 | Modbus、USB、CAN、SSD 传输层 |
| CRC-32 | 32 | 0x04C11DB7 (IEEE 802.3) | 1024 字节 | 以太网、ZIP/Gzip、PNG、存储校验 |
| CRC-64 | 64 | 0x42F0E1EBA9EA3693 (ECMA-182) | 8 KB | 大文件完整性、分布式存储、数据库校验 |
说白了,位数越高,冲突概率越低,但计算开销和存储空间也越大。这中间怎么取舍?得看你的实际场景。
CRC-8:轻量级选手,适合短报文
CRC-8 只生成 1 字节的校验码。我常在物联网传感器节点上用,数据包只有几个字节,CPU 跑在 8MHz 以下,CRC-8 的查表法几乎不占周期。缺点也很明显——只能检测最多 8 个连续错误位,对突发干扰的防护能力有限。
我的经验是: 如果数据长度不超过 32 字节,且信道质量不是特别差,CRC-8 完全够用。我曾经在一个温湿度采集项目里用 CRC-8,跑了一年没出过漏检。当然,要是你在强干扰环境(比如电机旁边),就得上更强的算法了。
CRC-16:工业界的主力,平衡之王
我个人最喜欢的算法就是 CRC-16。为什么?因为它在一半场景里都够用,而且实现起来非常快。CRC-16 的冲突概率大约是 1/65536,对于绝大多数通信协议来说,这已经是安全边界了。
Modbus 协议就用 CRC-16,数据帧长度通常在 256 字节以内。USB 数据包也用 CRC-16,只不过它的多项式不同。这里要注意:CRC-16 的变体非常多,包括 CRC-16/CCITT (0x1021)、CRC-16/MODBUS (0x8005)、CRC-16/USB (0x8005 但初始化不同)。
// 一个典型的 CRC-16/MODBUS 计算(C 语言风格,查表法)
uint16_t crc16_modbus(const uint8_t *data, size_t len) {
uint16_t crc = 0xFFFF; // 初始值
static const uint16_t table[256] = { /* 预计算表 */ };
for (size_t i = 0; i < len; i++) {
crc = (crc >> 8) ^ table[(crc ^ data[i]) & 0xFF];
}
return crc ^ 0x0000; // 异或输出
}
嗯,这里要注意:初始值和最后的异或值不是随便写的。不同协议定义不一样,我踩过这个坑——把 Modbus 的 CRC 直接用在自定义协议里,结果两边怎么都对不上。后来我才老老实实去查标准的多项式、初值和异或值。
CRC-32:网络和文件的标配
CRC-32 几乎是「只要是个正经协议,就会用到」的算法。以太网帧尾部就用 CRC-32,PNG 图片的每个 chunk 也有 CRC-32 校验。ZIP 文件的校验和也是 CRC-32。它的 32 位校验码可以检测出所有长度不超过 32 位的突发错误,理论上的漏检率已经非常低了。
我记得有一次排查一个网络丢包问题,底层驱动打印的 CRC-32 校验错误率高达 0.1%。拿到协议分析仪一看,原来是 PHY 芯片的时钟抖动太大,导致数据采样错位。要不是 CRC-32 够敏感,我可能得花好几天才能定位到硬件问题。
不过 CRC-32 也不是万能的。它计算一次需要 1 KB 的查表空间,在低端 MCU 上可能有点吃力。而且对于超大数据块(比如几 GB 的文件),CRC-32 的冲突概率虽然低,但理论上还是有可能漏检。这时候就得上 CRC-64 了。
CRC-64:大胖子保镖,为海量数据而生
说实话,在一般嵌入式项目里,我用 CRC-64 的次数一只手数得过来。它主要用于大文件完整性校验和分布式存储系统。比如 Google 的 Colossus 文件系统就用 CRC-64,因为文件动辄几百 TB,CRC-32 的冲突概率在这个量级下已经不够看了。
CRC-64 查表需要 8 KB 空间,而且 64 位计算在 32 位 MCU 上会慢一些。我个人建议:除非你确信未来数据量会爆炸,否则不要轻易上 CRC-64。性能开销和空间成本摆在那里。
算法选择依据:四个核心考量
讲了这么多,到底怎么选?我归纳了四个原则:
- 数据最大长度:数据越长,需要的校验位数越多。32 字节以内用 CRC-8;256 字节以内用 CRC-16;大包传输用 CRC-32;超大文件用 CRC-64。
- 实时性和资源预算:CRC-8 查表只需要 256 字节,几百个时钟周期就能算完;CRC-32 在 8 位 MCU 上可能跑 2000 个周期。如果每秒要处理几千个数据包,算力就是个硬约束。
- 硬件加速支持:很多现代 MCU(比如 STM32)内置了 CRC 硬件模块,通常支持 CRC-32 (IEEE 802.3)。如果硬件有内置,优先用硬件,又快又不占 CPU。这时 CRC-32 就是最佳选择。
- 误码容忍度:控制类指令可以容忍一次重传,但不能接受漏检(错误码被当成正确码)。这类场景建议用 CRC-16 或以上。
附:一张图理清 CRC 算法选择逻辑
我把上面的知识画成了一幅决策树。按数据长度、算力、误码率三个维度向下走,末尾就是推荐算法。你看一眼就能明白:
你看到那个「超大数据 → CRC-64」的节点了吗?我特意放在底部作为补充路径。在实际项目中,这个分支很少触发,但一旦触发,就是 GB 级别的文件校验。所以我把它单独拎出来,提醒自己「别忘了我还有这手准备」。
好了,这一节的内容就是这些。核心就一句话:按数据长度、算力、误码容忍度三个维度去选,优先用标准算法,别自己乱改多项式。我在各个项目里来回折腾过好几轮,最后发现最稳妥的、最省事的,反而是查标准、用现成、不折腾。