Qt/C++ QDataStream序列化和反序列化64-bit整型的问题

6 篇文章 1 订阅

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,所以qint32quint32qint64quint64最终解释如前面编译错误中提到的一致。看到这里,我们可以大胆地猜一下,在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_tuint64_t实现重载函数。

5. Reference

  1. Can’t Overload << Operator on 64-bit Linux
  2. __int8, __int16, __int32, __int64
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值