20

I had code in my app that looks like the following. I got some feedback around a bug, when to my horror, I put a debugger on it and found that the MAX between -5 and 0 is -5!

NSString *test = @"short";
int calFailed = MAX(test.length - 10, 0);                      // returns -5

After looking at the MAX macro, I see that it requires both parameters to be of the same type. In my case, "test.length" is an unsigned int and 0 is a signed int. So a simple cast (for either parameter) fixes the problem.

NSString *test = @"short";
int calExpected = MAX((int)test.length - 10, 0);                    // returns 0

This seems like a nasty and unexpected side effect of this macro. Is there another built-in method to iOS for performing MIN/MAX where the compiler would have warned about mismatching types? Seems like this SHOULD have been a compile time issue and not something that required a debugger to figure out. I can always write my own, but wanted to see if anybody else had similar issues.

4

2 回答 2

30

正如 FDinoff 的回答所建议的那样,启用-Wsign-compare是一个好主意,但我认为可能值得更详细地解释其背后的原因,因为这是一个很常见的陷阱。

问题并不在于MAX宏,而是a)以导致溢出的方式从无符号整数中减去,以及b)(如警告所示)编译器如何处理有符号和无符号的比较值一般。

第一个问题很容易解释:当您从无符号整数中减去结果为负数时,结果“溢出”为一个非常大的正值,因为无符号整数不能表示负值。所以[@"short" length] - 10将评估为4294967291

可能更令人惊讶的是,即使没有减法,类似的东西MAX([@"short" length], -10)也不会产生正确的结果(它会评估为-10,即使[@"short" length]5,这显然更大)。这与宏无关,类似if ([@"short" length] > -10) { ... }会导致同样的问题(if 块中的代码不会执行)。

所以一般的问题是:当您将无符号整数与有符号整数进行比较时会发生什么(为什么首先会出现警告)?编译器将根据可能导致令人惊讶的结果的某些规则将这两个值转换为通用类型。

引用理解整数转换规则 [cert.org]

  • 如果带符号整数类型的操作数的类型可以表示无符号整数类型的操作数类型的所有值,则将无符号整数类型的操作数转换为有符号整数类型的操作数的类型。
  • 否则,两个操作数都转换为与带符号整数类型的操作数类型对应的无符号整数类型。

(强调我的)

考虑这个例子:

int s = -1;
unsigned int u = 1;
NSLog(@"%i", s < u);
// -> 0

结果将是0(false),即使s( -1) 明显小于u( 1)。发生这种情况是因为这两个值都转换为unsigned int,因为int不能表示可以包含在 中的所有值unsigned int

s如果您将类型更改为to ,它会变得更加混乱long。然后,您会在 32 位平台 (iOS) 上得到相同(不正确)的结果,但在 64 位 Mac 应用程序中它可以正常工作!(说明:long那里是 64 位类型,所以它可以表示所有 32 位unsigned int值。)

所以,长话短说:不要比较无符号和有符号整数,特别是如果有符号值可能为负数。

于 2013-04-29T04:59:34.210 回答
12

您可能没有打开足够的编译器警告。如果您打开-Wsign-compare(可以用 开启-Wextra),您将生成如下所示的警告

warning: signed and unsigned type in conditional expression [-Wsign-compare]

这允许您在必要时将铸件放置在正确的位置,并且您不需要重写 MAX 或 MIN 宏

于 2013-04-29T01:17:07.933 回答