Skip to content

Instantly share code, notes, and snippets.

@jeremy-w jeremy-w/bitmask.m
Last active Aug 29, 2015

Embed
What would you like to do?
Beware truncation when testing option bits or other bitmasks. Also: Why _Bool is better than BOOL.
//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
You can’t perform that action at this time.