为什么C语言不能比较无符号整数和负数值?

4
考虑以下这段C代码:
#include "stdio.h"

int main(void) {

    int count = 5;
    unsigned int i;

    for (i = count; i > -1; i--) {
        printf("%d\n", i);
    }
    return 0;
}

我的观察/问题:循环从未执行过。但是,如果我将i的数据类型从unsigned int更改为int,则一切都按预期工作。
我一直认为无符号整数是当你尝试从它们中不断减去时会“环绕”的值。因此,当i为零且我减去1时,它将环绕到UINT_MAX。而且由于其值永远不为负,这实际上就是一个无限循环。(当我将比较从i > -1更改为i >= 0时,这正是发生的情况。)
如果i是无符号的,并且我将其与-1进行比较,则我的逻辑存在错误,因为循环从未执行过。可能是编译器以某种方式对其进行了优化,或者运行时的值与我所期望的有所不同。
为什么循环不运行?

你尝试过对字面值-1进行显式转换吗? - 0xC0000022L
你是完全正确的。但请记住,5 > UINT_MAX 是错误的。 - sidyll
请返回翻译文本: https://dev59.com/hXI95IYBdhLWcg3w8iv1 https://dev59.com/WoHba4cB1Zd3GeqPMA1k https://dev59.com/8HA75IYBdhLWcg3wy8Xr%E2%88%921-sizeofint - phuclv
4个回答

20
i > -1 中,-1 被转换为 unsigned int,导致其值变为 UINT_MAX。因此,i 永远不会大于该值,所以循环体永远不会执行。
您可能会发现,可以说服编译器警告您存在条件上总是为真或总是为假的表达式。但如果您写了 i > -2,则这仍然不能帮助您,因此您可能还会发现您可以启用所有混合符号比较的警告。
请注意,在 C 语言中,算术运算始终使用相同类型的操作数。这包括比较,但不包括移位运算符(我IRC 不确定)。如果操作数的类型不同,如本例中,则至少将其中一个操作数转换为相同类型。计算目标类型的规则在 6.3.1.1/2 和 6.3.1.8/1 中给出。

新手问题:您在章节编号中所指的规格是什么? - Jaanus
1
@Jaanus:C99(确切地说是n1256)。在C89中,它是3.2.1.1和3.2.1.5。 - Steve Jessop

3
当您在“类型对称”的二进制操作(例如您的示例中的+*>)中混合相同宽度的有符号和无符号操作数时,无符号类型“获胜”,并且该操作在无符号域中计算。也就是说,有符号操作数被转换为无符号类型。
在您的示例中,整数常量具有signed int类型,而i具有unsigned int类型。操作数具有相同的宽度,因此在您的示例中,i > -1被解释为i > (unsigned) -1,这等价于i > UINT_MAX。这就是为什么您的循环永远不会执行的原因。

通常情况下,无符号类型并不总是占优势。例如,尝试使用“unsigned char”和“int”。 - vitaut
@vitaut:嗯,是的,你说得对,但实际上这不是一个很好的例子。当你在表达式中使用unsigned char时,通常的算术转换首先应用,unsigned char很早就变成了signed int。它发生在我在答案中谈到的“二进制”规则有机会发挥作用之前。所以,比喻地说,你根本不能在表达式中使用unsigned char - 它不会粘着。在其他任何事情有机会发生之前,它将立即转换为int - AnT stands with Russia
为了更好地举例说明,假设(通常是这样的)long long 可以表示 unsigned int 的所有值,那么 unsigned int 就不如 signed long long。因此,在大多数实现中,(unsigned int)5 > -1LL 为真,但如果 intlong long 具有相同的宽度,则为假。这是不要在 unsigned intlong 之间进行混合比较的“真正好的”理由:在 LP64 机器上会得到有符号比较,在 LLP64 和(可能)32 位上会得到无符号比较。 - Steve Jessop
@Steve Jessop:说得好。我更新了我的答案,使其涉及相同宽度的类型。 - AnT stands with Russia
@SteveJessop:我想知道为什么C标准的实现者没有规定,在情况下,'正常'规则会导致负数比正数大时,结果应该是实现定义的呢?任何依赖于C行为的代码都不应该被认为是过时的吗? - supercat

1
无论您处理无符号还是有符号数字,-1始终会编译为0xffffffff。处理器具有有符号和无符号比较标志。将该数字与5进行比较时,有符号标志将将其视为-1并表示它小于5,但无符号标志将将其视为大数并表示它大于5。由于该数字也与UINT_MAX相同,因此对于所有无符号数字,您的比较都将为false。

“总是编译为0xffffffff”(4,294,967,295)- 这并不正确,标准并没有保证UINT_MAX的值。事实上,它只声明:“它们的实现定义值应等于或大于65535(2^16-1)的数量级(绝对值)”。 - Wiz
这是唯一一个正确指出-1没有改变的答案。在无符号比较中使用时,它只被视为无符号值。 - stark

0

在无符号比较中,-1会变成UINT_MAX。由于没有比这更大的数字,循环条件永远不为真,因此循环永远不会进入。

如果你将它改为i >= 0,那应该可以按预期工作。实际上,在这种情况下,你可能根本不应该使用无符号类型 :-)


1
如果你把它改成 i >= 0,那么它总是为真,因此毫无意义。 - R.. GitHub STOP HELPING ICE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接