669为什么变成了413
1.现象
这是一个在实际开发过程中发现的问题:我们从平台A传了一个数字669,到平台B上后,变成了413。
后来发现数字在传输过程中并没有变化,只不过解析的时候出了问题。
为了简化表述,我们写了一个示例程序来模拟实际场景,如下:
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char** argv)
{
unsigned short send_num = 669; // 要发送的数字
char send_buf[2]; // 发送缓冲区,2字节
//按照大端序写入发送缓冲区
send_buf[0] = (char)(send_num >> 8);
send_buf[1] = (char)send_num;
//发送到客户端
//...
//客户端收到
char recv_buf[2];
memcpy(recv_buf, send_buf, 2);
//错误的接收方式
unsigned short recv_num = (recv_buf[0] << 8) + recv_buf[1];
printf("send_num = %u\n", send_num);
printf("recv_num = %u\n", recv_num);
return 0;
}
运行结果如下:
可以看到669变成了413,这就是我们遇到的问题。
2.探究
1)是发送的时候写错了么?
669的二进制表示是这样的:
即:00000010 10011101
我们用下列代码把缓冲区中实际的二进制数值打印出来:
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
//把char中的整数(0~255)以二进制形式存放在一个long中,可以用%08ld打印出来
long bin_by_int(unsigned char chr_num)
{
unsigned char mask = 0x80; // 1000 0000
long ret = 0;
for (int i = 0; i < 8; ++i)
{
ret *= 10;
if (chr_num & mask)
ret += 1;
chr_num = chr_num << 1;
}
return ret;
}
int main(int argc, char** argv)
{
unsigned short send_num = 669; // 要发送的数字
char send_buf[2]; // 发送缓冲区,2字节
//按照大端序写入发送缓冲区
send_buf[0] = (char)(send_num >> 8);
send_buf[1] = (char)send_num;
//发送到客户端
//...
//客户端收到
char recv_buf[2];
memcpy(recv_buf, send_buf, 2);
//错误的接收方式
unsigned short recv_num = (recv_buf[0] << 8) + recv_buf[1];
printf("send_num = %u\n", send_num);
printf("send_buf = %08ld %08ld\n", bin_by_int(send_buf[0]), bin_by_int(send_buf[1]));
printf("recv_num = %u\n", recv_num);
printf("recv_buf = %08ld %08ld\n", bin_by_int(recv_buf[0]), bin_by_int(recv_buf[1]));
return 0;
}
运行结果如下:
我们发现缓冲区中的二进制其实是对的,那么问题只可能出在了解析上。
2)为什么00000010 10011101会被解释成413呢?
我们把413的二进制表示打印出来:
这两个数的二进制表示还挺相似的:
669 : 00000010 10011101
413 : 00000001 10011101
乍一看,我们很自然地认为是前面那个字节解释错了,应该是00000010而不是00000001。但是仔细一想不应该啊,进行位移的时候就应该移8位,所以不应该错的。
这时候后面这个二进制串引起了我们的注意。1通常是符号位,难不成是被解释成负数了?
回忆下大学时候的知识:
- 正数符号位是0,数值部分用原码表示;
- 负数符号位是1,数值部分用补码表示,即除符号位取反再加1。
那么10011101代表的数值其实是负的0011101减1再取反,即负的1100011,即-99。
前一个字节代表的数是2,根据算式,recv_num的值,其实是2<<8 – 99,即512-99,即413。解释通了。
3.修订
根据上面的分析, 我们其实是错把recv_buf[1]中的数值解释成了负数了。原因就是我们定义的recv_buf类型是char而不是unsigned char,因此计算的时候,recv_buf被隐式转换为了有符号整型而不是无符号整型。
解决方法也非常简单,把recv_buf定义为unsigned char数组即可,这样隐式转换的时候就不会别解释成负值了。代码如下:
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
//把char中的整数(0~255)以二进制形式存放在一个long中,可以用%08ld打印出来
long bin_by_int(unsigned char chr_num)
{
unsigned char mask = 0x80; // 1000 0000
long ret = 0;
for (int i = 0; i < 8; ++i)
{
ret *= 10;
if (chr_num & mask)
ret += 1;
chr_num = chr_num << 1;
}
return ret;
}
int main(int argc, char** argv)
{
unsigned short send_num = 669; // 要发送的数字
char send_buf[2]; // 发送缓冲区,2字节
//按照大端序写入发送缓冲区
send_buf[0] = (char)(send_num >> 8);
send_buf[1] = (char)send_num;
//发送到客户端
//...
//客户端收到
unsigned char recv_buf[2];
memcpy(recv_buf, send_buf, 2);
//错误的接收方式
unsigned short recv_num = (recv_buf[0] << 8) + recv_buf[1];
printf("send_num = %u\n", send_num);
printf("send_buf = %08ld %08ld\n", bin_by_int(send_buf[0]), bin_by_int(send_buf[1]));
printf("recv_num = %u\n", recv_num);
printf("recv_buf = %08ld %08ld\n", bin_by_int(recv_buf[0]), bin_by_int(recv_buf[1]));
return 0;
}
运行结果如下: