Matlab系列(009) 一CRC循环冗余校验详解

CRC 循环冗余校验详解:原理、手工推导与 Matlab/C 代码实现

数据在传输和存储的过程中,难免因为噪声、干扰等原因发生比特错误。如何低成本、高效地发现这些错误?CRC(Cyclic Redundancy Check,循环冗余校验)给出了答案。本文将从零开始,带你彻底搞懂 CRC 的原理、推导过程,并给出可直接运行的 Matlab 和 C 代码。

1. 什么是 CRC

CRC(Cyclic Redundancy Check,循环冗余校验) 是一种根据数据内容计算出一段固定长度校验码(称为 CRC 码、校验值或帧检验序列 FCS)的检错算法。

它的基本思想是:把待发送的数据看成一个二进制多项式,用这个多项式去除以一个约定的"生成多项式",把除得的余数作为校验码附在数据后面一起发送。接收方用同样的方式计算,如果计算出的校验码与收到的校验码不一致(或者除不尽),就说明数据在传输过程中发生了错误。

在讲 CRC 之前,先看看它的"前辈们":

检错手段 原理 缺点
奇偶校验 统计 1 的个数奇偶性,补 1 位 只能检测奇数个比特错误,检测能力弱
校验和(Checksum) 数据按字求和取反 实现简单,但漏检率高,抗突发错误能力差
CRC 多项式模 2 除法取余数 检错能力强、硬件实现简单,工业界事实标准

CRC 之所以被广泛采用,主要有三个特点:

  1. 检错能力强:能检测出所有奇数个错误、所有长度不超过 CRC 位数的突发错误,以及绝大多数的其他错误模式;
  2. 实现简单:核心运算只有"移位"和"异或",非常适合硬件实现(芯片里几行逻辑门就能搞定),软件实现也很快;
  3. 不增加太多开销:CRC-32 只给每帧增加 4 个字节,对以太网 1500 字节的帧来说开销极小。

2. CRC 实现的原理

2.1 二进制数据可以看作多项式

CRC 的理论基础是多项式算术。任意一段二进制数据,都可以表示成一个以 0/1 为系数的多项式:

1011001  ⟺  1⋅x6+0⋅x5+1⋅x4+1⋅x3+0⋅x2+0⋅x1+1=x6+x4+x3+1 1011001 \iff 1 \cdot x^6 + 0 \cdot x^5 + 1 \cdot x^4 + 1 \cdot x^3 + 0 \cdot x^2 + 0 \cdot x^1 + 1 = x^6 + x^4 + x^3 + 1 1011001⟺1⋅x6+0⋅x5+1⋅x4+1⋅x3+0⋅x2+0⋅x1+1=x6+x4+x3+1

规律很简单:第 i 位为 1,就对应一项 xix^ixi(从右往左从 0 开始编号)。

2.2 模 2 运算 = 按位异或

CRC 中所有的加减乘除都是在 模 2(GF(2) 有限域) 下进行的,这里只有两条规则:

  • 加法 = 减法 = 异或 (XOR):1+1=01+0=1,没有进位也没有借位;
  • 除法就是重复"异或消位"的过程。

例如:

1011⊕1001=0010 1011 \oplus 1001 = 0010 1011⊕1001=0010

这正是为什么 CRC 的硬件实现只需要移位寄存器 + 异或门,而不需要任何加法器。

2.3 生成多项式 G(x)

发送端和接收端必须约定同一个生成多项式 G(x)G(x)G(x),这是 CRC 的灵魂。G(x)G(x)G(x) 的最高次数 n 决定了 CRC 校验码的位数(n 位 CRC 对应 n+1 位的多项式)。

G(x)G(x)G(x) 的选择一般遵循以下原则:

  • 最高位和最低位必须为 1(否则可以整体约掉一个 x,退化);
  • 多项式不能含有因子 x,且应尽量选择"不可约多项式",使检错能力最大化;
  • 实际工程中不必自己设计,直接用现成的国际标准即可(见 2.5 节)。

2.4 发送端计算流程

#mermaid-svg-Tqho0ndU4K1J1h0l{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Tqho0ndU4K1J1h0l .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Tqho0ndU4K1J1h0l .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Tqho0ndU4K1J1h0l .error-icon{fill:#552222;}#mermaid-svg-Tqho0ndU4K1J1h0l .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Tqho0ndU4K1J1h0l .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Tqho0ndU4K1J1h0l .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Tqho0ndU4K1J1h0l .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Tqho0ndU4K1J1h0l .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Tqho0ndU4K1J1h0l .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Tqho0ndU4K1J1h0l .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Tqho0ndU4K1J1h0l .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Tqho0ndU4K1J1h0l .marker.cross{stroke:#333333;}#mermaid-svg-Tqho0ndU4K1J1h0l svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Tqho0ndU4K1J1h0l p{margin:0;}#mermaid-svg-Tqho0ndU4K1J1h0l .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Tqho0ndU4K1J1h0l .cluster-label text{fill:#333;}#mermaid-svg-Tqho0ndU4K1J1h0l .cluster-label span{color:#333;}#mermaid-svg-Tqho0ndU4K1J1h0l .cluster-label span p{background-color:transparent;}#mermaid-svg-Tqho0ndU4K1J1h0l .label text,#mermaid-svg-Tqho0ndU4K1J1h0l span{fill:#333;color:#333;}#mermaid-svg-Tqho0ndU4K1J1h0l .node rect,#mermaid-svg-Tqho0ndU4K1J1h0l .node circle,#mermaid-svg-Tqho0ndU4K1J1h0l .node ellipse,#mermaid-svg-Tqho0ndU4K1J1h0l .node polygon,#mermaid-svg-Tqho0ndU4K1J1h0l .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Tqho0ndU4K1J1h0l .rough-node .label text,#mermaid-svg-Tqho0ndU4K1J1h0l .node .label text,#mermaid-svg-Tqho0ndU4K1J1h0l .image-shape .label,#mermaid-svg-Tqho0ndU4K1J1h0l .icon-shape .label{text-anchor:middle;}#mermaid-svg-Tqho0ndU4K1J1h0l .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Tqho0ndU4K1J1h0l .rough-node .label,#mermaid-svg-Tqho0ndU4K1J1h0l .node .label,#mermaid-svg-Tqho0ndU4K1J1h0l .image-shape .label,#mermaid-svg-Tqho0ndU4K1J1h0l .icon-shape .label{text-align:center;}#mermaid-svg-Tqho0ndU4K1J1h0l .node.clickable{cursor:pointer;}#mermaid-svg-Tqho0ndU4K1J1h0l .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Tqho0ndU4K1J1h0l .arrowheadPath{fill:#333333;}#mermaid-svg-Tqho0ndU4K1J1h0l .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Tqho0ndU4K1J1h0l .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Tqho0ndU4K1J1h0l .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Tqho0ndU4K1J1h0l .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Tqho0ndU4K1J1h0l .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Tqho0ndU4K1J1h0l .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Tqho0ndU4K1J1h0l .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Tqho0ndU4K1J1h0l .cluster text{fill:#333;}#mermaid-svg-Tqho0ndU4K1J1h0l .cluster span{color:#333;}#mermaid-svg-Tqho0ndU4K1J1h0l div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Tqho0ndU4K1J1h0l .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Tqho0ndU4K1J1h0l rect.text{fill:none;stroke-width:0;}#mermaid-svg-Tqho0ndU4K1J1h0l .icon-shape,#mermaid-svg-Tqho0ndU4K1J1h0l .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Tqho0ndU4K1J1h0l .icon-shape p,#mermaid-svg-Tqho0ndU4K1J1h0l .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Tqho0ndU4K1J1h0l .icon-shape .label rect,#mermaid-svg-Tqho0ndU4K1J1h0l .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Tqho0ndU4K1J1h0l .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Tqho0ndU4K1J1h0l .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Tqho0ndU4K1J1h0l :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 原始数据 D(如 1011001)
左移 n 位:末尾补 n 个 0

得到 D' = D × x^n
模 2 除法:D' ÷ G(x)
得到余数 R,即 CRC 校验码(n 位)
发送帧 = D + R(数据后拼接校验码)

具体步骤:

  1. 设生成多项式 G(x)G(x)G(x) 的次数为 nnn,把数据 DDD 左移 nnn 位,即末尾补 nnn 个 0,得到 D′=D×xnD' = D \times x^nD′=D×xn;
  2. 用 D′D'D′ 对 G(x)G(x)G(x) 做模 2 除法 ,得到余数 RRR(不足 nnn 位时高位补 0);
  3. 把 RRR 拼接到 DDD 的末尾,构成发送帧 D+RD + RD+R 发出去。

2.5 接收端校验流程

#mermaid-svg-lOQp1NnE4vC5dXdE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lOQp1NnE4vC5dXdE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lOQp1NnE4vC5dXdE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lOQp1NnE4vC5dXdE .error-icon{fill:#552222;}#mermaid-svg-lOQp1NnE4vC5dXdE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lOQp1NnE4vC5dXdE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lOQp1NnE4vC5dXdE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lOQp1NnE4vC5dXdE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lOQp1NnE4vC5dXdE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lOQp1NnE4vC5dXdE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lOQp1NnE4vC5dXdE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lOQp1NnE4vC5dXdE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lOQp1NnE4vC5dXdE .marker.cross{stroke:#333333;}#mermaid-svg-lOQp1NnE4vC5dXdE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lOQp1NnE4vC5dXdE p{margin:0;}#mermaid-svg-lOQp1NnE4vC5dXdE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lOQp1NnE4vC5dXdE .cluster-label text{fill:#333;}#mermaid-svg-lOQp1NnE4vC5dXdE .cluster-label span{color:#333;}#mermaid-svg-lOQp1NnE4vC5dXdE .cluster-label span p{background-color:transparent;}#mermaid-svg-lOQp1NnE4vC5dXdE .label text,#mermaid-svg-lOQp1NnE4vC5dXdE span{fill:#333;color:#333;}#mermaid-svg-lOQp1NnE4vC5dXdE .node rect,#mermaid-svg-lOQp1NnE4vC5dXdE .node circle,#mermaid-svg-lOQp1NnE4vC5dXdE .node ellipse,#mermaid-svg-lOQp1NnE4vC5dXdE .node polygon,#mermaid-svg-lOQp1NnE4vC5dXdE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lOQp1NnE4vC5dXdE .rough-node .label text,#mermaid-svg-lOQp1NnE4vC5dXdE .node .label text,#mermaid-svg-lOQp1NnE4vC5dXdE .image-shape .label,#mermaid-svg-lOQp1NnE4vC5dXdE .icon-shape .label{text-anchor:middle;}#mermaid-svg-lOQp1NnE4vC5dXdE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lOQp1NnE4vC5dXdE .rough-node .label,#mermaid-svg-lOQp1NnE4vC5dXdE .node .label,#mermaid-svg-lOQp1NnE4vC5dXdE .image-shape .label,#mermaid-svg-lOQp1NnE4vC5dXdE .icon-shape .label{text-align:center;}#mermaid-svg-lOQp1NnE4vC5dXdE .node.clickable{cursor:pointer;}#mermaid-svg-lOQp1NnE4vC5dXdE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lOQp1NnE4vC5dXdE .arrowheadPath{fill:#333333;}#mermaid-svg-lOQp1NnE4vC5dXdE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lOQp1NnE4vC5dXdE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lOQp1NnE4vC5dXdE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lOQp1NnE4vC5dXdE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lOQp1NnE4vC5dXdE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lOQp1NnE4vC5dXdE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lOQp1NnE4vC5dXdE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lOQp1NnE4vC5dXdE .cluster text{fill:#333;}#mermaid-svg-lOQp1NnE4vC5dXdE .cluster span{color:#333;}#mermaid-svg-lOQp1NnE4vC5dXdE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lOQp1NnE4vC5dXdE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lOQp1NnE4vC5dXdE rect.text{fill:none;stroke-width:0;}#mermaid-svg-lOQp1NnE4vC5dXdE .icon-shape,#mermaid-svg-lOQp1NnE4vC5dXdE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lOQp1NnE4vC5dXdE .icon-shape p,#mermaid-svg-lOQp1NnE4vC5dXdE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lOQp1NnE4vC5dXdE .icon-shape .label rect,#mermaid-svg-lOQp1NnE4vC5dXdE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lOQp1NnE4vC5dXdE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lOQp1NnE4vC5dXdE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lOQp1NnE4vC5dXdE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

接收到的帧 D + R
模 2 除法:÷ G(x)(同一个多项式)
余数是否为 0?
✅ 传输无错,通过校验
❌ 检测到错误,丢弃或请求重传

因为 (D×xn+R)(D \times x^n + R)(D×xn+R) 恰好是 G(x)G(x)G(x) 的倍数,所以接收方把整个帧除以 G(x)G(x)G(x):

  • 余数为 0:说明数据没有出错(注意:CRC 只能"检错"不能"纠错",且极小概率下错误也可能恰好被整除而漏检);
  • 余数不为 0:说明数据一定出错了。

2.6 CRC 参数与常见标准

除了生成多项式,一个完整的 CRC 算法还包含 4 个参数,它们共同决定最终结果,缺一个都对不上

参数 含义
初始值 Init 移位寄存器计算的初始状态(常见 0x00 或 0xFFFF)
输入反转 RefIn 输入数据是否按位反转后参与计算
输出反转 RefOut 计算结果是否按位反转后输出
结果异或 XorOut 输出前是否与一个固定值异或

工业上常用的标准如下(测试向量:对 ASCII 字符串 "123456789" 计算):

算法 生成多项式(含隐式最高位) 初始值 输入反转 输出反转 结果异或 校验值(123456789)
CRC-8 0x07,x8+x2+x+1x^8+x^2+x+1x8+x2+x+1 0x00 0x00 0xF4
CRC-16/MODBUS 0x8005,x16+x15+x2+1x^{16}+x^{15}+x^2+1x16+x15+x2+1 0xFFFF 0x0000 0x4B37
CRC-32(以太网) 0x04C11DB7,x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1x^{32}+x^{26}+x^{23}+x^{22}+x^{16}+x^{12}+x^{11}+x^{10}+x^8+x^7+x^5+x^4+x^2+x+1x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1 0xFFFFFFFF 0xFFFFFFFF 0xCBF43926

后面实现代码时,我们会用这些标准值来验证程序是否正确。

3. CRC 的用途

CRC 是计算机和通信领域应用最广泛的检错手段之一,几乎无处不在:

  1. 有线/无线通信协议:以太网(IEEE 802.3)每个帧尾部都有 4 字节的帧检验序列 FCS(CRC-32);USB、蓝牙、CAN 总线、RS-485 等也都内置了 CRC 校验;
  2. 工业现场总线:Modbus-RTU 报文自带 CRC-16;Profibus、S7 通信等同样使用 CRC 族算法保障可靠性;
  3. 文件格式与压缩包:PNG 图像、ZIP/GZIP 压缩包用 CRC-32 检测文件块是否损坏,下载工具显示"校验失败"就是 CRC 在起作用;
  4. 存储设备:磁盘扇区、SSD、光盘刻录都会记录 CRC,用于在读取时发现介质损坏;
  5. 嵌入式与固件:固件升级时对 .bin 文件算 CRC 并在烧录后校验,防止烧录进"坏固件";通信协议帧普遍在末尾加 CRC 字段;
  6. 数据完整性校验:日志、配置文件、数据库页等在写入/读取时用 CRC 快速发现意外篡改或掉电损坏。

一句话总结:凡是"传数据怕出错"的场景,几乎都有 CRC 的身影。

4. CRC 推理实例

空谈原理不如动手算一遍。我们用一个小例子把整个计算过程完整走一遍。

参数设定

  • 数据:D=1011001D = 1011001D=1011001(7 位)
  • 生成多项式:G(x)=x4+x+1G(x) = x^4 + x + 1G(x)=x4+x+1,二进制为 10011(5 位),因此 CRC 位数 n = 4

步骤 1:数据左移 4 位(补 4 个 0)

D′=1011001 0000=10110010000 D' = 1011001\,0000 = 10110010000 D′=10110010000=10110010000

步骤 2:模 2 除法(只做异或,没有借位)

10110⊕10011=00101⇒余串 10101000010101⊕10011=00110⇒余串 110000011000⊕10011=01011⇒余串 10110010110⊕10011=00101⇒余串 1010(不足 5 位,停止) \begin{aligned} & 10110 \oplus 10011 = 00101 \quad \Rightarrow \text{余串 } 101010000\\ & 10101 \oplus 10011 = 00110 \quad \Rightarrow \text{余串 } 1100000\\ & 11000 \oplus 10011 = 01011 \quad \Rightarrow \text{余串 } 101100\\ & 10110 \oplus 10011 = 00101 \quad \Rightarrow \text{余串 } 1010 \quad \text{(不足 5 位,停止)} \end{aligned} 10110⊕10011=00101⇒余串 10101000010101⊕10011=00110⇒余串 110000011000⊕10011=01011⇒余串 10110010110⊕10011=00101⇒余串 1010(不足 5 位,停止)

每一步都取出当前余串的最高 5 位做异或,异或结果接上剩余位,再去掉前导 0,直到余串位数不足 5 位为止。

步骤 3:得到 CRC 校验码

CRC=1010 \boxed{CRC = 1010} CRC=1010

步骤 4:构造发送帧

发送帧=D+R=1011001+1010=10110011010 \text{发送帧} = D + R = 1011001 + 1010 = 10110011010 发送帧=D+R=1011001+1010=10110011010

步骤 5:接收端校验

接收方用同一个多项式 10011 去除整个帧 10110011010

10110011010÷10011=整除,余数为 0⇒校验通过,数据无误✓ 10110011010 \div 10011 = \text{整除,余数为 } 0 \quad \Rightarrow \text{校验通过,数据无误} \checkmark 10110011010÷10011=整除,余数为 0⇒校验通过,数据无误✓

如果传输中任意一位翻转为 0 或 1,余数几乎必然不为 0,错误就会被发现。

4.1 换个角度看:移位寄存器(等价推导)

上面的长除法,等价于 4 位移位寄存器逐位计算。寄存器初始为 0000,多项式取 10011 去掉最高位后的低 4 位 0011,每移入一个比特,若移出位为 1 则与 0011 异或:

序号 输入位 原寄存器 移出位 操作 新寄存器
1 1 0000 0 不移除 0001
2 0 0001 0 不移除 0010
3 1 0010 0 不移除 0101
4 1 0101 0 不移除 1011
5 0 1011 1 异或 0011 0101
6 0 0101 0 不移除 1010
7 1 1010 1 异或 0011 0110
8 0 0110 0 不移除 1100
9 0 1100 1 异或 0011 1011
10 0 1011 1 异或 0011 0101
11 0 0101 0 不移除 1010

处理完全部 7 个数据位 + 4 个补零位后,寄存器值 1010 正是 CRC。长除法和移位寄存器是同一件事的两种描述,而后者正是软件(和硬件)真正实现时用的方法。

5. CRC 代码实现

下面给出两种主流实现思路:按位计算 (直观、易理解,适合教学和低速场景)和查表法(一次处理一个字节,速度提升 8 倍,工业界最常用)。

(1)Matlab 代码实现

1.1 按位计算(与第 4 节实例完全一致)
matlab 复制代码
function crc_bits = crc_bitwise(data_bits, poly_bits)
% CRC 按位计算(模2除法 / 异或),与手工推导完全一致
% 输入:
%   data_bits : 数据位行向量, 如 [1 0 1 1 0 0 1]
%   poly_bits : 生成多项式位行向量, 如 [1 0 0 1 1]  (x^4 + x + 1)
% 输出:
%   crc_bits  : CRC 校验码位向量, 如 [1 0 1 0]
    n = length(poly_bits) - 1;            % CRC 位数 = 多项式次数
    bits = [data_bits, zeros(1, n)];      % 数据后补 n 个 0

    % 逐位模2除法: 当前位为 1 就与多项式异或
    for i = 1 : length(bits) - n
        if bits(i) == 1
            bits(i : i + n) = xor(bits(i : i + n), poly_bits);
        end
    end

    crc_bits = bits(end - n + 1 : end);   % 最后 n 位即余数
end

验证第 4 节的实例:

matlab 复制代码
data = [1 0 1 1 0 0 1];    % 数据 1011001
poly = [1 0 0 1 1];        % G(x) = x^4 + x + 1
crc  = crc_bitwise(data, poly);
disp(crc);                 % 输出: 1  0  1  0  ✓
1.2 查表法计算 CRC-32(以太网标准)
matlab 复制代码
function crc_out = crc32_lookup(data_bytes)
% 查表法计算 CRC-32(以太网标准, 多项式 0x04C11DB7 的反向形式 0xEDB88320)
% 输入: data_bytes - uint8 类型数组
% 输出: crc_out    - uint32 校验值
    crc = uint32(hex2dec('FFFFFFFF'));    % 初始值

    % 1. 生成 256 项查找表(只需生成一次)
    table = zeros(256, 1, 'uint32');
    for i = 0 : 255
        c = uint32(i);
        for j = 1 : 8
            if bitand(c, uint32(1))
                c = bitxor(bitshift(c, -1), uint32(hex2dec('EDB88320')));
            else
                c = bitshift(c, -1);
            end
        end
        table(i + 1) = c;
    end

    % 2. 逐字节查表计算
    for k = 1 : numel(data_bytes)
        idx = bitxor(bitand(crc, uint32(255)), uint32(data_bytes(k))) + 1;
        crc = bitxor(bitshift(crc, -8), table(idx));
    end

    % 3. 结果异或输出
    crc_out = bitxor(crc, uint32(hex2dec('FFFFFFFF')));
end

调用示例(用标准测试向量验证):

matlab 复制代码
data = uint8('123456789');
crc  = crc32_lookup(data);
fprintf('CRC-32 = 0x%08X\n', crc);   % 输出: CRC-32 = 0xCBF43926  ✓(标准值)

(2)C 代码实现

2.1 按位计算(通用函数)
c 复制代码
#include <stdint.h>
#include <stdio.h>

/**
 * 按位计算 CRC(MSB 优先,逐位移位 + 异或)
 * @param data     输入数据(如 0x59 = 0b1011001)
 * @param data_len 数据有效位数(如 7)
 * @param poly     CRC 多项式去掉最高位后的值
 *                 如 x^4+x+1(10011) → 低4位 0011 = 0x3
 * @param crc_len  CRC 位数(如 4)
 * @return CRC 校验值(低 crc_len 位有效)
 */
uint32_t crc_bitwise(uint32_t data, int data_len, uint32_t poly, int crc_len)
{
    uint32_t reg = 0;
    int i;
    for (i = 0; i < data_len + crc_len; i++) {
        /* 依次取出数据位,取完数据位后自动补 0(等效于末尾补 crc_len 个 0)*/
        uint32_t bit = (i < data_len) ? ((data >> (data_len - 1 - i)) & 1) : 0;
        uint32_t msb = (reg >> (crc_len - 1)) & 1;   /* 移出位 */

        reg = ((reg << 1) | bit) & ((1u << crc_len) - 1);
        if (msb) {
            reg ^= poly;
        }
    }
    return reg;
}

int main(void)
{
    /* 数据 1011001 = 0x59, 7 位有效位, G = x^4+x+1 → poly=0x3, CRC 4 位 */
    uint32_t crc = crc_bitwise(0x59, 7, 0x3, 4);
    printf("CRC = %04X\n", crc);       /* 输出: CRC = 000A (即 1010) ✓ */
    return 0;
}
2.2 查表法计算 CRC-16/MODBUS(工业总线最常用)
c 复制代码
#include <stdint.h>
#include <stddef.h>

#define CRC16_POLY 0xA001   /* 0x8005 按位反转得到的反向多项式 */

static uint16_t crc16_table[256];

/* 生成查找表(程序初始化时调用一次即可) */
void crc16_init(void)
{
    int i, j;
    for (i = 0; i < 256; i++) {
        uint16_t crc = (uint16_t)i;
        for (j = 0; j < 8; j++) {
            if (crc & 0x0001)
                crc = (crc >> 1) ^ CRC16_POLY;
            else
                crc >>= 1;
        }
        crc16_table[i] = crc;
    }
}

/* 计算 CRC-16/MODBUS: 初始值 0xFFFF, 输入输出反转, 结果不异或 */
uint16_t crc16_modbus(const uint8_t *buf, size_t len)
{
    uint16_t crc = 0xFFFF;
    size_t i;
    for (i = 0; i < len; i++) {
        crc = (uint16_t)((crc >> 8) ^ crc16_table[(crc ^ buf[i]) & 0xFF]);
    }
    return crc;
}

调用示例:

c 复制代码
#include <stdio.h>

int main(void)
{
    uint8_t data[] = "123456789";
    crc16_init();
    printf("CRC-16/MODBUS = 0x%04X\n", crc16_modbus(data, 9));
    /* 输出: CRC-16/MODBUS = 0x4B37  ✓(标准测试向量)*/
    return 0;
}
2.3 查表法计算 CRC-32(以太网/PNG/ZIP 标准)
c 复制代码
#include <stdint.h>
#include <stddef.h>

static uint32_t crc32_table[256];

/* 生成查找表 */
void crc32_init(void)
{
    int i, j;
    for (i = 0; i < 256; i++) {
        uint32_t crc = (uint32_t)i;
        for (j = 0; j < 8; j++) {
            crc = (crc & 1) ? (crc >> 1) ^ 0xEDB88320 : crc >> 1;
        }
        crc32_table[i] = crc;
    }
}

/* 计算 CRC-32: 初始值 0xFFFFFFFF, 输入输出反转, 结果异或 0xFFFFFFFF */
uint32_t crc32(const uint8_t *buf, size_t len)
{
    uint32_t crc = 0xFFFFFFFF;
    size_t i;
    for (i = 0; i < len; i++) {
        crc = crc32_table[(crc ^ buf[i]) & 0xFF] ^ (crc >> 8);
    }
    return crc ^ 0xFFFFFFFF;
}

调用示例:

c 复制代码
#include <stdio.h>

int main(void)
{
    uint8_t data[] = "123456789";
    crc32_init();
    printf("CRC-32 = 0x%08X\n", crc32(data, 9));
    /* 输出: CRC-32 = 0xCBF43926  ✓(标准测试向量)*/
    return 0;
}
2.4 按位法与查表法对比
对比项 按位法 查表法
计算速度 慢(每比特都要判断/异或) 快(一次处理 1 字节,快约 8 倍)
内存占用 几乎为 0 需 256 × CRC 位数的查找表(如 CRC-32 需 1KB)
代码复杂度 简单直观 稍复杂,但逻辑固定可复用
适用场景 教学、低速、表空间紧张(如 51 单片机小 RAM) 高速通信、大量数据、桌面/服务器程序

工程建议:追求速度用查表法;追求代码简洁或内存极小用按位法;如果 MCU 自带硬件 CRC 外设(如 STM32 的 CRC 单元),直接调用硬件即可,速度最快且不占 CPU。

6. 总结

CRC虽然是很简单的校验算法,但是在很多行业都有使用,理解其核心算法思想,后续在工作中可以随意结合其它算法使用。如果有什么不合理的地方,欢迎大家留言讨论!

相关推荐
予昊20 分钟前
从零实现“在线五子棋对战“:WebSocket 实时通信 + 段位匹配
java·开发语言·网络·websocket
罗西的思考30 分钟前
【OpenClaw具身硬件】ZeroClaw 源码阅读笔记(3)--- RAG
人工智能·算法·机器学习
weixin_461408581 小时前
Mybatis-flex小记
java·开发语言·mybatis
浪里镖客1 小时前
位姿转换矩阵写法-个人习惯(计算机理解其实是相反的)
线性代数·算法·矩阵
青梅味猪大肠2 小时前
【深入浅出C++】为什么虚表指针可以解决菱形继承
开发语言·c++
潘潘的嵌入式日记2 小时前
I²C 从机接收总被覆盖?双缓冲要在 STOP 时交接
c语言·开发语言·单片机
Lhan.zzZ3 小时前
QML 组件库:VS2022 + Qt 静态库方案
开发语言·c++·qt·visual studio
敲代码的嘎仔3 小时前
28届后端开发-海康威视日常实习一面(已OC)
java·开发语言·后端·面试·海康威视·实习·大厂
小白羊丨4 小时前
如何诊断 Prompt 模板导致的效果下降?
人工智能·算法·prompt