【问题标题】:C unsigned integer subtraction, macros, and typecastingC 无符号整数减法、宏和类型转换
【发布时间】:2013-02-06 02:51:39
【问题描述】:

为示例代码的 Objective-C 特性道歉,但我很确定我的问题的答案在 C 标准库和/或 Apple clang 编译器中。

我有一个NSArray,其中包含可变数量的项目。我想使用项目计数来创建一个介于 1 和 3 之间的值。我正在使用 C MAX 宏,但它有一些奇怪的行为:

NSLog( @"%d %d %d %d", 1, [tasks count], 3 - [tasks count], MAX( 1, 3 - [tasks count] ) );

当增加tasks 中的项目数时,此日志语句的输出是这样的:

1 0 3 3
1 1 2 2
1 2 1 1
1 3 0 1
1 4 -1 -1

我稍微研究了一下文档,发现count 函数返回了NSUInteger。我的困境的解决方案只是将返回值类型转换为NSInteger

NSLog( @"%d %d %d %d", 1, (NSInteger)[tasks count], 3 - (NSInteger)[tasks count], MAX( 1, 3 - (NSInteger)[tasks count] ) );

1 0 3 3
1 1 2 2
1 2 1 1
1 3 0 1
1 4 -1 1

(如果你不熟悉 Objective-C,在 32 位架构上,NSInteger 的类型定义为 intNSUIntegerunsigned int。)

我很难理解在我的原始代码中隐式发生的类型转换,这导致了我不直观的结果。有人能点亮吗?

【问题讨论】:

    标签: c casting macros


    【解决方案1】:

    打开编译器警告。正如你所说,Objective C 位无关紧要,所以我将从这里开始引用 C。

    假设您的 MAX() 宏是这样定义的(使用 GCC 扩展):

    #define MAX(x, y) ({ typeof (x) _x = (x); \
                         typeof (y) _y = (y); \
                         _x > _y ? _x : _y })
    

    MAX() 宏是非标准的

    当您计算 MAX(1, 3 - [tasks count]) 时,您会得到:

    int x = 1;
    unsigned y = 3 - [tasks count];
    x > y ? x : y;
    

    现在,可以在 C 中正确比较 intunsigned,但这不是您在 C 中使用 x > y 时得到的行为。相反,两个操作数都转换为 unsigned(由于到“通常的算术转换”)。

    所以您的比较是(unsigned) 1 > (unsigned) -1,这是错误的,因为(unsigned) -1 是最大可能的unsigned,在大多数系统(32 位和64 位)上是0xffffffff

    哪里出错了?

    NSLog(@"%d", [tasks count]);
    

    这在技术上是错误的,您将unsigned 传递给NSLog(),但使用了%d 格式说明符,它用于int。请改用%u,或先将您的值转换为int

    编译器警告

    编译器会发出两个警告:

    • 可能告诉您在计算 MAX(1, 3 - [tasks count]) 时正在比较有符号和无符号。

    • 当格​​式需要 int 时,它会告诉您您正在将 unsigned 传递给 NSLog()

    【讨论】:

    • Xcode 的默认编译器配置没有给我这些警告。如果有的话,我就不会浪费两个小时来调试它了。我必须打开“隐式签名转换”,然后给出 150 个我不关心的其他警告。
    • @jab:这也归结为风格问题。如果我负责您的代码,我会保留警告并修复代码,直到所有警告都消失。我的猜测是,在生成警告的代码中的这 150 个位置中的一些或许多位置中存在一些意外行为。这些警告默认情况下没有打开的事实反映了,IMO,大多数代码都是草率的,如果诊断消息太多,大多数程序员会忽略诊断消息。我通常用-Werror 编译,如果有any 警告,它会阻止代码编译。
    • 这很容易演变成关于强类型语言与动态语言的争论……我只想说,大多数时候,我对无符号整数和有符号整数之间的区别不感兴趣。有时它会咬我,很明显,但我最终打赌我会浪费更多时间修复编译器警告,而不是节省调试这些罕见情况的时间。懒惰是一种美德。
    • @jab:好吧,只要您可以编写带有这些警告检测到的缺陷类型的代码。我曾多次被签名/未签名比较所困扰,多年来,此类错误已导致各种产品中出现许多安全漏洞。它并不比缓冲区溢出更安全,如果你已经确定了自己的位置,我所说的也不会让你关心缓冲区溢出。
    • 这也不是关于静态与动态类型的真正区别。大多数动态语言使这种错误不可能发生。具体来说,这实际上更像是算术在 C 中的工作方式的一个怪癖。有人将此称为“强”和“弱”类型系统之间的区别。
    【解决方案2】:

    3 - [tasks count] 使用无符号类型完成,因此减法回绕,变为 0xffffffff。所以你会得到无符号类型的 MAX(1, 0xffffffff)。

    但是,您将其打印为有符号整数,因为您的 NSLog 格式化字符串是“%d”,它将使 NSLog 将参数的位视为有符号整数,并且位模式 0xfffffff 为 -1 .

    【讨论】:

    • 是的。当然,使用错误的格式说明符无论如何都会调用 UB。
    • @H2CO3:在这种情况下它会调用 UB,但使用错误的说明符并不总是会导致 UB(您可以将 %u%d 互换为 int 范围内的正值) .
    猜你喜欢
    • 1970-01-01
    • 2016-10-01
    • 2012-01-09
    • 2013-02-04
    • 1970-01-01
    • 1970-01-01
    • 2018-08-14
    • 2015-03-18
    相关资源
    最近更新 更多