1. 背景
在Qt中,我们可以使用QDataStream
对许多Qt内置的数据类型进行序列化和反序列,具体支持的列表可以参看Serializing Qt Data Types。关于QDataStream
的详细说明可以参看Serializing Qt Data Types。
本篇博文并不讨论如何使用QDataStream
完成序列化和反序列化,而是讨论本人在实际开发中遇到的一个64-bit整型数序列化和反序列化的问题。由于本人参与的项目是需要支持Windows和Linux两个操作系统,本人先在Windows完成了开发,然后另一个同事在Linux上进行跨平台验证时发现了编译错误。
2. 问题描述
在Qt工程源码中,存在下面一段代码:
int xxx::deserialize(QDataStream & stream, void *holder)
...
uint64_t objCount = 0;
stream >> objCount;
....
}
在Linux系统上编译工程代码时,出现了以下编译错误:
[build] xxx:xx:xx: error: invalid operands to binary expression ('QDataStream' and 'uint64_t' (aka 'unsigned long'))
[build] stream >> objCount;
[build] ~~~~~~ ^ ~~~~~~~~
...
[build] /opt/Qt/5.15.2/gcc_64/include/QtCore/qdatastream.h:166:18: note: candidate function not viable: no known conversion from 'uint64_t' (aka 'unsigned long') to 'qint32 &' (aka 'int &') for 1st argument
[build] QDataStream &operator>>(qint32 &i);
[build] ^
[build] /opt/Qt/5.15.2/gcc_64/include/QtCore/qdatastream.h:386:34: note: candidate function not viable: no known conversion from 'uint64_t' (aka 'unsigned long') to 'quint32 &' (aka 'unsigned int &') for 1st argument
[build] inline QDataStream &QDataStream::operator>>(quint32 &i)
[build] ^
[build] /opt/Qt/5.15.2/gcc_64/include/QtCore/qdatastream.h:168:18: note: candidate function not viable: no known conversion from 'uint64_t' (aka 'unsigned long') to 'qint64 &' (aka 'long long &') for 1st argument
[build] QDataStream &operator>>(qint64 &i);
[build] ^
[build] /opt/Qt/5.15.2/gcc_64/include/QtCore/qdatastream.h:389:34: note: candidate function not viable: no known conversion from 'uint64_t' (aka 'unsigned long') to 'quint64 &' (aka 'unsigned long long &') for 1st argument
[build] inline QDataStream &QDataStream::operator>>(quint64 &i)
[build] ^
...
从编译错误中可以看到,编译器尝试将uint64_t
逐个匹配QDataStream
支持的各种整型类型,但是都失败了。并且,编译器还给出了它们实际的类型定义:
- ‘uint64_t’ (aka ‘unsigned long’)
- ‘qint32 &’ (aka ‘int &’)
- ‘quint32 &’ (aka ‘unsigned int &’)
- ‘qint64 &’ (aka ‘long long &’)
- ‘quint64 &’ (aka ‘unsigned long long &’)
最接近的整型定义中,确实一个也匹配不上(😦)。看到这里,编译失败的原因找到了,也很显然。
3. 问题分析
在Linux上编译失败的原因找到了,但是秉着不见棺材不掉泪(刨根问底)的精神,还是发出了灵魂深处的疑问:为啥Windows上能正常编译通过呢?既然自己都诚心诚意地发问了,那就继续看看源码吧。
先来看看Linux上关于这几个整数的定义:
-
C++ STL:
// in <cstdint> namespace std { ... using ::uint64_t; // refer to <stdint-uintn.h> ... } // in <stdint-uintn.h> typedef __uint64_t uint64_t; // refer to <types.h> // in <types.h> typedef unsigned long int __uint64_t;
可以看到,在Linux平台上,C++ STL的
uint64_t
最终解析为unsigned long int
,确实就如编译器给出的错误提示一致。 -
Qt:
// in <qglobal.h> typedef signed char qint8; /* 8 bit signed */ typedef unsigned char quint8; /* 8 bit unsigned */ typedef short qint16; /* 16 bit signed */ typedef unsigned short quint16; /* 16 bit unsigned */ typedef int qint32; /* 32 bit signed */ typedef unsigned int quint32; /* 32 bit unsigned */ #if defined(Q_OS_WIN) && !defined(Q_CC_GNU) ... typedef __int64 qint64; /* 64 bit signed */ typedef unsigned __int64 quint64; /* 64 bit unsigned */ #else ... // selected here typedef long long qint64; /* 64 bit signed */ typedef unsigned long long quint64; /* 64 bit unsigned */ #endif
由于是在Linux平台上,并没有定义
Q_OS_WIN
,所以qint32
、quint32
、qint64
、quint64
最终解释如前面编译错误中提到的一致。看到这里,我们可以大胆地猜一下,在Windows下,__int64
的定义应该是和STL的定义是一致的。
那我们就检查一下Windows上的头文件:编译器版本MSVC(14.32.31326)
C++ STL:
// in <cstdint>
#define _CSTD ::
using _CSTD uint64_t; // refer to <stdint.h>
// in stdint.h
typedef unsigned long long uint64_t;
查看微软C++手册得知__int64
是编译器内置类型,解释如下:
The __int64 type is synonymous with type
long long
.
那么Qt中的typedef unsigned __int64 quint64;
自然就被解析为typedef unsigned long long quint64;
了,那么,它就能与uint64_t
匹配上,编译通过。
推广一下,int64_t
同样也会在Linux 64-bit上遇到相同的问题。
4. 解决方案
知道了原因后,问题就很好解决了,有两种方案:
- 方案1:把
uint64_t
改为quint64
,这是很符合直觉的,既然已经使用了Qt的序列化和反序列化工具,那就按照Qt的要求来编码; - 方案2:在Linux上分别为
int64_t
和uint64_t
实现重载函数。