将 int 转换为 short 并截断后,如何确定新值?

When an int is cast to a short and truncated, how is the new value determined?

有人可以阐明在 C 中将整数转换为 short 时会发生什么吗?我正在使用 Raspberry Pi,所以我知道 int 是 32 位,因此 short 必须是 16 位。

假设我使用以下 C 代码为例:

int x = 0x1248642;
short sx = (short)x;
int y = sx;

我知道 x 会被截断,但有人能解释一下具体是怎么截断的吗?是否使用轮班?一个数字如何从 32 位截断为 16 位?

简单的把整数的高16位截掉。因此你的空头将变成 0x8642 这实际上是负数 -31166.

32 位的值被截断为 16 位,就像将 32 厘米长的香蕉面包塞进 16 厘米长的平底锅中一样。一半可以装进去,仍然是香蕉面包,其余的将是 "gone".

sx 值将与 x 的 2 个最低有效字节相同,在这种情况下它将是 0x8642,它(如果解释为 16 位带符号整数)给出十进制的 -31166。

根据 ISO C 标准,当您将整数转换为有符号类型时,如果值超出目标类型的范围,则结果为 implementation-defined。 (或者可以引发 implementation-defined 信号,但我不知道有任何编译器会这样做。)

在实践中,最常见的行为是 high-order 位被丢弃。所以假设 int 是 32 位而 short 是 16 位,转换值 0x1248642 可能会产生看起来像 0x8642 的位模式。并假设有符号类型(几乎所有系统都使用)的 two's-complement 表示,high-order 位是符号位,因此结果的数值将为 -31166 .

int y   =   sx;

这也涉及隐式转换,从shortint。由于保证 int 的范围至少覆盖 short 的整个范围,因此该值不变。 (因为,在你的例子中,sx 的值恰好是负数,这种表示的变化很可能涉及 符号扩展 ,传播 1 符号位结果的所有 16 high-order 位。)

正如我所指出的,none 这些细节是语言标准所要求的。如果你真的想将值截断为更窄的类型,最好使用无符号类型(具有 language-specified 环绕行为)和可能的显式屏蔽操作,如下所示:

unsigned int x = 0x1248642;
unsigned short sx = x & 0xFFFF;

如果您有一个 32 位数量,您想要将其放入一个 16 位变量中,那么您应该做的第一件事就是决定如果该值不合适,您希望代码如何运行。一旦你决定了,你就可以弄清楚如何编写你想要的 C 代码。有时截断恰好是您想要的,在这种情况下您的任务将很容易,特别是如果您使用无符号类型。有时 out-of-range 值是一个错误,在这种情况下您需要检查它并决定如何处理错误。有时您可能希望值​​饱和而不是截断,因此您需要编写代码来做到这一点。

了解 C 中的转换如何工作很重要,但如果您从这个问题开始,您可能从错误的方向来解决问题。

截断发生在 CPU 寄存器中。它们有不同的大小:8/16/32/64 位。现在,您可以想象这样一个寄存器:

<--rax----------------------------------------------------------------> (64-bit)
                                    <--eax----------------------------> (32-bit)
                                                      <--ax-----------> (16-bit)
                                                      <--ah--> <--al--> (8-bit high & low)
01100011 01100001 01110010 01110010 01111001 00100000 01101111 01101110

x首先被赋予32位值0x1248642。在记忆中*,它看起来像:

-----------------------------
|  01  |  24  |  86  |  42  |
-----------------------------
 31..24 23..16 15..8  7..0       

现在,编译器将 x 加载到寄存器中。从中,它可以简单地加载最低有效的 16 位(即 ax)并将它们存储到 sx.


*为简单起见,不考虑字节顺序

或许让代码自己说话:

#include <stdio.h>

#define BYTETOBINARYPATTERN "%d%d%d%d%d%d%d%d"
#define BYTETOBINARY(byte)  \
   ((byte) & 0x80 ? 1 : 0), \
   ((byte) & 0x40 ? 1 : 0), \
   ((byte) & 0x20 ? 1 : 0), \
   ((byte) & 0x10 ? 1 : 0), \
   ((byte) & 0x08 ? 1 : 0), \
   ((byte) & 0x04 ? 1 : 0), \
   ((byte) & 0x02 ? 1 : 0), \
   ((byte) & 0x01 ? 1 : 0) 

int main()
{
    int x    =   0x1248642;
    short sx = (short) x;
    int y    =   sx;

    printf("%d\n", x);
    printf("%hu\n", sx);
    printf("%d\n", y);

    printf("x: "BYTETOBINARYPATTERN" "BYTETOBINARYPATTERN" "BYTETOBINARYPATTERN" "BYTETOBINARYPATTERN"\n",
        BYTETOBINARY(x>>24), BYTETOBINARY(x>>16), BYTETOBINARY(x>>8), BYTETOBINARY(x));

    printf("sx: "BYTETOBINARYPATTERN" "BYTETOBINARYPATTERN"\n",
        BYTETOBINARY(y>>8), BYTETOBINARY(y));

    printf("y: "BYTETOBINARYPATTERN" "BYTETOBINARYPATTERN" "BYTETOBINARYPATTERN" "BYTETOBINARYPATTERN"\n",
        BYTETOBINARY(y>>24), BYTETOBINARY(y>>16), BYTETOBINARY(y>>8), BYTETOBINARY(y));

    return 0;
}

输出:

19170882
34370
-31166

x: 00000001 00100100 10000110 01000010
sx: 10000110 01000010
y: 11111111 11111111 10000110 01000010

如您所见,int -> short 产生低 16 位,正如预期的那样。

short 转换为 int 会产生设置了 16 个高位的 short。但是,我怀疑这是特定于实现和未定义的行为。您实质上是将 16 位内存解释为一个整数,它会读取 16 位额外的任何垃圾(如果编译器很好并且希望帮助您更快地找到错误,则为 1)。

认为执行以下操作应该是安全的:

int y = 0x0000FFFF & sx;

显然你不会找回丢失的位,但这将保证高位被正确归零。

如果有人可以通过权威参考来验证 short -> int 高位行为,将不胜感激。

注:二进制宏改编自this answer