为什么这个if条件失败用于比较负整数和正整数

man*_*m-n 7 c c++ comparison signed if-statement

#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");
}
Run Code Online (Sandbox Code Playgroud)

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

Pau*_*l R 17

问题出在你的比较中:

    if ((-1) < SIZE)
Run Code Online (Sandbox Code Playgroud)

sizeof通常返回一个unsigned long,所以SIZE将是unsigned long,而-1只是一个int.C和相关语言的推广规则意味着-1将size_t在比较之前转换为,因此-1将成为非常大的正值(a的最大值unsigned long).

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

    if (-1 < (long long)SIZE)
Run Code Online (Sandbox Code Playgroud)

虽然它实际上是一个毫无意义的比较,因为根据定义,无符号值总是> = 0,编译器可能会对此提出警告.

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

  • @DieterLücking为什么你不喜欢**更好的建议**而不是`if((-1)<(int)sizeof(x))`**?** (2认同)
  • @PaulR其实我应该删除我的投票:"虽然它实际上是一个毫无意义的比较,因为根据定义,无符号值总是> = 0" (2认同)

Tem*_*Rex 10

TL; DR

注意混合签名/未签名操作(使用-Wall编译器警告).标准有很长的篇幅.特别是,通常但不总是将signed的值转换为unsigned(尽管在您的特定示例中也是如此).请参阅下面的解释(摘自此问答)

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");
}
Run Code Online (Sandbox Code Playgroud)

在Coliru在线编译器,这个打印为4和8 sizeof()-1SIZE分别,并且选择"更多"分支(活例子).

原因是无符号类型的等级大于有符号类型.因此,第1节适用,并且签名类型被值转换为无符号类型(在大多数实现中,通常通过保留位表示,因此包裹到非常大的无符号数),然后比较继续选择"更多"分支.

主题的变化

将条件重写为if ((long long)(-1) < (unsigned)SIZE)"less"分支(实例).

原因是签名类型的排名高于无符号类型,并且还可以容纳所有无符号值.因此,第2节适用,无符号类型转换为有符号类型,然后比较继续选择"less"分支.

当然,你永远不会if()用显式强制转换来编写这样一个人为的声明,但是如果你将变量与类型long long和变量进行比较,就会发生同样的效果unsigned.因此,它说明了混合有符号/无符号算术是非常微妙的,并且取决于相对大小(标准单词中的"排名").特别是,没有固定的规则说签名将始终转换为无符号.

  • +1的全面答案,无疑将被后代所赏识...... (3认同)

Nob*_*lis 7

当你做的比较signedunsigned其中unsigned至少有一个同等级别到了的signed类型(见TemplateRex对确切的答案规则),将signed被转换成的类型unsigned.

关于你的情况,在32位机器上的二进制表示-1unsigned4294967295.所以实际上你正在比较4294967295是否小于8(它不是).

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

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

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

Gosling:对于我作为一名语言设计师而言,我现在并不像以前那样真实地认为,"简单"真正意义上的结果是我可以期待J. Random Developer在他的脑海中保留这个规范.这个定义说,例如,Java不是 - 实际上很多这些语言都有很多极端情况,这些都是没人真正理解的.测试任何C开发人员关于unsigned的问题,很快你就会发现几乎没有C开发人员真正理解无符号算法是什么,无符号算术是什么.这样的事情让C变得复杂.我认为Java的语言部分非常简单.你必须查找的库.

  • 在没有给出解释评论的情况下,只是downvote的白痴数量相当高. (2认同)

650*_*502 6

这是C的历史设计错误,也在C++中重复.

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

如果unsigned含义是"非负整数"(大小在逻辑上不能为负),那么这就不会是错误,但这是语言转换规则的问题.

给定语言的转换规则,unsignedC中的类型不代表非负数,但它更像是一个位掩码(数学术语实际上是" 的成员?/n ").要了解为什么要考虑C和C++语言

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

如果你读unsigned作"非负数" ,它们都显然毫无意义.

当然,说对象的大小是一个?/n环的成员根本没有任何意义,这里它是错误所在的位置.

实际影响:

每次处理对象的大小时都要小心,因为值unsigned和C/C++中的类型有很多对于数字不合逻辑的属性.请永远记住,unsigned这并不意味着"非负整数",而是" ?/n代数环的成员",并且最危险的是,在混合操作的情况下,a int被转换为unsigned int而不是相反.

例如:

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

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

语言的规则将转换1(整数),以1{mod n}将在执行减法?/n产生(size-1){mod n},将其转换i也为{mod n}表示和将做在比较?/n.

C/C++实际上定义了一个<操作符?/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]);
    }
}
Run Code Online (Sandbox Code Playgroud)

但我通常更喜欢

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

换句话说unsigned,尽快摆脱,并只是定期使用.

永远不要unsigned用来表示容器或柜台的大小,因为unsigned"成员?/n"的意思和容器的大小不是其中之一.无符号类型很有用,但不能表示对象的大小.

不幸的是,标准的C/C++库做出了错误的选择,现在修复它已经太晚了.但是,你并没有被迫犯同样的错误.

用Bjarne Stroustrup的话说:

使用无符号而不是int来再获得一位来表示正整数几乎不是一个好主意.通过声明无符号变量来确保某些值为正的尝试通常会被隐式转换规则所取代