Using portable bit masks and flags in C

I develop firmware for embedded systems using various microcontroller architectures / word sizes, and try to emphasize portability when I can. My question is creating / applying bit masks and flags that are safe and portable, as well as for any non-universal code, what will be the angular cases.

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) 

Is it always guaranteed to get a width value where all the bits are zero, except for the bottom 8 of b? Is there a difference between the two versions, as they need to be expanded 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 common macro to generate flag constants, will this always result in a flag constant for the specified width?

AND

 #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 previous scenarios, will it always execute internal code if the desired bit is set to the original value?

For all cases, suppose:

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

Edit for anyone mentioning the use of stdint.h . I recently ran into a problem when we needed to pass a serial protocol handler, which I wrote to another family of microcontrollers, only to find that the RAM was not addressable. We ended up removing all uses of uint8_t and made changes to working with 16-bit address memory. This made me wonder if I could implement it differently with native C types to avoid a later modification. My questions indirectly addressed this problem.

+5
source share
2 answers

Is it always guaranteed to get a width value where all the bits are zero, except for the bottom 8 of b?

Yes.

The resulting macro type may be different, depending on type b . This can cause portability problems. Therefore, it would be better to make the result a target type, for example uint32_t .

Is there a difference between the two versions

No, they are equivalent.

they must be signed, if necessary,

As a rule, it makes no sense to use bitwise operators for signed types.

If I need a common macro to generate flag constants, will this always result in a flag constant for the specified width?

Yes, but the result will have a different type depending on the size of int or long.

Recently, I ran into a problem when we needed to transfer the serial protocol handler that I wrote to another family of microcontrollers, only to find that the RAM was not addressable for the byte.

Basically a compiler problem. Also, it is unclear how uint8_t will be a problem for such systems; there is always implicit integer progress. It looks like you had some kind of problem with the algorithm, maybe the code using uint8_t* pointers or something like that.


A pedantically fully portable code would look something like this:

 #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)) ) 

Currently, most of the potential size and implicit type restrictions have been removed.


The main portability features are important here:

  • Your code depends on the size of int and long . Such code is not portable, as these types can be of any size. Using fixed-width integer types from stdint.h will solve many of these problems.
  • Your code is not aware of the standardness of default types and integer literals. In many cases, you use bitwise operators for signed operands, which is always bad.
  • You generally don’t know how implicit type promotions work in C.

As it turns out, all of these problems can be solved using MISRA-C . I would recommend buying MISRA-C: 2012 and reading it for educational purposes.


As a side note, a code of type a = OBSUCRE_MACRO(b); much 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 secret macro language.

Functional macros are also unsafe and should be avoided when possible.

So, I ask the question what these macros do well in the first place.

+3
source

What you really have to do is use the types defined in <stdint.h> . You know exactly how many bits you need for your flags, so you choose the appropriate data type. For instance:

 #define GEN_FLAG_8(x) ((uint8_t)(1 << (x))) #define GEN_FLAG_16(x) ((uint16_t)(1 << (x))) 

You can also choose the same guarantees, but potentially larger data types with uint_fast16_t etc.

It is worth mentioning that you must make macros or functions to mask, so that you avoid overflowing your code with whole literals.

-1
source

All Articles