Last active
August 29, 2015 13:57
-
-
Save jeremy-w/9558336 to your computer and use it in GitHub Desktop.
Beware truncation when testing option bits or other bitmasks. Also: Why _Bool is better than BOOL.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//cc -Weverything -fobjc-arc -framework Foundation bitmask.m -o bitmask | |
/** @file bitmask.m | |
* @author Jeremy W. Sherman (@jeremy-w on Github) | |
* @license ISC (http://opensource.org/licenses/isc-license.txt) | |
* | |
* Provides an example where assignment of the result of a naive bitmask check | |
* to BOOL fails. | |
* | |
* Demonstrates 2 ways to avoid this issue. | |
* | |
* Note that during compilation, -Wconversion warns about this issue. | |
* | |
* To read more on the dangers of C arithmetic, see: | |
* | |
* - https://jeremywsherman.com/blog/2013/09/26/math-is-hard/ | |
* - https://www.informit.com/articles/article.aspx?p=686170&seqNum=5 | |
* - https://www.securecoding.cert.org/confluence/display/seccode/INT02-C.+Understand+integer+conversion+rules | |
* | |
* Sample output: | |
* | |
* Workpad% cc -Weverything -fobjc-arc -framework Foundation bitmask.m -o bitmask | |
* bitmask.m:43:27: warning: implicit conversion loses integer precision: 'unsigned long' to 'BOOL' (aka 'signed char') [-Wconversion] | |
* BOOL is_whoah = flags & FLAG_WHOAH; | |
* ~~~~~~~~ ~~~~~~^~~~~~~~~~~~ | |
* bitmask.m:61:35: warning: implicit conversion loses integer precision: 'unsigned long' to 'BOOL' (aka 'signed char') [-Wconversion] | |
* BOOL is_a_bit_too_big = flags & FLAG_A_BIT_TOO_BIG_FOR_BOOL; | |
* ~~~~~~~~~~~~~~~~ ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
* 2 warnings generated. | |
* Workpad% ./bitmask | |
* 2014-03-14 18:34:27.584 bitmask[92932:507] NOTE: All these booleans should report 1, but truncation intervenes. | |
* 2014-03-14 18:34:27.586 bitmask[92932:507] is_whoah 0 | |
* no_really_is_whoah 1 | |
* is_whoah_bang_bang 1 | |
* 2014-03-14 18:34:27.586 bitmask[92932:507] is_a_bit_too_big 0 | |
* 2014-03-14 18:34:27.586 bitmask[92932:507] _Bool works: whoah set? 1 - too big set? 1 | |
*/ | |
#import <Foundation/Foundation.h> | |
typedef NS_OPTIONS(NSUInteger, Flags) { | |
FLAG_A = 1, | |
FLAG_B = 2, | |
FLAG_C = 4, | |
FLAG_A_BIT_TOO_BIG_FOR_BOOL = 0x100, | |
FLAG_WHOAH = 0x80000000, | |
}; | |
int | |
main(void) | |
{ | |
@autoreleasepool { | |
NSLog(@"NOTE: All these booleans should report 1, " | |
@"but truncation intervenes."); | |
NSUInteger flags = FLAG_A|FLAG_C|FLAG_A_BIT_TOO_BIG_FOR_BOOL|FLAG_WHOAH; | |
/* flags & FLAG_WHOAH happens in NSUInteger land. | |
* Then we assign down to a little tiny signed char and things don't go so | |
* well: the value is truncated. */ | |
BOOL is_whoah = flags & FLAG_WHOAH; | |
/* In this case, we're still working in NSUInteger land, but the equality | |
* test results in a single (int)1, which fits just fine into our BOOL | |
* and in fact compares == YES. */ | |
BOOL no_really_is_whoah = ((flags & FLAG_WHOAH) == FLAG_WHOAH); | |
/* !! "bang-bang" has the same boolean value as the original expression, | |
* but * has the effect of forcing the value to be | |
* either (int)1 or (int)0. */ | |
BOOL is_whoah_bang_bang = !!(flags & FLAG_WHOAH); | |
NSLog(@"is_whoah %hhd\n" | |
@"no_really_is_whoah %hhd\n" | |
@"is_whoah_bang_bang %hhd", | |
is_whoah, no_really_is_whoah, is_whoah_bang_bang); | |
/* This neatly demonstrates truncation, since the flag value is just | |
* outside the range representable by a single char. */ | |
BOOL is_a_bit_too_big = flags & FLAG_A_BIT_TOO_BIG_FOR_BOOL; | |
NSLog(@"is_a_bit_too_big %hhd\n", is_a_bit_too_big); | |
/* Now, behold the magic of _Bool: it handles this mess for you, as if | |
* you had done the bang-bang trick yourself. */ | |
_Bool is_magic = flags & FLAG_WHOAH; | |
_Bool no_really = flags & FLAG_A_BIT_TOO_BIG_FOR_BOOL; | |
NSLog(@"_Bool works: whoah set? %d - too big set? %d", | |
is_magic, no_really); | |
/* Note we had to use %d here or get yelled at by -Wformat, | |
* since %hhd specifies signed char, but we're passing a _Bool. | |
* _Bool has no specifier of its own, so use %d and rely on the | |
* default argument promotions that happen with varargs functions. */ | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment