【问题标题】:What is the rule for C to cast between short and int?C 在 short 和 int 之间转换的规则是什么?
【发布时间】:2013-10-16 06:29:08
【问题描述】:

在使用 C 在 short 和 int 之间进行转换时,我感到很困惑。我假设 short 是 16 位,而 int 是 32 位。我用下面的代码进行了测试:

unsigned short a = 0xFFFF;
signed short b = 0xFFFF;

unsigned int u16tou32 = a;
unsigned int s16tou32 = b;
signed int u16tos32 = a;
signed int s16tos32 = b;

printf("%u %u %d %d\n", u16tou32, s16tou32, u16tou32, s16tou32);

我得到的是:

  • u16tou32: 65535
  • s16tou32: 4294967295
  • u16tos32: 65535
  • s16tos32:-1

我感到困惑的是 s16 到 u32 以及 u16 到 s32 之间的转换。似乎 s16 到 u32 正在做“符号扩展”,而 u16 到 s32 则没有。这背后的规律究竟是什么?这也依赖于实现吗?在 C 中进行这种类型的转换是否安全,或者我应该自己使用位操作以避免意外结果?

【问题讨论】:

  • 您最好也打印ab,因为您可能会对它们是什么感到惊讶。
  • 您的 printf 尝试每次打印前 2 个变量两次,并且从不使用最后 2 个。这是故意的吗?

标签: c


【解决方案1】:

这里发生的情况是,参数的右侧首先从 16 位扩展到 32 位,并且仅在赋值时转换为左侧类型。这意味着如果右侧有符号,则在转换为 32 位时将进行符号扩展,同样,如果它是无符号的,则将仅补零。

如果你对你的转换很小心,那么应该不会有任何问题 - 但除非你正在做一些超级性能密集型的事情,否则额外的几个按位运算应该不会造成任何伤害。

另一方面,如果您在假设不同整数类型的特定位宽的情况下做任何事情,那么您真的应该明确并使用stdint.h 中定义的类型。我最近在将(其他人的)代码从 *nix 移植到 Windows 时对此有所了解,因为 Visual C++ 编译器使用的整数大小约定(LLP64)与我使用过的任何其他 x64 或 power-7 编译器不同(LP64)。简而言之,如果你想要 32 位,你最好用uint32_t 之类的类型明确地说出来。


所以当这种转换发生在 C 中时,这将始终成立吗?由C标准定义? – 六月

是的,它应该始终保持不变。 C99 标准中的相关引用(带链接):"The integer promotions preserve value including sign." 处理通常的算术类型转换时:"... the integer promotions are performed on both operands. Then the following rules are applied to the promoted operands..."

【讨论】:

  • 那么当这种转换发生在 C 中时,这将始终成立吗?由 C 标准定义?
  • @Jun - WhozCraig 的回答比我的要完整得多,而且他显然比我更了解标准。我会听从他的专业知识。
【解决方案2】:

每当整数类型被转换为不同的整数类型时,它都会通过标准规定的确定性弹球机规则,有时是实现。

价值限定的概述:

C99 6.3.1.1-p2

如果 int 可以表示原始类型的所有值(受宽度限制,对于位域),则该值将转换为 int;否则,它会被转换为 unsigned int。这些被称为整数促销。整数提升不会改变所有其他类型。

也就是说,让我们看看您的转化情况。 signed-shortunsigned int 包含在以下内容中,因为正在转换的 不在 unsigned int 域之外:

C99 6.3.1.3-p2

否则,如果新类型是无符号的,则在新类型可以表示的最大值的基础上反复加减一,直到该值在新类型的范围内。

这基本上意味着“添加 UINT_MAX+1”。在你的机器上,UINT_MAX 是 4294967295,因此,这变成了

-1 + 4294967295 + 1 = 4294967295

关于您的 unsigned shortsigned int 的转换,这包含在常规的价值限定促销中。具体来说:

C99 6.3.1.3-p1

当整数类型的值转换为_Bool以外的其他整数类型时,如果该值可以用新的类型表示,则不变。

也就是说,因为你的unsigned short的值在signed int的覆盖范围内,所以没有什么特别的事情,只是简单地保存了值。

最后,正如上面一般评论中提到的,你的b声明发生了一些特别的事情

signed short b = 0xFFFF;

本例中的 0xFFFF 是有符号整数。十进制值为 65535。但是,该值无法signed short 表示,因此发生了另一次转换,您可能不知道:

C99 6.3.1.3-p3

否则,新类型是有符号的,值不能在其中表示;结果要么是实现定义的,要么引发实现定义的信号。

换句话说,您的实现选择将其存储为(-1),但您不能将其依赖于不同的实现。

【讨论】:

  • 您的开头段落不正确。 integer Promotions 是指小于int 的整数类型值,在某些上下文中使用时会隐式转换为int。 int-to-short 等情况不属于整数促销
【解决方案3】:

这是数字 65535 的无符号短表示:

unsigned short a = 0xFFFF;

这是数字 -1 的有符号短表示:

signed short b = 0xFFFF;

从 unsigned short 到 unsigned int 的简单提升,因此 u16tou32 是数字 65535 的 unsigned int 表示:

unsigned int u16tou32 = a;

b(值为 -1)被提升为 int。因此其十六进制表示为 0xFFFFFFFF。然后将其转换为无符号数,因此是数字 4294967295 的表示形式:

unsigned int s16tou32 = b;

从 unsigned short 提升到 unsigned int 的值为 65535。然后是有符号 int 的大小写,这也将是数字 65535 的表示:

signed int u16tos32 = a;

signed short 到signed int 的简单提升,所以s16tos32 也是数字-1 的表示:

signed int s16tos32 = b;

【讨论】:

    【解决方案4】:

    如问题所述,假设 16 位 short 和 32 位 int

    unsigned short a = 0xFFFF;
    

    这会将a 初始化为0xFFFF65535。表达式0xFFFF 的类型为int;它被隐式转换为unsigned short,并保留该值。

    signed short b = 0xFFFF;
    

    这有点复杂。同样,0xFFFF 的类型为 int。它被隐式转换为signed short——但由于该值超出了signed short 的范围,因此转换无法保留该值。

    整数到有符号整数类型的转换,当值无法表示时,会产生一个实现定义的值。原则上,b 的值可以是介于-32768+32767 之间的任何值。在实践中,几乎可以肯定是-1。我将假设该值为-1

    unsigned int u16tou32 = a;
    

    a的值为0xFFFF,由unsigned short转换为unsigned int。转换保留了价值。

    unsigned int s16tou32 = b;
    

    b 的值为-1。它被转换为unsigned int,显然不能存储-1 的值。将整数转换为无符号整数类型(与转换为有符号类型不同)由语言定义;结果以MAX + 1 为模减少,其中MAX 是无符号类型的最大值。在这种情况下,s16tou32 中存储的值是UINT_MAX - 1,或0xFFFFFFFF

    signed int u16tos32 = a;
    

    a0xFFFF 的值转换为signed int。该值被保留。

    signed int s16tos32 = b;
    

    b-1 的值转换为signed int。该值被保留。

    所以存储的值是:

    a == 0xFFFF (65535)
    b == -1     (not guaranteed, but very likely)
    u16tou32 == 0xFFFF (65535)
    s16tou32 == 0xFFFFFFFF (4294967295)
    u16tos32 == 0xFFFF (65535)
    s16tos32 == -1
    

    总结整数转换规则:

    如果目标类型可以表示该值,则保留该值。

    否则,如果目标类型是无符号的,则值以MAX+1为模减少,这相当于丢弃除低位N位之外的所有位。描述这一点的另一种方式是,值MAX+1 被重复地添加到值中或从值中减去,直到你得到一个范围内的结果(这实际上是 C 标准描述它的方式)。编译器实际上并不生成代码来执行这种重复的加法或减法。他们只需要得到正确的结果。

    否则,目标类型是有符号的,不能表示值;转换产生一个实现定义的值。在几乎所有的实现中,结果使用二进制补码表示丢弃除低位 N 位之外的所有位。 (C99 为这种情况添加了一条规则,允许引发实现定义的信号。我不知道有任何编译器会这样做。)

    【讨论】:

    • 我知道我迟到了,但只是为了澄清一下:按位“符号扩展”始终基于源类型,无论目标类型是否已签名?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-08-13
    • 1970-01-01
    • 1970-01-01
    • 2012-08-30
    • 2015-04-03
    相关资源
    最近更新 更多