I develop firmware for embedded systems using various microcontroller architectures/word sizes and try to emphasize portability when I can. My question is about generating/applying bit masks and flags that is safe and portable, and for any non-universal code, what would the corner cases be.
Bit masks
#define MASK_8_V1(x) ((x) & 0xFF)
#define MASK_8_V2(x) ((x) & 0x00FF)
a = MASK_8_V1(b)
a = MASK_8_V2(b)
Are these always guaranteed to get the value of a's width where all bits are zero'd out except the lower 8 from b? Is there any difference between the 2 versions since they should be signed extended if necessary?
Flags
#define GEN_FLAG_16(x) ((0xFFFF) & ((unsigned) 1 << (x)))
#define GEN_FLAG_32(x) ((0xFFFFFFFF) & ((unsigned long) 1 << (x)))
If I need a generic macro for generating flag constants, will this always result in a flag constant for the listed width?
Both
#define CHECK_FLAG_16(x, y) ((x) & GEN_FLAG_16(y))
#define CHECK_FLAG_32(x, y) ((x) & GEN_FLAG_32(y))
if(CHECK_FLAG_16(a, b))
{
// Do something.
}
Combining the previous scenarios, will this always execute the inner code if the desired bit in b's original value is set?
For all cases, assume:
Edit for those mentioning the use of stdint.h
: I recently came across an issue where we needed to port a serial protocol handler I had written over to another microcontroller family, only to discover that RAM was not byte addressable. We ended up removing all uses of uint8_t and made modifications to work with 16-bit addressable memory. This got me wondering if I could have implemented it differently with native C types to avoid later modification. My questions came indirectly from that problem.
Are these always guaranteed to get the value of a's width where all bits are zero'd out except the lower 8 from b?
Yes.
The resulting type of the macro might be different though, depending on what type b
is. This could cause portability issues. Therefore it would be best to cast the result to the intended type, for example uint32_t
.
Is there any difference between the 2 versions
No they are equivalent.
they should be signed extended if necessary
It usually doesn't make any sense to use bit-wise operators on signed types.
If I need a generic macro for generating flag constants, will this always result in a flag constant for the listed width?
Yes, but the result will have a different type depending on size of int or long.
I recently came across an issue where we needed to port a serial protocol handler I had written over to another microcontroller family, only to discover that RAM was not byte addressable.
That's mainly the compiler's problem. Also, it isn't clear how uint8_t
would be a problem on such systems, there is always implicit integer promotion. Sounds rather like you had some algorithm problem, maybe code using uint8_t*
pointers or something like that.
Pedantically, fully portable code would look something like:
#define MASK8_32(x) ((uint32_t)(x) & 0xFFul)
#define GEN_FLAG_16(x) (uint16_t)( 0xFFFFu & (1u << (x)) )
#define GEN_FLAG_32(x) (uint32_t)( 0xFFFFFFFFu & (1ul << (x)) )
Now most of the potential size-of-int and implicit type promotion reliances have been removed.
Main portability concerns here are:
int
and long
Such code is non-portable, because those types may have any size. Using the fixed-width integer types from stdint.h will solve a lot of these problems. As it turns out, all of these problems can be solved by MISRA-C. I'd recommend buying MISRA-C:2012 and reading it for education purposes.
As a side note, code like a = OBSUCRE_MACRO(b);
is far less readable than code like a = b & 0xFF;
. Because you can always assume that the reader is a C programmer, and as such knows the C language, but does not know your private, secret macro language.
In addition, function-like macros are unsafe and should be avoided when possible.
So I question what good these macros do in the first place.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With