669为什么变成了413

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;
}

运行结果如下:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注