如何将 CRC16-CCITT 算法的 C++ 代码转换为 Python 代码?

How can I convert C++ code of a CRC16-CCITT algorithm to Python code?

我有一个用 C++ 编写的 CRC16-CCITT 算法示例代码,我需要帮助将其转换为 Python。

示例 C++ 代码:

#include<iostream>

using namespace std;

unsigned short calculateCRC(unsigned char data[], unsigned int length)
{
        unsigned int i;
        unsigned short crc = 0;

        for(i=0; i<length; i++){
                crc = (unsigned char)(crc >>8) | (crc<<8);
                crc ^= data[i];
                crc ^= (unsigned char)(crc & 0xff) >> 4;
                crc ^= crc << 12;
                crc ^= (crc & 0x00ff) << 5;
        }

        return crc;
}

int main()
{
        unsigned int length;
        length = 15;

        unsigned char data[length] = {0x01,0x08,0x00,0x93,0x50,0x2e,0x42,0x83,0x3e,0xf1,0x3f,0x48,0xb5,0x04,0xbb};
        unsigned int crc;
        crc =  calculateCRC(data, length);
        cout<< std::hex << crc << '\n';
}

此代码给出正确的输出 9288。

我在 Python 中尝试了以下方法:

#!/usr/bin/env python3

def calculateCRC(data):
    crc = 0

    for dat in data:
        crc = (crc >> 8) or (crc << 8)
        crc ^= dat
        crc ^= (crc and 0xff) >> 4
        crc ^= crc << 12
        crc ^= (crc and 0x00ff) << 5
    crc = hex(crc)
    return (crc)


data = [0x01,0x08,0x00,0x93,0x50,0x2e,0x42,0x83,0x3e,0xf1,0x3f,0x48,0xb5,0x04,0xbb]
print(calculateCRC(data))

这输出 0xf988334b0799be2081。

你能帮我理解我做错了什么吗? 谢谢。

Python 的 int 类型是无界的,但 C/C++ unsigned short 值以 2 个字节表示,因此当您向左移动时会溢出。您需要在 Python 中添加 masking 以达到相同的效果,其中您删除高于第 16 个最高有效位的任何位。这仅在值向左移动时才需要,因为右移已经删除了最右边的旋转位。

接下来,你在混淆| and & bitwise operators with or and and boolean logical operators。 C++ 代码使用按位运算符,在 Python.

中使用相同的运算符

最后但同样重要的是,将十六进制转换留给调用者,不要在 CRC 函数本身中执行此操作:

UNSIGNED_SHORT_MASK = 0xFFFF  # 2 bytes, 16 bits.

def calculateCRC(data):
    crc = 0
    for dat in data:
        crc = (crc >> 8) | (crc << 8 & UNSIGNED_SHORT_MASK)
        crc ^= dat
        crc ^= (crc & 0xff) >> 4
        crc ^= crc << 12 & UNSIGNED_SHORT_MASK
        crc ^= (crc & 0x00ff) << 5
    return crc

现在你得到相同的输出:

>>> print(format(calculateCRC(data), '04x'))
9288

我使用 format() function 而不是 hex() 来创建没有 0x 前缀的十六进制输出。

正如 Mark Adler 正确指出的那样,我们不需要为每个左移操作都屏蔽;仅仅因为 C/C++ 操作自然会产生屏蔽值,并不意味着我们需要在这里经常这样做。每次迭代屏蔽一次就足够了:

def calculateCRC(data):
    crc = 0
    for dat in data:
        crc = (crc >> 8) | (crc << 8)
        crc ^= dat
        crc ^= (crc & 0xFF) >> 4
        crc ^= crc << 12
        crc ^= (crc & 0x00FF) << 5
        crc &= 0xFFFF
    return crc

我们可以应用更多的捷径来削减操作,从而加快操作速度,但如果速度确实是个问题,我会在 Cython 或 C 或其他本机编译选项中重新实现它, 反正.

另请注意,您可以使用 bytes 对象,而不必使用整数列表:

data = b'\x01\x08\x00\x93\x50\x2e\x42\x83\x3e\xf1\x3f\x48\xb5\x04\xbb'

遍历 bytes 对象仍然会为您提供 0 到 255 之间的整数,就像 C++ 中的 char 数组一样。

最后,您实际上不必自己翻译代码,您可以只使用像 crccheck 这样的现有项目,它实现了这个特定的 CRC16 变体以及许多其他变体:

>>> from crccheck.crc import CrcXmodem
>>> print(format(CrcXmodem.calc(data), '04x'))
9288

crccheck写成纯Python。对于本机实现,有 crcmod。这个库的文档有点欠缺,但也很灵活,功能强大,居然包含了预定义的函数:

>>> from crcmod.predefined import mkPredefinedCrcFun
>>> xmodem = mkPredefinedCrcFun('xmodem')
>>> print(format(xmodem(data), '04x'))
9288