Q“无符号保持”和“值保持”规则之间有什么区别?
A这些规则涉及将无符号类型提升为“更大”类型时的行为。它应该被提升为更大的有符号还是无符号类型?(预示答案,这可能取决于较大的类型是否真正更大。)
在无符号保持(也称为“符号保持”)规则下,提升后的类型始终是无符号的。此规则的优点是简单,但可能导致意外(请参阅下面的第一个示例)。
在值保持规则下,转换取决于原始类型和提升类型的实际大小。如果提升后的类型确实更大——这意味着它可以将原始无符号类型的所有值表示为有符号值——那么提升后的类型是有符号的。如果两种类型的大小实际上相同,那么提升后的类型是无符号的(与无符号保持规则相同)。
由于在进行判断时使用了类型的*实际*大小,因此结果会因机器而异。在某些机器上,short int小于int,但在某些机器上,它们的大小相同。在某些机器上,int小于long int,但在某些机器上,它们的大小相同。
实际上,在二进制运算符的一个操作数是(或提升为)int而另一个操作数根据提升规则可能为int或unsigned int时,无符号和值保持规则之间的差异最常出现。如果一个操作数是unsigned int,另一个操作数将被转换为该类型——如果其值为负数,几乎肯定会导致不期望的结果(再次,请参阅下面的第一个示例)。当 ANSI C 标准建立时,选择了值保持规则,以减少发生这些意外结果的案例数量。(另一方面,值保持规则也减少了*可预测*案例的数量,因为可移植程序不能依赖机器的类型大小,因此无法知道值保持规则将如何进行。)
这是一个人为设计的示例,展示了在无符号保持规则下可能发生的意外情况
unsigned short us = 10; int i = -5; if(i > us) printf("whoops!\n");重要的考虑因素是表达式i > us是如何评估的。在无符号保持规则下(以及在short整数和普通整数大小相同的机器上的值保持规则下),us被提升为unsigned int。通常的整数转换规则规定,当二进制运算符的类型相遇时,两个操作数都将转换为无符号,因此unsigned int和inti也被转换为。原始值unsigned int,-5,将被转换为某个大的无符号值(在 16 位机器上为 65,531)。该转换后的值大于 10,因此代码打印“whoops!”也被转换为在值保持规则下,在普通整数比
整数大的机器上,short被转换为普通us(并保留其值 10),而int保持为普通也被转换为。表达式不成立,代码不打印任何内容。(要了解为什么只有当有符号类型更大时才能保留值,请记住,像 40,000 这样的值可以表示为无符号 16 位整数,但不能表示为有符号整数。)int不幸的是,值保持规则并不能防止所有意外。在 short 和普通整数大小相同的机器上,上面刚介绍的示例仍然会打印“whoops”。值保持规则也可能带来一些自己的意外——请考虑代码
在左移之前,
unsigned char uc = 0x80; unsigned long ul = 0; ul |= uc << 8; printf("0x%lx\n", ul);uc被提升。在无符号保持规则下,它被提升为,代码继续打印unsigned int0x8000,正如预期的那样。然而,在值保持规则下,被提升为*有符号* int(只要被提升。在无符号保持规则下,它被提升为's 比intchar大,通常是这样)。中间结果uc << 8继续与ul相遇,而是unsigned long。因此,有符号的中间结果也必须被提升,并且如果int小于long,中间结果会被符号扩展,在拥有 32 位的机器上变成0xffff8000long。在该机器上,代码打印的机器上变成,这可能不是预期的。(在int和long大小相同的机器上,代码打印,正如预期的那样。然而,在值保持规则下,,无论哪种规则。)
为了避免意外(无论是在任一规则下,还是由于规则意外更改),最好避免在同一表达式中混合有符号和无符号类型,尽管正如第二个示例所示,此规则并不总是足够。您始终可以使用显式类型转换来明确指示您希望在哪里以及如何执行转换;请参阅问题 12.42 和 16.7 中的示例。(一些编译器会尝试在检测到歧义情况或可能在无符号保持规则下行为不同的表达式时发出警告,尽管有时这些警告触发得太频繁;另请参阅问题 3.18。)
参考文献:K&R2 第 2.7 节第 44 页,第 A6.5 节第 198 页,附录 C 第 260 页
ISO 第 6.2.1.1 节,第 6.2.1.2 节,第 6.2.1.5 节
Rationale 第 3.2.1.1 节
H&S 第 6.3.3、6.3.4 节,第 174-177 页