我有一个小问题需要解决问题的根源: 我有一个 C 文件:
#include <stdio.h>
int main()
{
signed int a = -5;
signed int b = 10;
unsigned int c = 0;
if (a > (b+c)) // -5 > (10 + 0) ?
{
printf("This shouldn't print.n");
}
return 0;
}
给我作为输出:
$ gcc test_uint.c && ./a.out
This shouldn't print.
我的简单问题是:为什么???比较有符号和无符号整数在 C 中是未定义的行为吗?或者说,选角顺序是如何定义的?
当给定运算符不同类型的表达式时,必须使用通用类型进行计算。对于数字类型,这是由通常的算术转换决定的。
这些转换在 C 标准的第 6.3.1.8p1 节中指定,相关部分引用如下:
否则,如果无符号整数类型的操作数有秩 大于或等于另一个操作数的类型的等级,则 有符号整数类型的操作数被转换为 无符号整数类型的操作数。
在您的代码中,对于
+
运算符和 >
运算符都会发生这种情况,其中一个操作数是 int
,另一个是 unsigned int
,因此 int
操作数会转换为 unsigned int
。这到底是如何发生的由第 6.3.1.3p2 节规定:
1 当整数类型的值转换为另一种整数类型时 除_Bool外,如果该值可以用新类型表示,则 没有变化。
2 否则,如果新类型是无符号的,则该值将转换为 反复加或减 1 比最大值 可以用新类型表示,直到该值在以下范围内 新类型。
对于
b+c
,b
的值将转换为unsigned int
。由于值 10 可以用两种类型表示,因此该值与上面的第 1 条一样保持不变。那么 b+c
的结果具有类型 unsigned int
和值 10。
然后评估
a > (b+c)
。与之前一样,a
必须转换为 unsigned int
。由于值 -5 超出了 unsigned int
的范围,因此根据上面的第 2 条进行转换。因此,假设 unsigned int
的最大值为 232-1,则值 -5 会转换为值 232-5。该值大于 10,因此条件成立。
C 2018 标准在第 6.5.6 条中指定了
+
运算符。第 4 段说“如果两个操作数都有算术类型,则对它们执行通常的算术转换。”
常用算术转换在 6.3.1.8 中指定。它们是按考虑顺序排列的列表。前三个涉及浮点类型。
然后是:
如果两个操作数具有相同的类型,则不需要进一步转换。
这不适用,因为
int
和 unsigned int
不是同一类型。
否则,如果两个操作数都具有有符号整数类型或都具有无符号整数类型,则具有较小整数转换等级的类型的操作数将转换为具有较大等级的操作数的类型。
这不适用,因为
int
和 unsigned int
并非均已签名或均未签名。
否则,如果无符号整数类型的操作数的等级大于或等于另一个操作数类型的等级,则有符号整数类型的操作数将转换为无符号整数类型的操作数的类型。
这确实适用,因为
unsigned int
的排名等于 int
。 (等级在 C 标准的其他地方定义,它是整数类型的排序:_Bool
最低,char
更大,short
更大,依此类推。)
因此,当添加
int
和 unsigned int
时,两者都会转换为 unsigned int
。
根据隐式整数提升的规则,
b
将在此处提升为unsigned
:(b+c)
,因此结果为10u
。
同样遵循相同的规则,
a
在此提升为 unsigned
:a > 10u
。 -5
不能用 unsigned
表示,因此行为是未定义的替代。定义的实现。许多实现使负值环绕成为一个非常大的 unsigned
值(例如,-1
变成 UINT_MAX
)。正是这个非常大的 unsigned
值与相对较小的 10u
进行比较,这就是条件为真的原因。