警告-有符号和无符号整数表达式之间的比较


77

我目前正在通过Accelerated C ++工作在练习2-3中遇到了一个问题。

程序概述-程序基本上会命名,然后在星号的框架内显示问候语-即Hello!用*包围。

练习-在示例程序中,作者使用const int确定问候语和星号之间的填充(空白)。然后,作为练习的一部分,他们要求读者向用户输入有关其填充尺寸的信息。

这一切似乎很容易,我继续向用户询问两个整数(int),并存储它们,并更改程序以使用这些整数,并在编译时删除了作者使用的整数,尽管我得到了以下警告;

练习2-3.cpp:46:警告:有符号和无符号整数表达式之间的比较

经过一些研究,似乎是因为代码试图将上述整数(int)与a进行比较string::size_type,这很好。但是我想知道-这是否意味着我应该将整数之一更改为 unsigned int?显式声明我的整数是带符号的还是无符号的是否重要?

 cout << "Please enter the size of the frame between top and bottom you would like ";
 int padtopbottom;
 cin >> padtopbottom;

 cout << "Please enter size of the frame from each side you would like: ";
 unsigned int padsides; 
 cin >> padsides;

 string::size_type c = 0; // definition of c in the program
 if (r == padtopbottom + 1 && c == padsides + 1) { // where the error occurs

上面是相关的代码位,为cis类型,string::size_type因为我们不知道问候语可能持续多长时间-但是当作者的代码在使用时没有遇到问题时,为什么现在会出现此问题const int?另外-对于可能已经完成Accelerated C ++的任何人-本书稍后会对此进行解释吗?

我在通过Geany使用g ++的Linux Mint上,如果有帮助或有所作为(据我了解,确定是什么string::size_type的话)。


2
会不会有人假设您要取消对ints的签名?我想不出顶部和底部应该为负的逻辑原因
Woot4Moo 2010年

这是真的,我在上面的文章中提到了这一点,但是我仍然不明白为什么当他们使用const int时在作者的示例程序中没有出现这个问题?我敢肯定,我会在书中谈到这一点,但不禁会感到好奇。
Tim Harrington

废话-显然,在这种情况下它没有发出警告,因为int始终为1 ... oops。
蒂姆·哈灵顿

1
通常,范围的增加不值得使用unsigned整数类型进行计数的麻烦。无符号数字还保证了环绕行为,从而使其效率略低。
乔恩·普迪

4
作者可能已经看到相同的警告,而忽略了它。不要以为书的作者比普通的程序员有更多的知识或知识。
克里斯托弗·约翰逊

Answers:


101

通常最好将变量声明为unsignedsize_t是否将其与大小进行比较,以避免出现此问题。只要有可能,请使用要与之进行比较的确切类型(例如,std::string::size_typestd::string的长度进行比较时使用)。

编译器会发出有关比较有符号和无符号类型的警告,因为有符号和无符号int的范围是不同的,并且将它们相互比较时,结果可能令人惊讶。如果必须进行这样的比较,则应该在检查以确保转换有效之后,将其中一个值显式转换为与另一个兼容的类型。例如:

unsigned u = GetSomeUnsignedValue();
int i = GetSomeSignedValue();

if (i >= 0)
{
    // i is nonnegative, so it is safe to cast to unsigned value
    if ((unsigned)i >= u)
        iIsGreaterThanOrEqualToU();
    else
        iIsLessThanU();
}
else
{
    iIsNegative();
}

11
我知道,当前的C标准有时要求负有符号的值要比无符号的值要大,但是发生这种情况的任何情况都不应被视为不赞成使用吗?我希望标准能够发展到至少允许编译器产生算术正确的行为(这意味着如果带符号的值为负,则比较小,如果无符号的值超过带符号的最大值,则比较大)。在没有显式类型转换的情况下,要求编译器产生愚蠢的行为似乎很奇怪。
2012年

4
@supercat:由于整数比较只能编译为一条机器指令,并且任何测试或边缘情况处理都需要多条机器指令,因此您建议的内容不太可能被添加为C功能……它肯定不是默认行为,因为即使程序员知道没有必要,它也会不必要地降低性能。
Blake Miller

@BlakeMiller:想要比较有符号和无符号的值(好像两者都是无符号的)的代码可以强制转换并以“全速”运行。否则,在许多情况下,比较和跳转需要两条指令和三个指令之间的区别将比手动处理各种情况的代码便宜。
2013年

1
@BlakeMiller :(我之所以说二对三,是因为大多数比较两个数字的代码将使用一条指令执行比较并根据它们设置标志;在许多情况下,编译器可以安排事情,以便在比较之前, “ sign”标志将保留其中一个操作数的高位,因此在比较之前进行一次条件跳转就足以确保正确的语义。请注意,由于有多种方法可以实现正确的语义,因此编译器可以选择最便宜的一种方法。为正确的语义编写C代码会更困难。
超级猫

6
只是为了证明“结果可能令人惊讶”,下面的程序(在#include <cstdio>顶部插入之后,而我正在使用g ++ 4.4.7)将显示“ true”,并指出(带符号)是真的-1大于(无符号)12: int main(int, char**) { int x = -1; unsigned int y = 12; printf("x > y: %s\n", x > y ? "true":"false"); return 0; }
villapx

9

昨天我在加速C ++中解决问题2-3时遇到了完全相同的问题。关键是将要比较的所有变量(使用布尔运算符)更改为兼容类型。在这种情况下,这意味着string::size_type(或unsigned int,但由于此示例使用的是前者,因此即使两者在技术上兼容,我也坚持这样做)。

请注意,正如您正确指出的那样,他们在原始代码中为c计数器(本书第2.5节的第30页)完全做到了这一点。

是什么让这个例子更复杂的是,不同的填充变量(padsides和padtopbottom),以及所有计数器,必须更改为string::size_type

以您的示例为例,您发布的代码最终看起来像这样:

cout << "Please enter the size of the frame between top and bottom";
string::size_type padtopbottom;
cin >> padtopbottom;

cout << "Please enter size of the frame from each side you would like: ";
string::size_type padsides; 
cin >> padsides;

string::size_type c = 0; // definition of c in the program

if (r == padtopbottom + 1 && c == padsides + 1) { // where the error no longer occurs

请注意,在前面的条件中,如果未string::size_typefor循环中将变量r初始化为a,则将收到错误。因此,您需要使用以下方法初始化for循环:

    for (string::size_type r=0; r!=rows; ++r)   //If r and rows are string::size_type, no error!

因此,基本上,一旦string::size_type在混合中引入了变量,任何时候只要要对该项目执行布尔运算,所有操作数都必须具有兼容的类型,才能在没有警告的情况下进行编译。


6

有符号和无符号整数之间的重要区别是最后一位的解释。有符号类型的最后一位代表数字的符号,表示:例如:

0001是1个有符号和无符号的1001是-1个有符号和9个无符号的

(为了说明清楚起见,我避免了整个补码问题!这不是int在内存中的表示方式!)

您可以想象知道与-1或+9比较会有所不同。在许多情况下,程序员都懒得声明将int计数为无符号的(膨胀for循环头fi),这通常不是问题,因为使用int时,您必须计数到2 ^ 31,直到符号被您咬住为止。这就是为什么这只是一个警告。因为我们太懒了,所以写“ unsigned”而不是“ int”。


啊,我明白了-我现在将计数int更改为unsigned。这被认为是好习惯,甚至是坏习惯吗?:)
蒂姆·哈灵顿

如果您不赞成,请简短说明原因。即使只有一个字。我的回答没有任何错。您可能会帮助我的问题。
AndreasT 2010年

1
@Tim:“ unsigned”是“ unsigned int”的同义词。您应该使用unsigned int或stl标准计数/迭代变量类型std :: size_t(也是同义词)。最佳做法是在“迭代元素0到n”的所有情况下使用无符号。它提高了清晰度并消除了警告,因此它是赢家;-)
AndreasT 2010年

8
有符号整数的内部表示形式取决于编译器(即计算机)。由于某些问题,带符号位的符号未得到广泛使用(+/-零是其中之一)。大多数机器使用二进制补码概念来表示负数。优点是无需更改也可以使用常规(无符号)算法。2的补码概念中的-1为1111 btw。
sstn 2011年

1
@AndreasT:“为了清楚起见,避免整个补码问题”是可以理解的,但您可以使用一个与2的补码兼容的示例,几乎所有平台都使用该表示法。1001因为-1是一个糟糕的选择,所以更好的选择是1111等于-1有符号和15无符号”
MestreLion

4

在极限范围内,无符号整数可以大于整数。
因此,编译器将生成警告。如果确定这不是问题,请随时将类型强制转换为相同类型,以便警告消失(使用C ++强制转换以便易于发现)。

或者,使变量具有相同的类型,以防止编译器抱怨。
我的意思是,是否可能会有负填充?如果是这样,则将其保留为整数。否则,您可能应该使用unsigned int并让流捕获用户输入负数的情况。


0

或使用此标头库并编写:

// |notEqaul|less|lessEqual|greater|greaterEqual
if(sweet::equal(valueA,valueB))

并且不在乎签名/未签名或其他大小


0

主要问题是基础硬件(CPU)仅具有比较两个有符号值或比较两个无符号值的指令。如果您向无符号比较指令传递一个带符号的负值,它将把它视为一个大的正数。因此,-1(所有位都打开(二进制补码))的位模式对于相同数量的位变为最大无符号值。

8位:-1有符号与255个无符号16位相同:-1有符号与65535个无符号等。

因此,如果您具有以下代码:

int fd;
fd = open( .... );

int cnt;
SomeType buf;

cnt = read( fd, &buf, sizeof(buf) );

if( cnt < sizeof(buf) ) {
    perror("read error");
}

您会发现,如果由于文件描述符无效(或某些其他错误)而导致read(2)调用失败,则该cnt将被设置为-1。与无符号值sizeof(buf)进行比较时,if()语句将为false,因为0xffffffff不小于sizeof()一些数据结构(合理,未构想为最大大小)。

因此,如果要删除已签名/未签名的警告,您必须编写以上内容,例如:

if( cnt < 0 || (size_t)cnt < sizeof(buf) ) {
    perror("read error");
}

这只是大声说出了问题。

1.  Introduction of size_t and other datatypes was crafted to mostly work, 
    not engineered, with language changes, to be explicitly robust and 
    fool proof.
2.  Overall, C/C++ data types should just be signed, as Java correctly
    implemented.

如果值太大,以至于找不到有效的带符号值类型,则说明您选择的语言使用的处理器太小或值的大小太大。如果像金钱一样,每个数字都很重要,那么在大多数语言中都有可以使用的系统可以为您提供无限的精度。C / C ++不能很好地做到这一点,您必须非常明确地了解类型中的所有内容,如本文其他许多答案中所述。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.