Objective C中的MAX / MIN功能可避免出现铸造问题

时间:2013-04-29 01:10:15

标签: ios objective-c

我的应用中的代码如下所示。我得到了一些关于bug的反馈,当我恐惧时,我在其上放了一个调试器,发现-5到0之间的MAX是-5!

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

在查看MAX宏之后,我发现它需要两个参数属于同一类型。在我的例子中,“test.length”是unsigned int,0是signed int。因此,一个简单的强制转换(用于任一参数)可以解决问题。

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

这似乎是这个宏的一个令人讨厌和意想不到的副作用。是否有另一种内置的iOS方法来执行MIN / MAX,编译器会警告不匹配的类型?看起来像这样应该是一个编译时问题而不是需要调试器弄清楚的东西。我总是可以写自己的,但想知道是否有其他人有类似的问题。

2 个答案:

答案 0 :(得分:30)

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

特别是问题不是MAX宏,而是a)以导致溢出的方式从无符号整数中减去,b)(如警告所示)与编译器通常会处理有符号和无符号值的比较。

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

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

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

引自Understand integer conversion rules [cert.org]

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

(强调我的)

考虑这个例子:

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

结果将是0(false),即使s-1)明显低于u1)。发生这种情况是因为两个值都转换为unsigned int,因为int无法代表unsigned int中可包含的所有值。

如果将s的类型更改为long,则会更加混乱。然后,你会在32位平台(iOS)上获得相同(不正确)的结果,但在64位Mac应用程序中,它可以正常工作! (解释:long是64位类型,因此它可以表示所有32位unsigned int值。)

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

答案 1 :(得分:12)

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

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

这允许您在必要时将演员阵容放置在正确的位置,您不需要重写MAX或MIN宏