我如何字节交换在C符号数?(How do I byte-swap a signed number i

2019-09-17 00:20发布

据我所知,从一个无符号类型的符号类型同等级别的铸造生产实现定义的值:

C99 6.3.1.3:

  1. 否则,新的类型是有符号和值不能在它来表示; 任一结果是实现定义或实现定义的信号上升。

这意味着,我不知道该怎么字节交换有符号数。 举例来说,假设我在little-endian顺序从外围设备接收两个字节,二进制补码符号值和大端CPU上处理它们。 在C库(如字节交换原语ntohs )被定义为在无符号值工作。 如果我将数据转换为无符号的,所以我可以字节交换吧,我怎么可靠地恢复一个符号的值之后?

Answer 1:

据我所知,从一个无符号类型的符号类型同等级别的铸造生产实现定义的值。

这将是实现定义仅仅是因为在C中的符号性格式是实现定义的。 例如,补就是这样一个实现定义的格式。

所以在这里唯一的问题是,如果传输的两侧也不会补,这是不是有可能会在现实世界中发生的。 我不会刻意去设计方案,可移植到混淆从黑暗时代,一个灭绝的补台计算机。

这意味着,我不知道该怎么字节交换有符号数。 举例来说,假设我收到两个字节,补码符号值在little-endian顺序从外围设备和大端CPU上处理它们

我怀疑这里混乱的根源是,你认为一个通用的二进制补码数将从发送者要么是大或小尾数和一个要么是大/小接收传输。 数据传输协议不喜欢的工作,虽然:他们明确指定字节序和符号性的格式。 因此,双方必须适应的协议。

而一旦这样规定的,真的没有火箭科学这里:您收到2个原始字节。 在原始数据的阵列存储。 然后将它们分配给你补变量。 假设协议规定的小尾数:

int16_t val;
uint8_t little[2];

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

比特移位具有作为尾数无关的优点。 所以上面的代码将工作不管你的CPU是大或小。 因此,尽管该代码包含了大量丑陋的隐式的促销,它是100%可移植的。 C被保证治疗上述,因为这:

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

移位运算的结果类型是其促进左操作数。 结果类型| 是平衡型(通常arthmetic转换)。

移签署负数会给不确定的行为,但我们摆脱移位,因为每个字节是无符号。 当他们得到提拔暗示,这些数字仍视为阳性。

而且,由于int是保证至少16位的代码将在所有的CPU工作。

另外,您也可以使用学究风格,完全排除了所有隐促销/转换:

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

但是,这是以可读性为代价。



Answer 2:

正如你在你的问题的结果是实现定义或实现定义的信号成为 -即依赖于平台/编译器会发生什么。



Answer 3:

到字节交换一个符号数,同时避免尽可能多的实现所定义的行为成为可能,则可以利用一个签署中间较宽,能够代表无符号类型的整个范围内具有相同的宽度,你想的有符号值字节交换。 以你的小尾数,16位数字的例子:

// Code below assumes CHAR_BIT == 8, INT_MAX is at least 65536, and
// signed numbers are twos complement.
#include <stdint.h>

int16_t
sl16_to_host(unsigned char b[2])
{
    unsigned int n = ((unsigned int)b[0]) | (((unsigned int)b[1]) << 8);
    int v = n;
    if (n & 0x8000) {
        v -= 0x10000;
    }
    return (int16_t)v;
}

下面是这样做。 首先,将其转换在小端值b到主机端的无符号的值(无论哪个字节序的主机实际上是)。 然后,它存储在一个更广泛的签署变量值。 其值仍处于范围[0,65535],但它现在是一个带符号的量。 因为int可以表示在该范围内的所有值,则转换完全由标准限定。

现在到了关键的一步。 我们测试的无符号的价值,这是符号位的高位,如果这是真的,我们从签订价值65536(0x10000处)减去。 该范围[32768,655535]映射到[-32768,-1],而这恰恰是如何二进制补码符号数进行编码。 这仍然是在更广泛的类型发生,所以我们在保证范围内的所有值都表示的。

最后,我们截断更宽类型int16_t 。 这一步涉及到不可避免的实现定义的行为,但以概率为1,您的实现定义它的行为如你所愿。 在您实现使用签署和幅度或1-补码表示的符号数,值-32768将被截断在一起厮混,并可能导致程序崩溃的察觉万一。 我不会打扰担心。

另一种方法,这可能是byteswapping 32位数字,当你没有获得一个64位的类型是有用的,是掩盖了符号位,分别处理:

int32_t
sl32_to_host(unsigned char b[4])
{
    uint32_t mag = ((((uint32_t)b[0]) & 0xFF) <<  0) |
                   ((((uint32_t)b[1]) & 0xFF) <<  8) |
                   ((((uint32_t)b[2]) & 0xFF) << 16) |
                   ((((uint32_t)b[3]) & 0x7F) << 24);
    int32_t val = mag;
    if (b[3] & 0x80) {
        val = (val - 0x7fffffff) - 1;
    }
    return val;
}

我已经写了(val - 0x7fffffff) - 1在这里,而不是仅仅val - 0x80000000 ,以确保减法发生在一个符号类型。



文章来源: How do I byte-swap a signed number in C?