以二进制输出64位类型的数据_分析C++中基本数据类型间转换的一些深坑

6b70cab878a46248101407c91a66e9ef.png

经常写C代码的同学可能对类型之间的转换不会陌生,但是由于C的灵活性,基础数据类型之间的转换有时可能会带来很严重的问题。举个例子:

笔者曾经在设置OpenGL的viewport时,由于多个模块之间的代码历史遗留问题,出现了将float转成std::uint32_t的情况,在PC上一切正常,但到了手机就出现异常了,本来正常的画面显示不正常了,经排查发现是arm将负的float转换成unsigned int时截取到了0,而intel架构下则不截取。

当然类似的问题还有其它,因此这里总结一篇文章来详细解答一下相关的问题,本文以intel架构为例进行说明,对arm感兴趣的同学自行研究吧。

int32_t 和 uint32_t

int32_t 和 uint32_t属于兼容的数据类型,两者之间的转换不会损失任何数据,例如

int32_t a = -1;uint32_t b = a;int32_t c = b;

结果将是c = -1, b = 2^32 - 1。我们来看一下编译成汇编之后的结果,都是使用movl指令来进行拷贝赋值,movl指令仅仅作数据的拷贝,不作任何特殊处理:

movl $-1, -24(%rbp)movl -24(%rbp), %ecxmovl %ecx, -28(%rbp)movl -28(%rbp), %ecxmovl %ecx, -32(%rbp)

float转int32_t和uint32_t

float到(unsigned) int无法进行隐式类型转换,需要显示地进行强制类型转换,如下所示。

float a = -1.0f;int32_t i = (int32_t)a;uint32_t u = (uint32_t)a;

注意,前面已经说过,float到unsigned类型的转换并非ISO C++的标准,具体结果由编译器自行定义,因此这种转换可能会导致跨平台的兼容性问题。例如上面的结果在intel平台是i=-1, u = 2^32 - 1。但是在arm平台上,u的结果为0,也即是所有小于0的浮点数转成unsigned int时得到的结果都是0。

movl$-1082130432, -36(%rbp) ## imm = 0xBF800000cvttss2si-36(%rbp), %ecxmovl%ecx, -40(%rbp)cvttss2si-36(%rbp), %rdxmovl%edx, %ecxmovl%ecx, -44(%rbp)

其中-1082130432是IEEE754中-1.0f的二进制表示,可以发现将float转换成signed和unsigned都是使用cvttss2si指令来完成的,该指令的详情请参考(https://www.felixcloutier.com/x86/cvttss2si)。

32位到64位的转换

对于int32_t到int64_t和uint32_t到uint64_t很容易理解,从窄类型到宽类型转换,应当保持值的不变,即下面的结果在任何平台上的结果都应当是a == a64 == -1, b == b64 == 99999。

int32_t a = -1;int64_t a64 = a;uint32_t b = 99999;uint64_t b64 = b;

相关的汇编代码如下:

movl$-1, -8(%rbp)movslq-8(%rbp), %rcxmovq%rcx, -16(%rbp)movl$99999, -20(%rbp) ## imm = 0x1869Fmovl-20(%rbp), %edxmovl%edx, %ecxmovq%rcx, -32(%rbp)

movslq指令用来将32的有符号整数转换成64位的整数,其中q是64位指令的后缀。其中rcx是64位寄存器,其低32位与ecx重叠,因此将unsigned的32位整数赋值给64位整数时,只是将该整数的二进制数据拷贝到64位的低32位部分。

但如果是int32_t到uint64_t和uint32_t到int64_t会出现什么情况呢?

int32_t a = -1;

uint64_t a64 = a;

uint32_t b = 99999;

int64_t b64 = b;

其相应的反汇编代码如下:

movl$-1, -8(%rbp)movslq-8(%rbp), %rcxmovq%rcx, -16(%rbp)movl$99999, -20(%rbp) ## imm = 0x1869Fmovl-20(%rbp), %edxmovl%edx, %ecxmovq%rcx, -32(%rbp)

这里用到了一个指令movslq,其作用是将32bit的整型扩展成64bit的整型,其扩展方式为将最高位的符号位移动到所有的32-63位处,即

dst[63:32] = src[31]dst[31:0] = src[31:0]

由于a = -1, 其二进制表示为0b1111...1, 因此扩展a64=0b1111....1,即a64=18446744073709551615,至于为什么要这样处理,就需要去了解一下``补码`相关的知识了。至于b64=b就很容易理解了,直接将b移动到其低32位即可,即rcx寄存器的低32位。

另外至于从64位到32位的转换,这里就不展开了,因为无论编译器如何处理,都可能会有数据丢失的情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值