回顾Qt中的字节序错误及其修复方法

2021-01-02 17:09:55

如您所知,我是Debian中的Qt 5维护者。维护Qt不仅意味着每次发布新版本时都会对版本进行升级,而且还确保确保Qt在Debian支持的所有架构上成功构建(对于某些子模块,自动测试通过)。

生成失败的一种重要类型是字节序特定的失败。最广泛使用的体系结构(x86_64,aarch64)是小端的。但是,Debian官方支持一个大字节序架构(s390x),并且非正式地提供了更多端口,例如ppc64和sparc64。

不幸的是,Qt上游在其CI系统中没有任何大型endian机器,因此只有当软件包无法在我们的构建守护进程中构建时,字节序问题才被注意到。在过去的几年中,我发现并修复了Qt各个部分中的此类问题,因此我决定写一篇文章来说明如何编写真正的跨平台C / C ++代码。

此处的代码将图像序列化为QImage :: Format_ARGB32格式,然后将字节传递到WebP的导入函数中。通过这种格式,图像将使用32位ARGB格式(0xAARRGGBB)存储。这意味着字节将在big endian上为0xBB,0xGG,0xRR,0xAA或Little Endian和0xAA,0xRR,0xGG,0xBB。但是,WebPPictureImportBGRA希望在所有体系结构上使用第一种格式。

解决方法是使用QImage :: Format_RGBA8888。正如QImage文档所说的,如果以字节0xRR,0xGG,0xBB,0xAA读取,则这种格式的颜色顺序在任何体系结构上都是相同的。

该代码似乎已经支持big endian。但是也许您可以发现错误?

这是缺少的逗号!它存在于小字节序块中,而不存在于大字节序块中。这是微不足道的。

结构数据{quint32 m_index:IndexBits; quint32 m_counter:CounterBits; quint32 m_unused:2; };联合{数据d; quint32 m_handle; };

声明了大小,例如IndexBits + CounterBits + 2始终等于32(四个字节)。

m_handle的值会根据字节序的不同而不同!因此,在给定构造函数参数的情况下,期望特定值的测试失败了。我通过使用以下宏对其进行了修复:

#if Q_BYTE_ORDER == Q_BIG_ENDIAN#定义GET_EXPECTED_HANDLE(qHandle)(((qHandle.index()<<(qHandle.CounterBits + 2))+(qHandle.counter()<<< 2))#else / * Q_LITTLE_END * /#定义GET_EXPECTED_HANDLE(qHandle)(qHandle.index()+(qHandle.counter()<< qHandle.IndexBits))#endif

QML编译器使用了一个名为LEUInt32的帮助程序类(基于QLEInteger),该类始终将这些数字内部存储在little endian中。该类可以在小字节序系统上安全地与本地quint32混合,但在大字节序系统上可以安全地混合。

通常,编译器会警告类型不匹配,但是这里的代码使用了reinterpret_cast,例如:

因此在构建时并未注意到这一点,但是编译器崩溃了。该修复程序再次变得微不足道,将quint32替换为QLEUInt32。

QModbusPdu :: FunctionCode是一个枚举,所以代码是一个多字节值(即使只有一个字节有效)。但是,(char *)(& code)返回一个指向其第一个字节的指针。在小字节序系统上是必需的字节,但在大字节序系统上是错误的字节!

quint8 codeByte = 0; if(stream。readRawData((char *)(& codeByte),sizeof(quint8))!= sizeof(quint8))返回流; QModbusPdu :: FunctionCode代码=(QModbusPdu :: FunctionCode)codeByte;

顾名思义,此函数检查字符串是否为ASCII。它通过将字符串分成4个字节的块来做到这一点:

while(ptr + 4< = end){quint32 data = qFromUnaligned< quint32> (ptr); if(data& = 0x80808080U){uint idx = qCountTrailingZeroBits(data); ptr + = idx / 8;返回false; } ptr + = 4; }

idx / 8是结尾的零字节数。但是,在小字节序上拖尾的字节实际上在大字节序上领先!因此,我们可以在那里使用qCountLeadingZeroBits。

if(bytesNeeded< = 2){read_bytes_unchecked(it,& it-> extra,1,bytesNeeded);如果(bytesNeeded == 2)-> extra = cbor_ntohs(--> extra); }

extra的类型为uint16_t,因此有两个字节。当我们只需要一个字节时,我们读入错误的字节,因此在big endian上生成的数字比应有的数字高256倍。添加一个临时的一字节变量将其修复。

在小端系统上,qToLittleEndian是无操作的,但在大端系统上,它是为某些已知类型定义的模板函数。 但是事实证明,我们需要将枚举值显式转换为简单类型,因此该修订将qint32(QDataStream :: Qt_DefaultCompiledVersion)传递给该函数。 测试中的代码试图使用reinterpret_cast将数字表示为字节序列: 在小字节序和大字节序系统上,字节顺序将有所不同。此修复程序将以下行添加到函数的开头: 附言 我们正在寻找新人来帮助维护Qt6。如果您想做如上所述的有趣工作,请加入我们的团队!