正确解释有符号与无符号

Posted

技术标签:

【中文标题】正确解释有符号与无符号【英文标题】:Correct interpretation signed vs unsigned 【发布时间】:2022-01-22 10:12:31 【问题描述】:

我从通信中接收 2 个字节,然后我需要合并这 2 个值以获得 16 位值。

现在假设我期望收到数字 200,那么这两个字符是 ab

char a=0x00;
char b=0xc8;
int  cw = (a << 8) | b ;
printf("cw= %d\n",cw);

进行合并,变量 cw 变为 -56 而不是 200

如果我将 char 更改为 unsigned char 我得到了正确的值 200 我该如何解决?我希望收到正数和负数,当然还有数字 200

【问题讨论】:

首先,绝对使用unsigned char 来读取您正在阅读的字节。如果您可以假设发送和接收系统对负数使用相同的表示(这当然是一个安全的假设,因为它们几乎肯定都使用二进制补码),那么剩下的唯一问题是在这种情况下对负数进行符号扩展您机器上的整数大于 16 位。类似于if(cw &amp; 0x8000) cw |= 0xffff0000; 好的,感谢您的评论,我更改为 unsigned char,我的机器将 int 解释为 32 位,我将其更改为短(16 位),现在可以正常工作 等等。您希望能够处理 all 8 位有符号数字(例如 -128...127) 值 200 吗?由于后者不在前者的范围内,并且您正在接收八位字节,那么您区分 -56 和 200 的计划是什么?听起来可能是这样,但我不会重复你的问题。我问的是不同的。如果您需要处理接收 -56(根据您的说法是可能的),与接收同样由八位字节表示的 200 相比,该八位字节是什么样的? @WhozCraig:200 是将 0x00 放入有符号 16 位整数的高八位并将 0xc8 放入低八位的预期结果。 @WhozCraig 他正在接收 16 位,所以他应该没问题。 -56 是ffc8,200 是00c8 【参考方案1】:

C 标准基本上没有提供将 1 位移入或移出符号位置的方法(&lt;&lt; 唯一定义的情况是针对不溢出的非负值),也没有明确的方法来转换无符号值到负值(超出范围的值到有符号整数类型的转换是实现定义的)。

所以我们不应该使用班次。但是,负值的乘法当然是定义的,所以我们可以使用:

int8_t  a;
uint8_t b;
// Put code here to receive a and b by some method.
uint16_t cw = a*256 + b;

如果您必须从无符号类型重构有符号整数,那么一种选择是测试符号位并手动应用二进制补码:

unsigned char a, b;
// Put code here to receive a and b by some method.
int cw = (a & 0x7f) << 8 | b;  // Assemble the low 15 bits.
if (a & 0x80)
    cw += -128*256;            // If sign bit is set, adjust.

您也可以复制以下位:

unsigned char a, b;
// Put code here to receive a and b by some method.
int16_t cw;
memcpy(&cw, (uint16_t [])  (uint16_t) a << 8 | b , sizeof cw);

(以上假设您的 16 位整数使用二进制补码。)

【讨论】:

【参考方案2】:

16 位值。

只要使用正确的类型。

unsigned char a = 0x00;
unsigned char b = 0xc8;
int16_t cw = ((unsigned int)a << 8) | b;

【讨论】:

诚实的问题:如果a 是例如0xFF,你不会冒险/导致有符号整数溢出 - >导致未定义的行为吗?如果不是在 32 位系统上,如果 word-size 是 16 位呢? a 的高位为on 时,(a &lt;&lt; 8) | b 超出int16_t 的范围,转换为int16_t 是实现定义的,尽管事实上int16_t 被指定为二进制补码。另外,如果int 是16 位,那么a &lt;&lt; 8 会溢出,并且行为未定义。 哦,你找到我了。当int 为16 位时,a &lt;&lt; 8 将写入符号位。让我们投射吧。 感谢您澄清@EricPostpischil - 卡米尔我仍然认为这是有问题的。 cw 应该是 uint16_t 或者如果必须签名,至少大于 16 位。【参考方案3】:

您应该做的唯一更改是定义无符号的最低有效字节:

char a;
unsigned char b;
... // receive a and b from your communication
int cw = (a << 8) | b;
printf("cw = %d\n", cw);

算术/逻辑表达式应该可以工作,但解释它为什么不溢出可能并非易事,因为它涉及将charunsigned char 提升为int(我猜是16 位在您的系统上)。

如果您希望您的代码可移植(即不假定 16 位 int 或您特定平台的任何其他属性),请使用具有定义大小的整数,并进行显式转换。

int8_t a;
uint8_t b;
... // receive a and b from your communication
int16_t cw = (int16_t)((int16_t)a << 8) | (int16_t)b;
printf("cw = %d\n", (int)cw);

但是这段代码的可读性较差,所以我不确定更便携有什么优势。

【讨论】:

C 标准没有定义负值的左移,即使类型是int16_t a &lt;&lt; 8(int16_t)a &lt;&lt; 8 如果 a 为负数,则会导致未定义的行为

以上是关于正确解释有符号与无符号的主要内容,如果未能解决你的问题,请参考以下文章

Verilog -- 有符号与无符号运算

C之有符号与无符号

第2课 有符号与无符号

关于:有符号与无符号整数的大小比较

uint8_t 与无符号字符

CORBA IDL 到 C++ - 字符与无符号字符?