c语言趣谈:不同数据类型变量的计算问题

最近在写一个AD驱动的时候遇到了一个问题:

24位(bit)的数据,以补码形式传上来,如何转换为正确的补码形式存储在上位机中,从而得到正确的电压值。

为什么这是一个问题呢?

在计算机中,所有的数据类型都是2的幂次方的bit长度,比如8bit,16bit,32bit。但是如果24bit的一个数字,以补码的形式传上来,赋值给一个32bit的数据类型,这里就会有坑。

这里的具体问题是,传上来的数据以byte为单位,3个byte组成一个24bit的数据,代表了一个电压值,这里是由于AD自身的属性。

举个例子,某个电压传上来的数据为0x80 0x80 0x80,软件需要将这3个byte合并为24bit的形式,也就是0x808080,这个操作通过位运算就可以进行计算,但是这里有2个问题:

  1. 24bit通过移位赋值给32bit,前边的高八位是怎么处理的?
  2. 如果将0x808080复制给一个32bit的int类型,那么符号位如何处理?

重点来了,上边说的这两个操作,windows下和linux下编程结果是不一样的,先看一下代码。

void main(){
    char a = 0x80;
    char b = 0x80;
    char c = 0x80;
    int32_t temp;   

    temp = (a<<16) | (b<<8) | (c);    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
    printf("%x\n", temp);
    printf("%d\n", temp);
}

运行结果如下

808080
8421504

可以看到,16进制的输出正常,但是转换为10进制以后就不是我们要的结果了,因为很显然80开头,结果应该是一个负数啊。
我们改写一下代码,将结果逐步输出。

void main(){
    char a = 0x80;
    char b = 0x80;
    char c = 0x80;
    int32_t temp;   
    printf("%x\n", temp);
    // temp = (a<<16) | (b<<8) | (c);    
    temp=a;
    printf("%x\n", temp);
    temp=temp<<8;
    temp=(temp|(b));
    printf("%x\n", temp);
    temp=temp<<8;
    printf("%x\n", temp);
    temp=(temp|(c));                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     

    printf("%x\n", temp);
    printf("%d\n", temp);
}
0
80
8080
808000
808080
8421504

可以看到,拼接移位的过程是按照我们想的来的,但是其实结果得到的是

0x00808080

也就是负数变正数了,当然如果本身就是正数,也就是24bit的数据最高位不是1,那么结果是对的,但是负数结果就不对了。这个时候可以这么操作。

void main(){
    char a = 0x80;
    char b = 0x80;
    char c = 0x80;
    int32_t temp;   
    if ((a&0x80)==0) 
        temp = (a<<16) | (b<<8) | (c);    
    else
        temp = ((a<<16) | (b<<8) | (c)) | 0xff000000;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             

    printf("%x\n", temp);
    printf("%d\n", temp);
}

区分一下第一个byte的最高位是不是1,如果是1,需要将0x00808080变为0xFF808080,这样一个操作将符号位移位到最前边,在做补码到源码的操作的时候,不会出现错误。最终结果为

ff808080
-8355712

这个补码转换为10进制是对的。

这里我们可以看到,在linux下,不同长度数据在进行计算的时候,会给不足长度补0,这一点非常重要。

windows里边的处理方法又不一样了,直接说结论,windows下会在这种操作的时候补1,我们看一下位移过程。
先看一下windows下的代码

void main(){
    char a = 0x80;
    char b = 0x80;
    char c = 0x80;
    __int32 temdata;
    temdata=a;
    printf("%x\n", temdata);
    temdata=temdata<<8;
    printf("%x\n", temdata);
    temdata=(temdata|(b));
    printf("%x\n", temdata);
    temdata=temdata<<8;
    printf("%x\n", temdata);
    temdata=(temdata|(c));
    printf("%x\n", temdata);
    printf("%d\n", temdata);
}

结果
在这里插入图片描述
可以看到,windows下虽然进行了补1的操作,但是在移位后的或运算上,后来补上的1会影响之前的数字,因此修改了代码

void main(){
    char a = 0x80;
    char b = 0x80;
    char c = 0x80;
    __int32 temdata;
    temdata=a;
    printf("%x\n", temdata);
    temdata=temdata<<8;
    printf("%x\n", temdata);
    temdata=(temdata|(b&0x000000ff));
    printf("%x\n", temdata);
    temdata=temdata<<8;
    printf("%x\n", temdata);
    temdata=(temdata|(c&0x000000ff));
    printf("%x\n", temdata);
    printf("%d\n", temdata);
}

这样结果就是对的了。
在这里插入图片描述

结论

windows下和linux下在一些默认的处理上会有不同,尤其在跨平台的项目中尽量不要把一些数据有关的东西交给处理器自己去处理,比如我们定义的32bit的临时变量,就是一个我们不知道处理器会如何做的变量。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gaosiy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值