【问题标题】:When an int is cast to a short and truncated, how is the new value determined?当一个 int 被强制转换为 short 并被截断时,新值是如何确定的?
【发布时间】:2016-04-25 11:13:54
【问题描述】:

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

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

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

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

【问题讨论】:

  • 请注意,演员表(像大多数演员表一样)是不必要的。可以声明short sx = x;x的值会隐式转换为short
  • “int”和“short”的实际大小会因平台而异。但是是的,假设“int”是 32 位,“short”是 16 位:1)是的,强制转换会将值从 32 位截断到 16 位,2)是的,高 16 位是“丢失”的,3)不,没有“转变”。 PS:你知道你的树莓派可能有一个完整的Mathematica 副本吗?绝对值得一试:)
  • 不完全重复,但密切相关:stackoverflow.com/q/19273658/4996248
  • 顺便说一句:您可以使用#include <stdint.h> 消除位宽猜测以引入int32_tint16_t 等。

标签: c casting truncate


【解决方案1】:

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

在实践中,最常见的行为是丢弃高位。所以假设int 是32 位,short 是16 位,转换值0x1248642 可能会产生一个看起来像0x8642 的位模式。并且假设有符号类型的二进制补码表示(几乎所有系统都使用),高位是符号位,因此结果的数值将为-31166

int y   =   sx;

这还涉及从shortint 的隐式转换。由于int 的范围保证至少覆盖short 的整个范围,因此该值不变。 (因为在您的示例中,sx 的值恰好是负数,这种表示形式的更改很可能涉及 符号扩展,将 1 符号位传播到所有 16 个高阶位结果。)

正如我所指出的,语言标准不需要这些细节。如果您真的想将值截断为更窄的类型,最好使用无符号类型(具有语言指定的环绕行为)以及可能的显式屏蔽操作,如下所示:

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

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

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

【讨论】:

  • 如果您的代码假设 x 适合短,您可以使用 assert( x <= USHRT_MAX ) 来强制执行该假设,而不是屏蔽。
  • 通知x & 0xFFF != (short) x 如果CHAR_BIT != 8
  • @black:或者,更准确地说,如果CHAR_BIT * sizeof (short) != 16。 (我曾在 CHAR_BIT==8 的系统上工作过,其中 sizeof (short) 是 4 甚至 8。
  • 除非您正在为一个非常深奥的平台编程(然后您可能知道),否则您可以放心地假设发生了截断行为。
  • 非常有用的答案,尤其是最后两段。
【解决方案2】:

32 位值被截断为 16 位,就像将 32 厘米长的香蕉面包塞进 16 厘米长的平底锅中一样,它会被切开。一半可以放进去,仍然是香蕉面包,其余的将“消失”。

【讨论】:

  • 不是最好的类比。我可以将一根 32 厘米的香蕉捣碎或切成两块并排的小块,将其放入 16 厘米的平底锅中。单词中的位比平底锅中的香蕉位具有更严格的约束。而且你什么也没说你最终会得到哪一半,或者为什么。
  • @KeithThompson - 你也可以切片和切块 32 位(你需要一把刀来切面包,或者购买一些操作操作),但类比要求将蛋糕推入锅中,不切片。关于进入的部分或消失的三个部分,是的,我没有设法包括那个细节
  • 不得不说这是关于 SO 的最佳答案。
【解决方案3】:

截断发生在 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


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

【讨论】:

  • 我相信 OP 想知道丢弃是如何发生的。原 32 中的哪 16 位被保留了?
  • @Schwern 谢谢,添加了更多解释——这说明清楚了吗?
  • 是的。它总是最低有效的 16 位吗?
  • @black 我提交了一个编辑以修复结果中的错字,但没有足够的字符,所以我也改进了(IMO)寄存器插图。如果您不同意我的解释,请随时进一步改进。
  • @Dan 谢谢丹,我采纳了你提出的一些建议。现在应该看起来好多了。
【解决方案4】:

只是从整数中去掉高 16 位。因此,您的空头将变为 0x8642,这实际上是负数 -31166

【讨论】:

  • 虽然我相信使用高位还是低位是特定于实现的?
  • @Link 结果完全是根据语言标准实现定义的,但我不相信有任何编译器会给你高位。
  • @Link:不,它不是特定于实现的。任何转换为​​更窄的类型总是会切断最重要的位。如果对于不同宽度的类型的联合会有所不同 - 这里大端/小端会有所不同。但对于上述情况,它到处都是一样的。
  • @ZbynekVyskovsky-kvr000:不,将超出范围的值转换为有符号类型的结果是实现定义的。请参阅N1570 第 6.3.1.3 节。 (丢弃高位当然是最常见的行为。)
  • @KeithThompson:很奇怪,我一直认为这里的编译器对于无符号和有符号类型是一致的。幸运的是,它们都与无符号值一样,否则很多软件会停止工作......
【解决方案5】:

也许让代码自己说话:

#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

【讨论】:

  • 我很想知道为什么也设置了高位,即使那是一个单独的问题
  • 这仅显示用于生成输出的实现的行为。
  • @KeithThompson 感谢 Keith 的洞察力。我做了一些进一步的测试并更新了我的答案。看起来你的答案更有知识和更完整(赞成),但我会留下我的答案,以防有人出于好奇而想自己运行代码。
【解决方案6】:

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

【讨论】:

  • 0x8642 不是十进制的-311660x8642 是十进制的 34370。该值在转换为 16 位有符号类型时,通常产生 -31166,但这是一个不同的值。
  • @KeithThompson:谢谢,我澄清了我的回答。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-07
  • 1970-01-01
  • 2011-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多