C在short和int之间施放的规则是什么?

当使用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中进行这种类型的转换是否安全,或者我应该自己使用位操作来避免意外结果?

无论何时整数类型被转换为不同的整数类型,它都属于标准中的分类,称为整数提升,并且所有这些都被定义(其中一个是实现,但我们将到达最后一个;扰流器在上面的一般评论中提到)。

关于价值鉴定的一般概述:

C99 6.3.1.1-p2

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

也就是说,让我们看看你的转换。 signed-short to unsigned int由以下内容涵盖,因为转换的超出unsigned int域:

C99 6.3.1.3-p2

否则,如果新类型是无符号的,则通过重复地添加或减去一个可以在新类型中表示的最大值来转换该值,直到该值在新类型的范围内。

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

 -1 + 4294967295 + 1 = 4294967295 

关于您的unsigned short to signed 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) ,但您不能依赖于其他实现。

如问题中所述,假设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 - 10xFFFFFFFF

 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为这种情况添加了一条规则,允许引发实现定义的信号。我不知道有任何编译器这样做。)

这里发生的是参数的右侧首先从16位扩展到32位,而左侧类型的转换仅在赋值时发生。 这意味着如果右侧是有符号的,那么当它被转换为32位时它将被符号扩展,同样如果它是无符号的,那么它将只是零填充。

如果你对你的演员阵容小心,那么应该没有任何问题 – 但除非你做一些超级性能密集的事情,否则额外的几个按位操作不应该伤害任何东西。

另一方面,如果您正在为不同的整数类型假设某些位宽,那么您应该明确地使用stdint.h中定义的类型。 我最近从* nix移植(其他人的)代码到Windows时得到了一点,因为Visual C ++编译器使用不同的整数大小(LLP64)约定,而不是我使用过的任何其他x64或power-7编译器(LP64)。 简而言之,如果你想要32位,你最好用类似uint32_t的类型明确地说它。


那么当C中发生这种转换时,这总是会成立吗? 由C标准定义? – 君

是的,它应该永远持有。 来自C99标准的相关引号(带链接): “整数促销保留包括符号的值。” 处理通常的算术类型转换时: “…对两个操作数执行整数提升。然后将以下规则应用于提升的操作数……”

这是数字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。 因此,它的hex表示将是0xFFFFFFFF。 它然后被转换为无符号,因此是数字4294967295的表示:

 unsigned int s16tou32 = b; 

从unsigned short到unsigned int的提升值为65535.然后是signed int,它也是数字65535的表示:

 signed int u16tos32 = a; 

简单推广签名的short to signed int,所以s16tos32也是数字-1的表示:

 signed int s16tos32 = b;