C 无符号整数减法、宏和类型转换

Posted

技术标签:

【中文标题】C 无符号整数减法、宏和类型转换【英文标题】:C unsigned integer subtraction, macros, and typecasting 【发布时间】: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。)

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

【问题讨论】:

【参考方案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 范围内的正值) .

以上是关于C 无符号整数减法、宏和类型转换的主要内容,如果未能解决你的问题,请参考以下文章

如何在python中将有符号整数转换为无符号整数

从有符号/无符号字符到无符号/有符号整数类型转换的 IA32 汇编代码

与类型转换相关的排名是啥意思?

如何以优雅有效的方式将无符号/有符号整数/长整数转换为 C 字符串?

printf()函数输出啥类型的数据?

Android Studio NDK 入门教程--Java与C++之间的简单数据转换与传递