embedded software boot camp

Binary Literals in C

Wednesday, September 30th, 2009 by Michael Barr

A couple of years ago, Netrino engineer Dan Smith was writing stepper motor control firmware that interfaced to lots of registers with binary fields and sub-fields. After struggling a bit with the usual error-prone “off by 1 bit shift” masking and conversion from binary to hexadecimal literals in C, he happened across a useful post on a forum.

In a nutshell, the “binary literal” technique involves the following set of C preprocessor macros:

// Internal Macros
#define HEX__(n) 0x##n##LU
#define B8__(x) ((x&0x0000000FLU)?1:0) \
+((x&0x000000F0LU)?2:0) \
+((x&0x00000F00LU)?4:0) \
+((x&0x0000F000LU)?8:0) \
+((x&0x000F0000LU)?16:0) \
+((x&0x00F00000LU)?32:0) \
+((x&0x0F000000LU)?64:0) \
+((x&0xF0000000LU)?128:0)

// User-visible Macros
#define B8(d) ((unsigned char)B8__(HEX__(d)))
#define B16(dmsb,dlsb) (((unsigned short)B8(dmsb)<<8) + B8(dlsb))
#define B32(dmsb,db2,db3,dlsb) \
(((unsigned long)B8(dmsb)<<24) \
+ ((unsigned long)B8(db2)<<16) \
+ ((unsigned long)B8(db3)<<8) \
+ B8(dlsb))

Here are some examples of the usage of these macros:
B8(01010101) // 85
B16(10101010,01010101) // 43,605
B32(10000000,11111111,10101010,01010101) // 2,164,238,933

So if you had a memory-mapped 8-bit control register of the format XXXYYZZZ (where XXX, YY, and ZZZ are subfields), you could initialize it like so:

*p_reg = ( (B8(010) << 5) | (B8(11) << 3) | (B8(101) << 0) )

which sets the XXX bits to 010, YY to 11, and ZZZ to 101. If I ever needed to change XXX to 011, just change a single 0 to a 1 in the source code, and everything magically changes. Best of all, it’s all done at compile-time. No error-prone conversion to hexadecimal necessary, no figuring out which bits belong to which nibbles, etc.

What is that old saying? — “good programmers write good code; great programmers steal great code

Tags: ,

6 Responses to “Binary Literals in C”

  1. Nigel Jones says:

    Great post (or perhaps steal :-) ). I've always wanted a way to express binary numbers in C, and now I have one! I took the code and experimented a bit with it. If you turn optimization completely off, them my IAR ARM compiler did include some redundant code. However, the lowest level of optimization fixed that problem.The MISRA compliance checker did of course have an absolute fit over the use of these macros, generating a large number of complaints. I think this is yet another example where one has to make an intelligent trade off. Should I use a macro that is unsafe by MISRA standards, yet allows me to eliminate an entire class of errors?Perhaps in my copious free time I'll see if I can restructure the macros to make MISRA compliance possible.

  2. Nick says:

    MISRA compliance allows exceptions for exactly this kind of thingThis is making your code safer and not less safe:The macros encapsulate individually unsafe constructs but package them up with a very safe interface.You just need a project-wide MISRA deviation specialised to these three macros and you can retain full MISRA compliance, both in the spirit and the letter of the law.Different MISRA checkers handle deviations in different ways but PC-Lint handles macro-specific deviations very nicely, for one example.

  3. Kenneth says:

    I'm trying to learn the finer points of C and how to be creative with it. :) I don't understand something the binary macros. —————————-/* 8-bit conversion function */#define B8__(x) ((x&0x0000000FLU)?1:0) \+((x&0x000000F0LU)?2:0) \+((x&0x00000F00LU)?4:0) \+((x&0x0000F000LU)?8:0) \+((x&0x000F0000LU)?16:0) \+((x&0x00F00000LU)?32:0) \+((x&0x0F000000LU)?64:0) \+((x&0xF0000000LU)?128:0)——————————-If we take part of the first line:(x&0x0000000FLU)It would appear to me that x is being anded with the constant 0x0000000FLU.Its with this constant that I get lost. To me 0x0000000F = 15 (in decimal). The "LU" as I underatand it, and I could be wrong, signifies the constant is an Unsigned Long.I understand how the macro is supposed to work. My problem, I guess is with the syntax.Can someone shed some light?Thanks,Kennykenl@anspach.com

  4. Nigel Jones says:

    Sure can. By way of example, lets look at B8(1011).First off the HEX__ macro converts the argument 1011 into 0x1011UL. It does this via the stringize operator ##. (This is a pretty obscure part of the preprocessor – you'll find more information online).Now let's see what happens with the B8__(x) macro. It consists of 8 lines, each of which effectively tests one bit position. The first line is (x & 0x0000000Flu) ? 1 : 0. (Note that I've added spaces and made the LU lower case, thus making things a little clearer IMHO).Anyway, this becomes: 0x1011ul & 0x0000000Ful = 0×1. This evidently evaluates to true and so the ternary operator returns 1.The second line of the B8__() macro is (x & 0x000000F0lu) ? 2 : 0). Thus this expands to 0×1011 & 0x000000F0 = 0×10. This evidently evaluates to true and so the ternary operator returns 2, which is added to the previous result, giving a total of decimal 3.The third line of the B8__() macro is (x & 0x00000F00lu) ? 4 : 0). Thus this expands to 0×1011 & 0x00000F0 = 0×0. This evidently evaluates to false and so the ternary operator returns 0, which is added to the previous result, keeping our total at decimal 3.The fourth line of the B8__() macro is (x & 0x0000F000lu) ? 8 : 0). Thus this expands to 0×1011 & 0x0000F000 = 0×1000. This evidently evaluates to true and so the ternary operator returns 8, which is added to the previous result, giving a total of decimal 11 – and our final result.The B16 and B32 macros simply build upon this technique by using appropriate casts and shifts.Hope this helps. If not post again and I'll try and expand upon the explanation.

  5. Miro Samek says:

    Indeed, MISRA checker does not particularly like the macros, because they violate the following MISRA rules:macro HEX__(n) Violates MISRA Required Rule 98, Multiple use of '#/##' operators in macro definitionmacro B8__ Violates MISRA Rule 7, Trigraphs shall not be usedmacro B8__ Violates MISRA Rule 96, Expression-like macro '' not parenthesizedBut the macros can be restructured as follows to comply with MISRA:#define HEX__(n_) ((uint32_t)0x##n_)#define B8__(x_) \ ( ((x_) & 0x1LU) \ | (((x_) & 0x10LU) >> 3) \ | (((x_) & 0x100LU) >> 6) \ | (((x_) & 0x1000LU) >> 9) \ | (((x_) & 0x10000LU) >> 12) \ | (((x_) & 0x100000LU) >> 15) \ | (((x_) & 0x1000000LU) >> 18) \ | (((x_) & 0x10000000LU) >> 21))#define B8(b0_) ((uint8_t)B8__(HEX__(b0_)))#define B16(b1_,b0_) \ ((uint16_t)(B8__(HEX__(b0_)) | (B8__(HEX__(b1_)) << 8)))#define B32(b3_,b2_,b1_,b0_) \ ((uint32_t)(B8__(HEX__(b0_)) \ | (B8__(HEX__(b1_)) << 8) \ | (B8__(HEX__(b2_)) << 16) \ | (B8__(HEX__(b3_)) << 24)))While the macros are MISRA compliant, the actual use can sometimes lead to violating MISRA rule 19 (octal constant used), if you start your binary literal with a zero. Miro Samek

  6. [...] topics, if you have not read Mike Barr’s recent posting on binary literals, then I strongly recommend that you do so. It would have fitted very nicely into [...]

Leave a Reply