起因
在技术群里发现有人在问
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 1
64位下是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 = 8960
b = 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