BOOL's sharp corners
2016-05-18 16:37
856 查看
Update October 2013 - On 64-bit iOS (device and simulator)
now actually
Objective-C is actually a pretty old language, dating back from from the mid eighties. As such, it’s got some sharp corners here and there due to limitations of early C. Today’s sharp corner is
handy
and untruth. It turns out that
an eight-bit value. It’s not a first-class boolean type, which is one with compiler support (like
of places where
actually hold any non-zero value from -128 through 127 (in addition to zero), so there are 255 different flavors of truth. It’s a nice convention that functions and methods always return
but you can’t depend on a truth value from one of these to be equal to
generally a bad idea:
You have to trust that
and no other value. Without looking at the source or disassembly I can’t trust its truth value is always
a
How could a non-
then use that value to determine a Truth. If the math results in zero, then the Truth is false. If the math results in a non-zero value, then the truth is True.
Here’s a contrived piece of logic - given two integers, indicate if they’re different. An experienced C programmer might do this:
(You can find this code, and other code referenced here at this gist)
If you used a
But with Objective-C, the
returned, so it will be the result of the subtraction modulo 256. Comparing to
Here are some uses of this function, comparing to
You’d hope that all of these would print out “thing1 != thing2” in all cases. But that’s not the case:
Only the first case correctly says that the two numbers are different. If you only had the first case in your unit test, you’d think that things were working correctly. (ship it!)
The actual return values from
And notice the last expression actually evaluated to
Because I don’t trust the general programmer population to be aware of this subtlety when using
Of course, it’s always safe to compare against
When I write code that returns a
explicitly, versus doing some kind of arithmetic. You can also rely on logical expressions, though, to return values of zero and one, which happen to map to
I was actually caught unawares by this behavior, and was a good Learning Experience for me. I was doing a code review at Google, and saw this code:
And I dinged it, saying the return should be return (a == b) ? YES : NO;, because I thought the value of the expression was undefined, and would just evaluate to a C truth or false value. (I blame some buggy compilers in my distant past.)
One of my gurus there, David Philip Oster, countered that it was actually legal, and the value of logical expressions is well-defined. When DPO makes a statement like that, he’s usually correct. So after a couple of minutes of writing some test code and finding
chapter and verse in the C99 and C++ standards, I was convinced: logical expressions in C and C++ will always evaluate to a
which are one and zero, and happily equal to
I still won’t ever compare directly to
Another
a non-
That sounds scary. How can that happen?
try to squeeze a value larger than a
the compiler will happily truncate the upper bits, slicing them off.
So, what does this code print?
Why? Here it is bitwise:
The compiler happily stripped off the upper byte, leaving zeros. Granted, a casting of a large constant into a
again, the
The same code, but using a standard
So if your entire C boolean experience has been with with first-class types like
behavior probably comes as a surprise.
The same thing can happen with pointers. (Thanks to Mike Ash for introducing me to this class of errors.) You might think this is safe code:
If
so this function will be returning a true value. It might not be
it would return the correct value.
Unfortunately, with
Here’s two cases. The first is the string living at address
That returns a true value, 0x80. It’s not
the lower byte is zero, say
So you can see the function works in most cases, except for those times when the string happens to lie at particular addresses. It only fails every now and then. This is the kind of bug that can change from run to run.
Luckily
if you try to pass a larger integer or pointer through a
writing the code.
The take-away from this? For this kind of test I would either return an explicit
or use a logical expression
And not depend on automatic pointer->
behavior.
So, what’s the point of all of this? Mainly that us as users of Objective-C, we can’t forget the C portion of the language. There are the occasional sharp corners that come from Objective-C’s C heritage that we need to be aware of, such as
a signed char. Many situations that work correctly with
BOOLis
now actually
bool, so the sharp corners have thankfully gone away for that platform. For everything else, though…
Objective-C is actually a pretty old language, dating back from from the mid eighties. As such, it’s got some sharp corners here and there due to limitations of early C. Today’s sharp corner is
BOOL.
BOOLseems innocuous enough. It holds a boolean value. We All Know that in C a zero is a false value, and non-zero is a true value. There’s even the
handy
YESand
NOmacros to represent truth
and untruth. It turns out that
BOOLis actually just a typedef for a signed
char-
an eight-bit value. It’s not a first-class boolean type, which is one with compiler support (like
bool) to make sure it acts sanely. There’s a couple
of places where
BOOL’s non-first-classitude can cause subtle problems.
Don’t Compare to YES
YESis Objective-C’s truth value, equal to the value 1.
BOOLcan
actually hold any non-zero value from -128 through 127 (in addition to zero), so there are 255 different flavors of truth. It’s a nice convention that functions and methods always return
YESor
NO,
but you can’t depend on a truth value from one of these to be equal to
YES(the value 1). Comparing to
YESis
generally a bad idea:
if ([kitteh haz: cheezburger] == YES) { // LOL }
You have to trust that
-haz:returns
YESor
NO,
and no other value. Without looking at the source or disassembly I can’t trust its truth value is always
YES. There is no compiler enforcement that
a
BOOLreturn value, or variable, always holds
NOor
YES.
How could a non-
YESvalue find its way coming out of a function? A common C idiom (not necessarily ideal, but common) is to do some kind of math and
then use that value to determine a Truth. If the math results in zero, then the Truth is false. If the math results in a non-zero value, then the truth is True.
Here’s a contrived piece of logic - given two integers, indicate if they’re different. An experienced C programmer might do this:
static BOOL different (int thing1, int thing2) { return thing1 - thing2; } // difference
(You can find this code, and other code referenced here at this gist)
If you used a
boolreturn type this function would actually work correctly.
But with Objective-C, the
BOOLreturn value is actually cast to a
charand
returned, so it will be the result of the subtraction modulo 256. Comparing to
YESwill end up causing false negatives.
Here are some uses of this function, comparing to
YES:
if (different(11, 10) == YES) printf ("11 != 10\n"); else printf ("11 == 10\n"); if (different(10, 11) == YES) printf ("10 != 11\n"); else printf ("10 == 11\n"); if (different(10, 15) == YES) printf ("10 != 15\n"); else printf ("10 == 15\n"); if (different(512, 256) == YES) printf ("512 != 256\n"); else printf ("512 == 256\n");
You’d hope that all of these would print out “thing1 != thing2” in all cases. But that’s not the case:
11 != 10 10 == 11 10 == 15 512 == 256
Only the first case correctly says that the two numbers are different. If you only had the first case in your unit test, you’d think that things were working correctly. (ship it!)
The actual return values from
differentare 1, -1, -5, and 0. Only the first one happens to equal
YES.
And notice the last expression actually evaluated to
NO. More on this weirdity in a bit.
Because I don’t trust the general programmer population to be aware of this subtlety when using
BOOL, I use this idiom for checking
BOOLvalues:
if ([kitteh haz: cheezburger]) { // LOL }
Of course, it’s always safe to compare against
NO, because there is only one false value in C expressions - zero.
But you can rely on logical expressions
When I write code that returns a BOOL, I return a
YESor
NOvalue
explicitly, versus doing some kind of arithmetic. You can also rely on logical expressions, though, to return values of zero and one, which happen to map to
NOand
YES.
I was actually caught unawares by this behavior, and was a good Learning Experience for me. I was doing a code review at Google, and saw this code:
BOOL something () { // stuff stuff stuff return a == b; }
And I dinged it, saying the return should be return (a == b) ? YES : NO;, because I thought the value of the expression was undefined, and would just evaluate to a C truth or false value. (I blame some buggy compilers in my distant past.)
One of my gurus there, David Philip Oster, countered that it was actually legal, and the value of logical expressions is well-defined. When DPO makes a statement like that, he’s usually correct. So after a couple of minutes of writing some test code and finding
chapter and verse in the C99 and C++ standards, I was convinced: logical expressions in C and C++ will always evaluate to a
booltrue or false value,
which are one and zero, and happily equal to
YESand
NO.
I still won’t ever compare directly to
YESbecause I don’t have time to review everyone’s code I call.
Slice of Life
Another BOOLsharp corner is a variation of the above. Not only can a non-
NO
BOOLhave
a non-
YESvalue, sometimes it can be
NO.
That sounds scary. How can that happen?
BOOLis a
char, which is eight bits. If you
try to squeeze a value larger than a
charthrough
BOOL,
the compiler will happily truncate the upper bits, slicing them off.
So, what does this code print?
BOOL truefalse = (BOOL)256; printf ("256 -> %d\n", truefalse);
Zero.
NO. This “true” value is actually zero.
Why? Here it is bitwise:
The compiler happily stripped off the upper byte, leaving zeros. Granted, a casting of a large constant into a
BOOLmight raise a red flag. But then
again, the
differentfunction returned the value of a subtraction, truncating the value silently and didn’t require a cast.
The same code, but using a standard
booltype works fine:
bool stdTruefalse = (bool)256; printf ("256 -> %d (bool)\n", stdTruefalse);
So if your entire C boolean experience has been with with first-class types like
bool(lower-case), then the
BOOL(upper-case)
behavior probably comes as a surprise.
The same thing can happen with pointers. (Thanks to Mike Ash for introducing me to this class of errors.) You might think this is safe code:
static NSString *g_name; static BOOL haveName () { return (BOOL)g_name; } // haveName
If
g_nameis non-
nil, it’s a true value,
so this function will be returning a true value. It might not be
YES, but truth is truth in C, isn’t it? If this function returned
bool,
it would return the correct value.
Unfortunately, with
BOOL, it doesn’t. If the address happens to have a zero lower byte, this function will return zero due to the same slicing behavior.
Here’s two cases. The first is the string living at address
0x010CA880.
That returns a true value, 0x80. It’s not
YES, but it’s true. So code in general still works. If the address happens to be aligned in memory such that
the lower byte is zero, say
0x010CA800, the compiler would slice off the top three bytes leaving a zero:
So you can see the function works in most cases, except for those times when the string happens to lie at particular addresses. It only fails every now and then. This is the kind of bug that can change from run to run.
Luckily
clangand current versions of
gcccomplain
if you try to pass a larger integer or pointer through a
BOOL, requiring a cast. Hopefully adding the cast will raise some red flags in the programmer
writing the code.
The take-away from this? For this kind of test I would either return an explicit
YESor
NOvalue:
if (g_name) return YES; else return NO;
or use a logical expression
return g_name != nil;
And not depend on automatic pointer->
BOOL(or any integer->
BOOL)
behavior.
Going BOOLing
So, what’s the point of all of this? Mainly that us as users of Objective-C, we can’t forget the C portion of the language. There are the occasional sharp corners that come from Objective-C’s C heritage that we need to be aware of, such as BOOLbeing
a signed char. Many situations that work correctly with
bool, the first-class boolean type, can fail in weird and wonderful ways with
BOOL.
相关文章推荐
- Mysql 5.7.12解决修改root密码
- ASM磁盘超过disk_repair_time导致磁盘状态为forcing
- ecshop二次开发--电子票
- JAVA_OPTS参数过小
- poi导出excel设置宽度
- 服务器安装Linux服务器
- Sql Server REPLACE函数的使用
- 文件分块器
- Linux 切换用户
- MyEclipse安装Freemarker插件
- handler机制原理
- LeetCode 4 : Median of Two Sorted Arrays ---- 两排序数组的中位数
- ActiveMq 自学(一)
- can't use subversion command line client : svn
- JAVA—对象流
- How to link with the correct C Run-Time (CRT) library
- 【JavaScript】select选中Option
- 正则表达式在iOS中的运用
- ImageView和其加载库浅析
- AFNetWorking 3.0 使用遇到的问题