起因
在技术群里发现有人在问
BOOL a = 8960;
bool b = 8960;
if (a) {
NSLog(@"a yes");
} else {
NSLog(@"a no");
}
if (b) {
NSLog(@"b yes");
} else {
NSLog(@"b no");
}
这一段会输出什么,问这个的原因是他看到博客上都说是输出a no b yes,但是自己测了不是这样。
先说结论:在32位系统上输出a no b yes,在64位系统上输出a yes b yes(本文所有关于操作系统的信息都是基于iPhone上,即iOS)。
BOOL和bool到底是什么
在Stack Overflow上搜到了,在这里整理一下,有需要的朋友可以看原文。
- 首先我们来看一下OC中对于
BOOL的定义,在objc.h头文件中,可以通过import <objc/objc.h>进入(适当删减)
// __OBJC_BOOL_IS_BOOL not set.
# if TARGET_OS_OSX || (TARGET_OS_IOS && !__LP64__ && !__ARM_ARCH_7K)
# define OBJC_BOOL_IS_BOOL 0
# else
# define OBJC_BOOL_IS_BOOL 1
# endif
#if OBJC_BOOL_IS_BOOL
typedef bool BOOL;
#else
# define OBJC_BOOL_IS_CHAR 1
typedef signed char BOOL;
// BOOL is explicitly signed so @encode(BOOL) == "c" rather than "C"
// even if -funsigned-char is used.
#endif
从这里面可以看出,如果设备是64位的iPhone或架构为ARMv7k则定义BOOL为bool类型。如果设备不满足这些条件,那么就定义BOOL为signed char有符号字符型(signed有符号unsigned无符号)
- 下面是
BOOL类型中YES和NO的定义
#if __has_feature(objc_bool)
#define YES __objc_yes
#define NO __objc_no
#else
#define YES ((BOOL)1)
#define NO ((BOOL)0)
#endif
__has_feature (objc_bool)意思是判断是否有objc_bool这个特性
if (__has_feature(objc_bool)) {
NSLog(@"yes");
} else {
NSLog(@"NO");
}
经测试在32位和64位iPhone上都打印为yes,那么对于YES和NO,在iOS里其实就是__objc_yes __objc_no,在LLVM文档里写了对了__objc_yes __objc_no的定义。

以前的
BOOL类型就是signed char,YES和NO是(BOOL)1和(BOOL)0的宏定义,后为了支持@(YES)和@(NO)的表达方式,现在就将YES和NO定义成了__objc_yes __objc_no(这两个都是关键字),用来消除BOOL类型和整形之间的歧义。打印一下
__objc_yes __objc_no结果为1和0,当使用%@占位符打印的时候,会报警告,后面的提示跟上面所说的不同位数下BOOL的类型相符。

那么这个
__objc_yes,32位下是signed char 164位下是bool 1。
- 那么
bool类型又是什么呢,首先我们在stdbool.h找到bool的定义
/* Don't define bool, true, and false in C++, except as a GNU extension. */
#ifndef __cplusplus
#define bool _Bool
#define true 1
#define false 0
#elif defined(__GNUC__) && !defined(__STRICT_ANSI__)
/* Define _Bool as a GNU extension. */
#define _Bool bool
#if __cplusplus < 201103L
/* For C++98, define bool, false, true as a GNU extension. */
#define bool bool
#define false false
#define true true
#endif
#endif
#define __bool_true_false_are_defined 1
__cplusplus这个宏是代表编译器将文件按照C/C++语法来解释。当前程序按
C语法来解释的话,定义bool为_Bool(stdbool.h头文件是在C99中新增的,为了解决C中没有bool类型这个问题,并且和C++兼容),_Bool是C99中新增的关键字,只有0 1两个值,只占1个字节。这里还定义了true 1,false 0,这就相当于将_Bool关键字转为了bool类型,且值为true或false。当前程序按
C++语法来解释的话(C++有bool类型,占1个字节),那么定义_Bool为bool类型。后面这几句#define x x是因为在C99 standard中提到了
The header shall define the following macros: bool, true, false, __bool_true_false_are_defined.
An application may undefine and then possibly redefine the macros bool, true, and false.
头文件应定义以下宏:bool、true、false、__bool_true_false_are_defined。
应用程序可能没有定义这些宏,然后重新定义bool、true和false。
这里#define x x还有一个用处是可以在其他地方使用
#ifdef bool
some code here
#endif
认清BOOL
我们可以发现,不管是在C(C99之后)还是在C++中,bool类型都只有两个值,0/1 (赋值的时候如果不等于0,则为1),且都只占一个字节。
那么:
- 在64位操作系统上或处理器架构为
ARMv7k,BOOL为bool,取值为0或1,如
BOOL a = ?;
// 等价于
bool a = ?;
// 即只有当?为0的情况下,a才NO,其余情况都等于YES
上文中的BOOL a = 8960,a就等于YES。
- 在32位操作系统上,
BOOL类型为signed char,占一个字节,取值范围为-128~127。
问题在哪
在32位操作系统上,BOOL为signer char时。
在赋值的过程中,如果赋值的对象字节数超过了1个字节,那么只会取到低8位的值。
如8960,转化为二进制为
0010 0011 0000 0000
这时候BOOL b = 8960等同于BOOL b = 0000 0000,高位会全部丢失,即b = 0,这时候就会出现BOOL b = 8960b = NO的问题。除了赋值的时候可能有问题,比较的时候也可能有问题,如
BOOL c = 2;
if (c == YES) {
NSLog(@"c YES");
} else {
NSLog(@"c NO");
}
if (c) {
NSLog(@"c == %d", c);
} else {
NSLog(@"else c == %d", c);
}
NSLog(@"@(c) = %@", @(c));
输出c NO,c == 2,@(c) = 1。在上文中已经说了YES这时候等于signed char 1,那么很显然,signed char 2 != signed char 1,即c != YES,将其转为对象后,输出1,猜测应该是NSNumber对BOOL类型进行了隐式处理。
我们该怎么做
在条件判断语句中,不要直接使用
x == YES,或x != YES这种写法避免将大于一个字节的值赋值给
BOOL类型的变量,如BOOL a = 8960使用
bool类型
参考链接
ObjC的BOOL为什么要用YES、NO而不建议用true、false
解释一下为啥负数的取值范围比整数要多一个
Objc 中 “== YES” 的愚蠢行为有多可怕
BOOL with 64-bit on iOS
_Bool data type of C99
Why does clang's stdbool.h contain #define false false
Utility of macros for enum
本文中32位的测试机为iPhone 5C