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

rai*_*r33 20 objective-c ios

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

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

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

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

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

omz*_*omz 30

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

问题实际上不是MAX特别针对宏,而是a)以导致溢出的方式从无符号整数中减去,b)(如警告所示)编译器如何处理有符号和无符号的比较一般的价值观.

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

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

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

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

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

(强调我的)

考虑这个例子:

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

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

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

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


FDi*_*off 12

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

warning: signed and unsigned type in conditional expression [-Wsign-compare]
Run Code Online (Sandbox Code Playgroud)

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

  • 是的,这就是诀窍.当然,现在我的项目有107个新问题.我想知道有多少像这样的bug.谢谢你的提示. (3认同)