0%

CRC-CCITT 的一种实现及简要解释

计组老师要求实现一个 CRC-CCITT 计算。查了好久,实现倒是有不少,几乎都没有讲原因。可能是只有我不懂吧。

总之,挑了个比较简单的来解释一下。

本文假设读者已经知晓 CRC 的用途和手工计算方法。

实现

Wiki 上讲 CRC 计算的页面在这里,伪代码实现,有直接按字节计算,也有查表计算。其中第一张演示表比较能帮助理解。

至于 C 语言的实现,在这里。这个平台偶尔查查文档还是可以的。以下是稍微修改过的代码,逻辑和手算没有很大区别。

以下是一个可以运行的代码(gcc 8.1.0),接收一个字符流,返回 CRC 校验和,生成多项式为 0x11021

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned short CRC16_CCITT(const unsigned char *data, int dataLen) {
unsigned short dividen = 0x0000;
unsigned short segbuff = 0x0000;
constexpr unsigned short divider = 0x1021;
while (dataLen--) {
segbuff = *data; // take
data++; // prepare for next byte of data
dividen ^= (segbuff << 8); // ADD to dividen
for (int i = 0; i < 8; ++i) {
if (dividen & 0x8000) {
// DIVIDE(substract) from
dividen = (dividen << 1) ^ divider;
} else {
dividen <<= 1;
}
}
}
return dividen;
}

因为模 2 除法在被除数和除数最高位都为 1 时才上商,首位必为零,我们可以省去除数(即生成多项式)的高位 1,以 0x1021 存储。这样一来除数恰有 2 字节长,结果也应有 2 字节。我们维护一段 2 字节的被除数(或者说商)即可。

实际上,只需要明白两点就可以理解这段代码:

  1. 模 2 除法即模 2 减法
  2. 模 2 加法和模 2 减法,都是异或操作
以下是详细解释 我们维护两个字节的余数,但每次只从字符流中取一个字节的数据。这个字节应该放到两个字节的高处。 主要的部分是一个 `while` 循环。每次,我们从字符流中获取一个字节的内容到 `segbuff` 中(此时字符流指针可以后移),接着通过异或操作加到被除数 `dividen` 的高字节上。 现在我们观察被除数。以一般的手算流程,最高位是 1 时可以发生一次减法,即与 `divider` 异或一次,注意因为除数省去了一个高位 1,需要先移位再减;不是 1 时,则移位去看下一位。每次从字符流获取 1 字节,所以这样的判断执行 8 次即可。 8 次后我们就有了目前为止的余数,同时右移了 8 位,下一个字节的字符要想加到正确的位置(连续的位置),也应该右移 8 位。 如此循环,维护余数的两个字节通过在字符流上“滑动”来完成计算。

CRC 原理以及可靠性证明

见此,一篇很棒的文章,但是出处好像遗失了。实际上看这篇基本就够了。

多说两句

可以看到对于 01 串这样的多项式来说,位权是非常灵活的,只需要保持好相对位置就可以随意操作,同时许多算法也只需要“相对位置”就能工作。

附一个 CRC 计算器。上面代码的结果和 CRC-CCITT(XModem) 的结果一致。