Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Applying portable bit masks and flags in C [closed]

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:

  • C90 or C99 compliant compiler
  • a and b can be any arbitrary combination of native C types, signed or unsigned
  • x always evaluates to a positive integer type
  • arbitrary native word size

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.

like image 622
derrick Avatar asked Oct 29 '22 22:10

derrick


1 Answers

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:

  • Your code relies on the size of 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.
  • Your code is unaware of signedness of default types and integer literals. In many cases you use bit-wise operators on signed operands, which is always a bad idea.
  • You seem generally unaware of how implicit type promotions work in C.

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.

like image 104
Lundin Avatar answered Nov 11 '22 15:11

Lundin