8
#include <stdio.h>

int arr[] = {1,2,3,4,5,6,7,8};
#define SIZE (sizeof(arr)/sizeof(int))

int main()
{
        printf("SIZE = %d\n", SIZE);
        if ((-1) < SIZE)
                printf("less");
        else
                printf("more");
}

编译后的输出gcc"more". 为什么if即使条件失败-1 < 8

4

6 回答 6

18

问题在于您的比较:

    if ((-1) < SIZE)

sizeof通常返回一个unsigned long,所以SIZE将是unsigned long,而-1只是一个int。C 及相关语言中的提升规则意味着 -1size_t在比较之前会被转换为,所以-1会变成一个非常大的正值(an 的最大值unsigned long)。

解决此问题的一种方法是将比较更改为:

    if (-1 < (long long)SIZE)

尽管这实际上是一个毫无意义的比较,因为根据定义,无符号值将始终 >= 0,编译器可能会就此发出警告。

正如@Nobilis 随后指出的那样,您应该始终启用编译器警告并注意它们:如果您使用例如gcc -Wall ...编译器进行编译,编译器会警告您您的错误。

于 2013-08-15T07:15:29.323 回答
10

TL;博士

小心混合有符号/无符号操作(使用-Wall编译器警告)。该标准有一个很长的部分。特别是,通常但并非总是如此,有符号的值转换为无符号的(尽管在您的特定示例中确实如此)。请参阅下面的说明(取自此问答

C++ 标准的相关引用:

5 表达式 [expr]

10 许多期望算术或枚举类型的操作数的二元运算符会导致转换并以类似的方式产生结果类型。目的是产生一个通用类型,这也是结果的类型。这种模式称为通常的算术转换,定义如下:

[省略等号类型或等号类型的 2 条子句]

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

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

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

你的实际例子

要查看您的程序属于 3 种情况中的哪一种,请将其稍微修改为

#include <stdio.h>

int arr[] = {1,2,3,4,5,6,7,8};
#define SIZE (sizeof(arr)/sizeof(int))

int main()
{
        printf("SIZE = %zu, sizeof(-1) = %zu,  sizeof(SIZE) = %zu \n", SIZE, sizeof(-1), sizeof(SIZE));
        if ((-1) < SIZE)
                printf("less");
        else
                printf("more");
}

在 Coliru 在线编译器上,这分别为sizeof()of-1和打印 4 和 8 SIZE,并选择“更多”分支(现场示例)。

原因是无符号类型的等级高于有符号类型。因此,第 1 条适用并且有符号类型被值转换为无符号类型(在大多数实现中,通常通过保留位表示,因此环绕到一个非常大的无符号数),然后比较继续选择“更多”的分支。

主题变奏曲

将条件重写为if ((long long)(-1) < (unsigned)SIZE)将采用“less”分支(现场示例)。

原因是有符号类型的等级高于无符号类型,并且还可以容纳所有无符号值。因此,第 2 条适用,无符号类型转换为有符号类型,然后比较继续选择“更少”分支。

当然,您永远不会if()使用显式强制转换来编写这种人为的语句,但是如果您将变量与类型long longunsigned. 因此,它说明了混合有符号/无符号算术非常微妙并且取决于相对大小(标准中的“排名”)。特别是,没有固定的规则说 signed 将始终转换为 unsigned

于 2013-08-15T09:07:45.007 回答
7

当您在 where 之间进行比较时signedunsignedwhereunsigned至少与signed类型的等级相同(有关确切规则,请参见 TemplateRex 的答案), 将signed转换为unsigned.

关于您的情况,在 32 位机器上, as 的二进制表示-1unsigned4294967295。因此,实际上您是在比较 4294967295 是否小于 8(不是)。

如果您启用了警告,编译器会警告您正在发生一些可疑的事情:

warning: comparison between signed and unsigned integer expressions [-Wsign-compare]

由于讨论已经改变了使用的适当unsigned程度,让我引用 James Gosling 关于unsignedJava 中缺乏类型的引用(我将无耻地链接到我关于该主题的另一篇文章):

Gosling:作为一名语言设计师,我现在并不认为自己是这样的,“简单”最终的真正含义是我能否期望 J. Random Developer 将规范牢记在心。该定义表明,例如,Java 不是——事实上,这些语言中的许多最终都会出现很多极端情况,即没有人真正理解的事情。向任何 C 开发人员询问有关无符号的问题,很快你就会发现几乎没有 C 开发人员真正了解无符号的情况,无符号算术是什么。这样的事情使 C 变得复杂。我认为 Java 的语言部分非常简单。您必须查找的库。

于 2013-08-15T07:16:26.790 回答
7

这是 C 的一个历史设计错误,在 C++ 中也重复出现。

它可以追溯到 16 位计算机,错误是决定使用所有 16 位来表示高达 65536 的大小,从而放弃了表示负大小的可能性。

unsigned如果含义是“非负整数”(大小在逻辑上不能为负),这在 se 中不会是错误,但这是语言转换规则的问题。

鉴于语言的转换规则,unsignedC 中的类型并不表示非负数,而是更像是位掩码(数学术语实际上是“的成员ℤ/n”)。看看为什么要考虑 C 和 C++ 语言

  • unsigned - unsigned给出一个unsigned结果
  • signed + unsigned给出和unsigned结果

unsigned如果您阅读为“非负数” ,它们显然都毫无意义。

当然,说对象的大小是ℤ/nring 的成员根本没有任何意义,这就是错误所在。

实际影响:

每次处理对象的大小时都要小心,因为值是unsigned,并且 C/C++ 中的类型有很多对数字不合逻辑的属性。请始终记住,unsigned这不是指“非负整数”而是“ℤ/n代数环的成员”,而且最危险的是,在混合运算的情况下,anint被转换为unsigned int而不是相反。

例如:

void drawPolyline(const std::vector<P2d>& pts) {
    for (int i=0; i<pts.size()-1; i++) {
        drawLine(pts[i], pts[i+1]);
    }
}

是错误的,因为如果传递一个空的点向量,它将执行非法(UB)操作。原因是它pts.size()是一个unsigned.

该语言的规则会将1(整数)转换为1{mod n},将执行减法以ℤ/n产生(size-1){mod n},还将转换i{mod n}表示形式并在 中进行比较ℤ/n

C/C++ 实际上定义了一个<运算符 in ℤ/n(很少在数学中完成),即使输入向量为空,您最终也会访问pts[0], pts[1]... 等等直到巨大的数字。

一个正确的循环可能是

void drawPolyline(const std::vector<P2d>& pts) {
    for (int i=1; i<pts.size(); i++) {
        drawLine(pts[i-1], pts[i]);
    }
}

但我通常更喜欢

void drawPolyline(const std::vector<P2d>& pts) {
    for (int i=0,n=pts.size(); i<n-1; i++) {
        drawLine(pts[i], pts[i+1]);
    }
}

unsigned换句话说,尽快摆脱,只使用常规整数。

永远不要unsigned用来表示容器或计数器的大小,因为unsigned意味着“成员ℤ/n”,而容器的大小不是其中之一。无符号类型很有用,但不能表示对象的大小。

不幸的是,标准 C/C++ 库做出了这个错误的选择,现在修复它为时已晚。但是,您不会被迫犯同样的错误。

用 Bjarne Stroustrup 的话来说

使用 unsigned 而不是 int 来获得更多位来表示正整数几乎不是一个好主意。通过声明变量 unsigned 来确保某些值是正数的尝试通常会被隐式转换规则打败

于 2013-08-15T08:02:37.147 回答
2

好吧,我不会重复 Paul R 所说的强词,但是当你比较无符号和整数时,你会遇到非常糟糕的事情。

if ((-1) < (int)SIZE)

而不是你的 if 条件

于 2013-08-15T07:18:34.113 回答
0

将 sizeof 运算符返回的无符号类型转换为有符号

当您比较两个无符号和有符号数时,编译器会隐式将有符号转换为无符号。
4 字节 int 中的 -1 有符号表示为 11111111 11111111 11111111 11111111 当转换为无符号时,此表示将指 2^16-1
所以基本上你是在比较 2^16-1>SIZE,这是真的。
您必须通过将无符号值显式转换为有符号来覆盖它。由于 sizeof 运算符返回 unsigned long long 您应该将其转换为 signed long long

if((-1)<(signed long long)SIZE)

在你的代码中使用这个 if 条件

于 2013-08-15T07:21:29.740 回答