为什么整数提升的结果不同?

请看我的测试代码:

#include  #include  #define PRINT_COMPARE_RESULT(a, b) \ if (a > b) { \ printf( #a " > " #b "\n"); \ } \ else if (a < b) { \ printf( #a " < " #b "\n"); \ } \ else { \ printf( #a " = " #b "\n" ); \ } int main() { signed int a = -1; unsigned int b = 2; signed short c = -1; unsigned short d = 2; PRINT_COMPARE_RESULT(a,b); PRINT_COMPARE_RESULT(c,d); return 0; } 

结果如下:

 a > b c < d 

我的平台是Linux,我的gcc版本是4.4.2。 我对输出的第二行感到惊讶。 第一行输出是由整数提升引起的。 但为什么第二行的结果不同?

以下规则来自C99标准:

如果两个操作数具有相同的类型,则不需要进一步转换。 否则,如果两个操作数都具有有符号整数类型或两者都具有无符号整数类型,则具有较小整数转换等级类型的操作数将转换为具有更高等级的操作数的类型。

否则,如果具有无符号整数类型的操作数的秩大于或等于另一个操作数的类型的等级,则具有有符号整数类型的操作数将转换为具有无符号整数类型的操作数的类型。

否则,如果带有符号整数类型的操作数的类型可以表示具有无符号整数类型的操作数类型的所有值,则具有无符号整数类型的操作数将转换为带有符号整数类型的操作数的类型。

否则,两个操作数都转换为无符号整数类型,对应于带有符号整数类型的操作数的类型。

我认为两个比较都应该属于同一个案例,第二个案例是整数提升。

使用算术运算符时,操作数将进行两次转换。

整数提升:如果int可以表示该类型的所有值,则操作数将提升为int。 这适用于大多数平台上的shortunsigned short 。 在此阶段执行的转换分别在每个操作数上完成,而不考虑其他操作数。 (有更多规则,但这是适用的规则。)

通常的算术转换:如果将unsigned intsigned int进行比较,因为既不包括另一个的整个范围,并且两者都具有相同的等级,则两者都转换为unsigned类型。 在检查两个操作数的类型之后完成此转换。

显然,如果没有两个操作数,“通常的算术转换”并不总是适用。 这就是为什么有两套规则。 例如,一个问题是移位运算符<<>>不执行通常的算术转换,因为结果的类型应该只依赖于左操作数(所以如果你看到有人输入x << 5U ,那么U代表“不必要的”)。

细分:让我们假设一个典型的系统具有32位int和16位short。

 int a = -1; // "signed" is implied unsigned b = 2; // "int" is implied if (a < b) puts("a < b"); // not printed else puts("a >= b"); // printed 
  1. 首先,两个操作数被提升。 由于两者都是intunsigned int ,因此不会进行任何促销。
  2. 接下来,将两个操作数转换为相同的类型。 由于int不能表示unsigned所有可能值,而unsigned不能表示int所有可能值,因此没有明显的选择。 在这种情况下,两者都转换为unsigned
  3. 当从signed转换为unsigned时,会将2 32重复添加到有符号值,直到它在无符号值的范围内。 就处理器而言,这实际上是一个noop。
  4. 所以比较变为if (4294967295u < 2u) ,这是假的。

现在让我们试试吧:

 short c = -1; // "signed" is implied unsigned short d = 2; if (c < d) puts("c < d"); // printed else puts("c >= d"); // not printed 
  1. 首先,两个操作数被提升。 由于两者都可以通过int忠实地表示,因此两者都被提升为int
  2. 接下来,它们将转换为相同的类型。 但它们已经是同一类型, int ,所以什么都没做。
  3. 因此比较变为if (-1 < 2) ,这是真的。

编写好的代码:有一种简单的方法可以在代码中捕获这些“陷阱”。 只需编译并打开警告,并修复警告。 我倾向于编写这样的代码:

 int x = ...; unsigned y = ...; if (x < 0 || (unsigned) x < y) ...; 

您必须注意,您编写的任何代码都不会遇到其他已签名和未签名的陷阱:签名溢出。 例如,以下代码:

 int x = ..., y = ...; if (x + 100 < y + 100) ...; unsigned a = ..., b = ...; if (a + 100 < b + 100) ...; 

一些流行的编译器会优化(x + 100 < y + 100)(x < y) ,但这是另一天的故事。 只是不要溢出你签名的号码。

脚注:请注意,虽然对于intshortlonglong long暗示了signed ,但它并不暗示char 。 相反,它取决于平台。

取自C ++标准:

4.5整体促销[conv.prom]
1如果int可以表示源类型的所有值,则可以将char,signed char,unsigned char,short int或unsigned short int类型的rvalue转换为int类型的rvalue。 否则,源rvalue可以转换为unsigned int类型的rvalue。

在实践中,它意味着,如果它可以覆盖您正在处理的整个值集,则所有操作(在列表中的类型上)实际上都在int类型上进行计算,否则它将在unsigned int 。 在第一种情况下,值被比较为unsigned int因为其中一个是unsigned int ,这就是-1为“大于”2的原因。在第二种情况下,值a被比较为有符号整数,因为int覆盖整个域shortunsigned short ,因此-1小于2。

(背景故事:实际上,所有关于以这种方式覆盖所有情况的复杂定义导致编译器实际上可以忽略实际类型(!):)并且只关心数据大小。)

C ++的转换过程被描述为通常的算术转换 。 但是,我认为最相关的规则是在子参考部分conv.prom:积分促销4.6.1 :

除了bool,char16_t,char32_t或wchar_t之外的整数类型的prvalue,其整数转换等级([conv.rank])小于int的等级,如果int可以表示所有值,则可以转换为int类型的prvalue源类型; 否则,源prvalue可以转换为unsigned int类型的prvalue。

有趣的是使用“can”这个词,我认为这个促销是由编译器自行决定的。

我还发现这个 C-spec片段暗示了促销的遗漏:

 11 EXAMPLE 2 In executing the fragment char c1, c2; /* ... */ c1 = c1 + c2; the ``integer promotions'' require that the abstract machine promote the value of each variable to int size and then add the two ints and truncate the sum. Provided the addition of two chars can be done without overflow, or with overflow wrapping silently to produce the correct result, the actual execution need only produce the same result, possibly omitting the promotions. 

还有待考虑的“等级”的定义 。 规则列表很长,但是因为它适用于这个问题,“排名”很简单:

任何无符号整数类型的等级应等于相应的有符号整数类型的等级。