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-8 的多项式有好几种变体,比如 CRC-8/MAXIM (0x31)、CRC-8/SMBUS (0x07)。别以为名字里带 "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-16 的查表法虽然快,但如果你在 RAM 受限的 MCU 上运行,256 字节约 512 字节的查表空间也不小。这时可以考虑用逐位法,慢是慢一点,但不用查表。

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。性能开销和空间成本摆在那里。

算法选择依据:四个核心考量

讲了这么多,到底怎么选?我归纳了四个原则:

  1. 数据最大长度:数据越长,需要的校验位数越多。32 字节以内用 CRC-8;256 字节以内用 CRC-16;大包传输用 CRC-32;超大文件用 CRC-64。
  2. 实时性和资源预算:CRC-8 查表只需要 256 字节,几百个时钟周期就能算完;CRC-32 在 8 位 MCU 上可能跑 2000 个周期。如果每秒要处理几千个数据包,算力就是个硬约束。
  3. 硬件加速支持:很多现代 MCU(比如 STM32)内置了 CRC 硬件模块,通常支持 CRC-32 (IEEE 802.3)。如果硬件有内置,优先用硬件,又快又不占 CPU。这时 CRC-32 就是最佳选择。
  4. 误码容忍度:控制类指令可以容忍一次重传,但不能接受漏检(错误码被当成正确码)。这类场景建议用 CRC-16 或以上。
警告:别以为「CRC 多项式是公开的,随便找一个就行」。不同协议的多项式、初值、异或值、字节序都有标准定义。我曾经在一个项目里自己「发明」了一个多项式,结果对接第三方的设备时,对方死活不认。后来我才知道人家只支持标准 CRC-16/CCITT。记住:优先用标准算法,别自己造轮子。

附:一张图理清 CRC 算法选择逻辑

我把上面的知识画成了一幅决策树。按数据长度、算力、误码率三个维度向下走,末尾就是推荐算法。你看一眼就能明白:

CRC 算法选择决策流程 数据长度 ≤ 32 字节 32 ~ 256 字节 > 256 字节 算力不足 算力宽松 算力不足 算力宽松 算力不足 算力宽松 容忍度高(重传) 容忍度低(不漏检) 容忍度高(重传) 容忍度低(不漏检) 容忍度高(重传) 容忍度低(不漏检) 容忍度高(重传) 容忍度低(不漏检) 容忍度高(重传) 容忍度低(不漏检) 容忍度高(重传) 容忍度低(不漏检) 推荐:CRC-8 推荐:CRC-8 推荐:CRC-16 推荐:CRC-16 推荐:CRC-32 推荐:CRC-32 超大数据 (几 GB+) → CRC-64 额外考量 注意:如果有硬件 CRC 模块可用(如 STM32 内置 CRC-32),优先用硬件,计算几乎不占 CPU 周期 如果两端协议标准已规定多项式,直接照做,不自己选

你看到那个「超大数据 → CRC-64」的节点了吗?我特意放在底部作为补充路径。在实际项目中,这个分支很少触发,但一旦触发,就是 GB 级别的文件校验。所以我把它单独拎出来,提醒自己「别忘了我还有这手准备」。

好了,这一节的内容就是这些。核心就一句话:按数据长度、算力、误码容忍度三个维度去选,优先用标准算法,别自己乱改多项式。我在各个项目里来回折腾过好几轮,最后发现最稳妥的、最省事的,反而是查标准、用现成、不折腾。