当将int强制转换为short并截断时,新值是怎么确定的?

44

当一个整数在C语言中被强制转换为short类型时会发生什么?我正在使用树莓派,所以我知道int类型是32位的,因此short类型必须是16位。

举个例子,假设我使用以下C代码:

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

我知道 x 会被截断,但有人能解释一下具体是如何做到的吗?使用移位操作吗?一个32位的数字具体如何被截断成16位?


2
请注意,类型转换(像大多数类型转换一样)是不必要的。您可以声明short sx = x;,并且x的值将被隐式转换为short - Keith Thompson
3
“int”和“short”的实际大小因平台而异。但假设“int”为32位,“short”为16位:1)是的,强制转换将把值从32位截断为16位,2)是的,高16位会“丢失”,3)不,没有“移位”。PS:您知道吗,您的Raspberry Pi可能有一份完整版的[Mathematica](https://www.raspberrypi.org/learning/getting-started-with-mathematica/)?绝对值得一看 :) - paulsm4
不完全是重复,但密切相关:https://dev59.com/UWIk5IYBdhLWcg3wbtyb - John Coleman
2
顺便提一下:您可以通过使用 #include <stdint.h> 来引入 int32_tint16_t 等,从而消除位宽猜测。 - rubicks
6个回答

46
根据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语言中的转换方式是很重要的,但是如果您从这个问题开始,可能会从错误的方向入手解决问题。


1
如果你的代码假设 x 可以适应一个 short 类型,那么你可以使用 assert( x <= USHRT_MAX ) 来强制执行这个假设,而不是进行掩码操作。 - Schwern
1
注意:如果 CHAR_BIT != 8,则 x & 0xFFF != (short) x - edmz
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Keith Thompson
除非你正在为一个非常奇特的平台编程(那么你可能知道),否则你可以安全地假设截断行为会发生。 - fuz
非常有用的答案,特别是最后两段。 - CompuChip

15

32位的值会以类似于将32cm长的香蕉面包塞入16cm长烤盘中切割的方式被截断为16位。其中一半可以适应并仍然是个香蕉面包,而另一半则会“消失”。


6
不是最好的比喻。我可以通过将32厘米的香蕉压碎或把它切成两个相邻的部分来放入16厘米的平底锅中。但是,与香蕉在平底锅中的情况不同,字中的位具有更严格的限制。而你没有说明最终使用哪一半,或者为什么要这样做。 - Keith Thompson
@KeithThompson - 你也可以切割32位(虽然需要面包刀或购买位操作的操作),但类比要求将蛋糕塞入平底锅中,而不是切割。关于进入或离开的部分,是的,我没有成功地包含那个细节。 - Amit
1
我必须说,这是SO上最好的答案。 - Hunter Kohler

9

截断发生在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
@Schwern 谢谢,我添加了更多的解释 -- 这样清楚了吗? - edmz
1
是的。它总是最不重要的16位吗? - Schwern
@black 我提交了一个修改来修正结果中的一个拼写错误,但是字符数不够,所以我也改进了(在我看来)注册插图。如果你对我的解释持有异议,请随意进一步完善它。 - Dan Bechard
@丹 谢谢你,丹。我采纳了你提出的一些建议。现在应该看起来好多了。 - edmz

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;

显然,您无法恢复丢失的位,但这将确保高位被正确清零。

如果有人能够通过权威参考验证短->整数高位行为,那将不胜感激。

注意:二进制宏改编自this answer


我很想知道为什么高位也被设置了,尽管这是一个单独的问题。 - buydadip
6
这只展示了你所使用的实现方式生成输出时的行为。 - Keith Thompson
@KeithThompson 感谢您的见解,Keith。我进行了进一步的测试并更新了我的答案。看起来您的答案更加专业和完整(已点赞),但我会保留我的答案,以防有人出于好奇想要运行代码。 - Dan Bechard

5

简单来说,这个整数的高16位将被截断掉。因此你的短整数将变成0x8642,实际上是负数-31166


虽然我相信使用高位或低位是实现特定的,但这并不影响程序的正确性。 - Rivasa
@Link 结果完全由语言标准定义,但我不认为有任何编译器会给你高位比特。 - Keith Thompson
@Link: 不,这不是实现特定的。任何转换为较窄类型的操作都会截断最高有效位。如果涉及到不同宽度类型的联合体,则大端/小端将产生差异。但对于上述情况,无论在哪里都是相同的。 - Zbynek Vyskovsky - kvr000
4
将超出范围的值转换为有符号类型的结果是由实现定义的。见N1570第6.3.1.3节。(丢弃高位比特通常是最常见的行为。) - Keith Thompson
@KeithThompson:奇怪,我一直认为编译器在无符号和有符号类型上是一致的。幸运的是,它们都与无符号值相同,否则很多软件将停止工作... - Zbynek Vyskovsky - kvr000

3

sx的值将与x的最后两个字节相同,在这种情况下,它将是0x8642,如果解释为16位带符号整数,则十进制为-31166。


0x8642在十进制中不是-311660x8642在十进制中是34370。当将该值转换为16位有符号类型时,通常会得到-31166,但那是一个不同的值。 - Keith Thompson
@KeithThompson:谢谢,我澄清了我的回答。 - nsilent22

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接