7

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

4

4 回答 4

7

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

价值限定的一般概述:

C99 6.3.1.1-p2

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

也就是说,让我们看看您的转化率。signed-shorttounsigned 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),但您不能将其依赖于不同的实现。

于 2013-10-09T04:26:35.977 回答
4

如问题所述,假设 16-bitshort和 32-bit int

unsigned short a = 0xFFFF;

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

signed short b = 0xFFFF;

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

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

unsigned int u16tou32 = a;

的值为 ,a0xFFFF转换unsigned shortunsigned int。转换保留了价值。

unsigned int s16tou32 = b;

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

signed int u16tos32 = a;

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

于 2013-10-09T04:50:05.400 回答
4

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

如果你小心你的演员,那么应该没有任何问题——但除非你正在做一些超级性能密集的事情,否则额外的几个按位运算应该不会有任何伤害。

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


那么当这种转换发生在 C 中时,这将始终成立吗?由C标准定义?– 君

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

于 2013-10-09T04:12:01.350 回答
0

这是数字 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;
于 2013-10-09T04:31:58.033 回答