如何在 C 中对有符号数进行字节交换? [英] How do I byte-swap a signed number in C?

查看:41
本文介绍了如何在 C 中对有符号数进行字节交换?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道从无符号类型转换为相同等级的有符号类型会产生一个实现定义的值:

<块引用>

C99 6.3.1.3:

<块引用>

  1. 否则,新类型是有符号的,值不能在其中表示;结果是实现定义的,或者引发了实现定义的信号.

这意味着我不知道如何对有符号数进行字节交换.例如,假设我从外围设备接收以小端顺序的两字节补码有符号值,并在大端 CPU 上处理它们.C 库中的字节交换原语(如 ntohs)被定义为处理无符号值.如果我将数据转换为无符号数据以便进行字节交换,那么之后如何可靠地恢复有符号值?

解决方案

我知道从无符号类型转换为相同等级的有符号类型会产生一个实现定义的值.

它只是实现定义的,因为 C 中的签名格式是实现定义的.例如,二进制补码就是这样一种实现定义的格式.

所以这里唯一的问题是传输的任何一方是否不是二进制补码,这在现实世界中不太可能发生.我不会费心设计可移植到黑暗时代的晦涩、绝迹计算机的程序.

<块引用>

这意味着我不知道如何对有符号数进行字节交换.例如,假设我从外围设备接收以小端顺序的两字节补码有符号值,并在大端 CPU 上处理它们

我怀疑这里的一个混淆源是您认为通用二进制补码将从大端或小端的发送方传输并由大/小端接收.数据传输协议不是那样工作的:它们明确指定字节序和签名格式.所以双方都要适应协议.

一旦指定,这里就真的没有火箭科学了:您将收到 2 个原始字节.将它们存储在原始数据数组中.然后将它们分配给您的二进制补码变量.假设协议指定了小端:

int16_t val;uint8_t 小[2];val = (little[1]<<8) |小[0];

位移位的优点是与字节序无关.所以无论你的 CPU 是大还是小,上面的代码都可以工作.因此,尽管此代码包含大量丑陋的隐式促销,但它是 100% 可移植的.C 保证将上述内容视为:

val = (int16_t)( ((int)((int)little[1]<<8)) | (int)little[0] );

移位运算符的结果类型是其提升的左操作数的结果类型.结果类型 |是平衡类型(通常的算术转换).

移位有符号负数会产生未定义的行为,但我们可以避免移位,因为单个字节是无符号的.当它们被隐式提升时,这些数字仍被视为正数.

并且由于 int 保证至少为 16 位,因此代码将适用于所有 CPU.

或者,您可以使用完全排除所有隐式促销/转化的迂腐风格:

val = (int16_t) ( ((uint32_t)little[1] <<8) | (uint32_t)little[0] );

但这是以可读性为代价的.

I understand that casting from an unsigned type to a signed type of equal rank produces an implementation-defined value:

C99 6.3.1.3:

  1. Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.

This means I don't know how to byte-swap a signed number. For instance, suppose I am receiving two-byte, twos-complement signed values in little-endian order from a peripheral device, and processing them on a big-endian CPU. The byte-swapping primitives in the C library (like ntohs) are defined to work on unsigned values. If I convert my data to unsigned so I can byte-swap it, how do I reliably recover a signed value afterward?

解决方案

I understand that casting from an unsigned type to a signed type of equal rank produces an implementation-defined value.

It will be implementation-defined only because the signedness format in C is implementation-defined. For example, two's complement is one such implementation-defined format.

So the only issue here is if either side of the transmission would not be two's complement, which is not likely going to happen in the real world. I would not bother to design programs to be portable to obscure, extinct one's complement computers from the dark ages.

This means I don't know how to byte-swap a signed number. For instance, suppose I am receiving two-byte, twos-complement signed values in little-endian order from a peripheral device, and processing them on a big-endian CPU

I suspect a source of confusion here is that you think a generic two's complement number will be transmitted from a sender that is either big or little endian and received by one which is either big/little. Data transmission protocols don't work like that though: they explicitly specify endianess and signedness format. So both sides have to adapt to the protocol.

And once that's specified, there's really no rocket science here: you are receiving 2 raw bytes. Store them in an array of raw data. Then assign them to your two's complement variable. Suppose the protocol specified little endian:

int16_t val;
uint8_t little[2];

val = (little[1]<<8) | little[0];

Bit shifting has the advantage of being endian-independent. So the above code will work no matter if your CPU is big or little. So although this code contains plenty of ugly implicit promotions, it is 100% portable. C is guaranteed to treat the above as this:

val = (int16_t)( ((int)((int)little[1]<<8)) | (int)little[0] );

The result type of the shift operator is that of its promoted left operand. The result type of | is the balanced type (usual arthmetic conversions).

Shifting signed negative numbers would give undefined behavior, but we get away with the shift because the individual bytes are unsigned. When they get implicitly promoted, the numbers are still treated as positive.

And since int is guaranteed to be at least 16 bits, the code will work on all CPUs.

Alternatively, you could use pedantic style that completely excludes all implicit promotions/conversions:

val = (int16_t) ( ((uint32_t)little[1] << 8) | (uint32_t)little[0] );

But this comes at the cost of readability.

这篇关于如何在 C 中对有符号数进行字节交换?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆