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

发布于 2024-12-09 06:43:06 字数 1093 浏览 0 评论 0原文

请看我的测试代码:

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

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

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

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

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

我认为这两次比较应该属于同一种情况,即整数提升的第二种情况。

Please look at my test code:

#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;
}

The result is the following:

a > b
c < d

My platform is Linux, and my gcc version is 4.4.2.
I am surprised by the second line of output.
The first line of output is caused by integer promotion. But why is the result of the second line different?

The following rules are from C99 standard:

If both operands have the same type, then no further conversion is needed.
Otherwise, if both operands have signed integer types or both have unsigned
integer types, the operand with the type of lesser integer conversion rank is
converted to the type of the operand with greater rank.

Otherwise, if the operand that has unsigned integer type has rank greater or
equal to the rank of the type of the other operand, then the operand with
signed integer type is converted to the type of the operand with unsigned
integer type.

Otherwise, if the type of the operand with signed integer type can represent
all of the values of the type of the operand with unsigned integer type, then
the operand with unsigned integer type is converted to the type of the
operand with signed integer type.

Otherwise, both operands are converted to the unsigned integer type
corresponding to the type of the operand with signed integer type.

I think both of the two comparisons should belong to the same case, the second case of integer promotion.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

柠檬心 2024-12-16 06:43:07

当您使用算术运算符时,操作数会经历两次转换。

整数提升:如果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 所有可能的值>,没有明显的选择。在这种情况下,两者都会转换为无符号
  3. 从有符号转换为无符号时,会重复将 232 与有符号值相加,直至其位于无符号值的范围内。就处理器而言,这实际上是一个空操作。
  4. 因此比较变成了 if (4294967295u < 2u),这是 false。

现在让我们尝试一下 short

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),但这是一个故事改天。只是不要溢出您的签名号码。

脚注:请注意,虽然 signed 隐含于 intshortlonglong long,它不隐含于 char。相反,它取决于平台。

When you use an arithmetic operator, the operands go through two conversions.

Integer promotions: If int can represent all values of the type, then the operand is promoted to int. This applies to both short and unsigned short on most platforms. The conversion performed on this stage is done on each operand individually, without regard for the other operand. (There are more rules, but this is the one that applies.)

Usual arithmetic conversions: If you compare an unsigned int against a signed int, since neither includes the entire range of the other, and both have the same rank, then both are converted to the unsigned type. This conversion is done after examining the type of both operands.

Obviously, the "usual arithmetic conversions" don't always apply, if there are not two operands. This is why there are two sets of rules. One gotcha, for example, is that shift operators << and >> don't do usual arithmetic conversions, since the type of the result should only depend on the left operand (so if you see someone type x << 5U, then the U stands for "unnecessary").

Breakdown: Let's assume a typical system with 32-bit int and 16-bit 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. First the two operands are promoted. Since both are int or unsigned int, no promotions are done.
  2. Next, the two operands are converted to the same type. Since int can't represent all possible values of unsigned, and unsigned can't represent all possible values of int, there is no obvious choice. In this case, both are converted to unsigned.
  3. When converting from signed to unsigned, 232 is repeatedly added to the signed value until it is in the range of the unsigned value. This is actually a noop as far as the processor is concerned.
  4. So the comparison becomes if (4294967295u < 2u), which is false.

Now let's try it with short:

short c = -1;          // "signed" is implied
unsigned short d = 2;
if (c < d)
    puts("c < d");     // printed
else
    puts("c >= d");    // not printed
  1. First, the two operands are promoted. Since both can be represented faithfully by int, both are promoted to int.
  2. Next, they are converted to the same type. But they already are the same type, int, so nothing is done.
  3. So the comparison becomes if (-1 < 2), which is true.

Writing good code: There's an easy way to catch these "gotchas" in your code. Just always compile with warnings turned on, and fix the warnings. I tend to write code like this:

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

You have to watch out that any code you do write doesn't run into the other signed vs. unsigned gotcha: signed overflow. For example, the following code:

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

Some popular compilers will optimize (x + 100 < y + 100) to (x < y), but that is a story for another day. Just don't overflow your signed numbers.

Footnote: Note that while signed is implied for int, short, long, and long long, it is NOT implied for char. Instead, it depends on the platform.

游魂 2024-12-16 06:43:07

摘自 C++ 标准:

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

实际上,这意味着,如果类型 int 可以覆盖您正在处理的整个值集,则所有操作(在列表中的类型上)实际上都是在类型 int 上求值,否则在 int 上执行。代码>无符号整数。
在第一种情况下,这些值将作为 unsigned int 进行比较,因为其中一个是 unsigned int,这就是为什么 -1 “大于”2。在第二种情况下,将 a 值作为有符号整数进行比较,因为 int 涵盖了 shortunsigned Short 的整个域,因此 -1 小于 2。

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

Taken from the C++ standard:

4.5 Integral promotions [conv.prom]
1 An rvalue of type char, signed char, unsigned char, short int, or unsigned short int can be
converted to an rvalue of type int if int can represent all the values of the
source type; otherwise, the source rvalue can be converted to an
rvalue of type unsigned int.

In practice it means, that all operations (on the types in the list) are actually evaluated on the type int if it can cover the whole value set you are dealing with, otherwise it is carried out on unsigned int.
In the first case the values are compared as unsigned int because one of them was unsigned int and this is why -1 is "greater" than 2. In the second case the values a compared as signed integers, as int covers the whole domain of both short and unsigned short and so -1 is smaller than 2.

(Background story: Actually, all this complex definition about covering all the cases in this way is resulting that the compilers can actually ignore the actual type behind (!) :) and just care about the data size.)

ま昔日黯然 2024-12-16 06:43:07

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

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

有趣的是使用了“can”这个词,我认为这表明这种提升是由编译器自行决定执行的。

我还发现 this C 规范片段暗示了促销的省略:

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.

有还要考虑“rank”的定义。规则列表相当长,但当它适用于这个问题时,“排名”很简单:

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

The conversion process for C++ is described as the usual arithmetic conversions. However, I think the most relevant rule is at the sub-referenced section conv.prom: Integral promotions 4.6.1:

A prvalue of an integer type other than bool, char16_t, char32_t, or
wchar_t whose integer conversion rank ([conv.rank]) is less than the
rank of int can be converted to a prvalue of type int if int can
represent all the values of the source type; otherwise, the source
prvalue can be converted to a prvalue of type unsigned int.

The funny thing there is the use of the word "can", which I think suggests that this promotion is performed at the discretion of the compiler.

I also found this C-spec snippet that hints at the omission of promotion:

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.

There is also the definition of "rank" to be considered. The list of rules is pretty long, but as it applies to this question "rank" is straightforward:

The rank of any unsigned integer type shall equal the rank of the
corresponding signed integer type.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文