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

时间:2011-10-10 09:57:46

标签: c integer

请查看我的测试代码:

#include <stdlib.h>
#include <stdio.h>


#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标准:

  

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

     

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

     

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

     

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

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

3 个答案:

答案 0 :(得分:20)

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

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

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

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

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

U
  1. 首先推广两个操作数。由于两者都是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 int,因此不会进行任何促销活动。
  2. 接下来,将两个操作数转换为相同的类型。由于unsigned int无法代表int的所有可能值,而unsigned无法代表unsigned的所有可能值,因此没有明显的选择。在这种情况下,两者都转换为int
  3. 当从signed转换为unsigned时,2 32 重复添加到有符号值,直到它在无符号值的范围内。就处理器而言,这实际上是一个noop。
  4. 因此比较变为unsigned,这是假的。
  5. 现在让我们试试if (4294967295u < 2u)

    short
    1. 首先,推广两个操作数。由于short c = -1; // "signed" is implied unsigned short d = 2; if (c < d) puts("c < d"); // printed else puts("c >= d"); // not printed 可以忠实地表示两者,因此两者都会被提升为int
    2. 接下来,它们将转换为相同的类型。但它们已经是同一类型int,所以什么都没做。
    3. 因此比较变为int,这是真的。
    4. 编写好的代码:在代码中捕获这些“陷阱”有一种简单的方法。只需编译并打开警告,并修复警告。我倾向于编写这样的代码:

      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)signedintshort隐含了long,但long long并未暗示char }。相反,它取决于平台。

答案 1 :(得分:1)

取自C ++标准:

  

4.5整数提升[conv.prom]
1类型为char,signed char,unsigned char,short int或unsigned short int的rvalue可以是   如果int可以表示的所有值,则转换为int类型的右值   来源类型;否则,源rvalue可以转换为   unsigned int的类型的右值。

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

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

答案 2 :(得分:0)

C ++的转换过程被描述为usual arithmetic conversions。但是,我认为最相关的规则是在子参考部分conv.prom: Integral promotions 4.6.1

  

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

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

我还发现了this 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.

还需要考虑definition of "rank"。规则列表很长,但适用于这个问题&#34; rank&#34;很简单:

  

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