// This macro is key
//
#define bitn(n) (1 << n)
// Define some stuff to "document" code without documenting anything
("self-documenting code")!
//
#define bitset8 uint8
#define bitset16 uint16
#define bitset32 uint32
#define bitset64 uint64
// Gotta love "close to the metal"!
//
#define chkbit(x, n) (((x) & bitn(n)) ne 0)
#define setbit(x, n) ((x) |= bitn(n))
#define clrbit(x, n) ((x) &= ~bitn(n))
#define tglbit(x, n) ((x) ^= bitn(n))
// WINDOW_THICK_BORDER|WINDOW_MINMAXBUTTONS anyone?
//
#define chkmask(x, m) (((x) & (m)) eq m)
#define setmask(x, m) ((x) |= (m))
#define clrmask(x, m) ((x) &= (~m))
#define tglmask(x, m) ((x) ^= (m))
// some uses may require the following
#define bitn64(n) (1ui64 << n)
**********
Aside, I use n-1 rather than n in the bitn macros, knowing that there is
potential for error there. I prefer, in this case, great semantics rather
than submission to the language.
I also have a collection of bit manipulation macros, and I think
macros do have advantages over straight up code.
1. Readability - This one is somewhat contentious for some of the
simpler methods, but when you start creating more sophisticated bit
manipulation methods, one can get lost in the mechanics.
/*!
* \brief Define the integer type used to create a bitmask of all
ones.
*
* One of the basic constructs used in the \c bits macro library is to
* create a mask that is all ones of a given width starting from the
* least significant bit. This is implemented using the expression
* <tt>~((~0) << width)</tt>. This is used to isolate a region of
bits
* to be set or cleared within an integer. This concept is used to
* isolate a range of bits in an integer demonstrated with the set of
* \c BIT_FIELD macros.
*
* Ideally this should be configured via config.h through autoconf,
but
* for now its location is here. The \c C_BITS_ALL_ONES define can be
* \c ~0 for \c int sized masks, <tt>~UINT32_C(0)</tt> to enable 32-
bit
* support on 16-bit integer platforms, or <tt>~UINT64_C(0)</tt> to
enable
* 64-bit support.
*
* See \ref C_BITS_ONE for more information.
*/
#define C_BITS_ALL_ONES ( ~UINT32_C(0) )
/*!
* \brief Extract \c WIDTH bits from \c WORD at \c INDEX.
*
* The macro \c C_EXTRACT_BIT_FIELD returns a region within \c WORD
* that contains the bits at \c INDEX that is \c WIDTH long. The
* result is shifted so that the bit at \c INDEX is the least
* significant bit. The purpose of the macro is to provide another
* method to access bitfields packed into a word or integer without
* relying on pointer casting or defining a struct with bitfields.
*/
#define C_EXTRACT_BIT_FIELD(WORD, INDEX, WIDTH) ( (WORD >> (INDEX)) &
~((C_BITS_ALL_ONES) << (WIDTH)) )
An example would be processing ARINC 429 flight data in a generic
fashion, where data is encoded within the word at different locations
and widths.
/*
* The following example will use the ARINC 429 standard message
* structure used in avionics applications for transmitting data,
* which is shown in the structure definition below.
*
* \code
* struct arinc_429_message
* {
* uint32_t parity:1; // parity bit
* uint32_t ssm:2; // sign/status matrix
* uint32_t data:19; // data field
* uint32_t sdi:2; // source/destination identifier
* uint32_t label:8; // label field
* };
* \endcode
*
* The ARINC 429 standard is a point to point protocol consisting of a
* twisted pair that transmits 32-bit messages from avionics devices
on
* an aircraft. The data bus consists of one source with many
potential
* receivers. The following is a description of the meaning of the
* message fields.
*
* <ul>
* <li> \c label - This identifies the data type and the parameters
* associated with it for representing avionics
data.
* A single avionics device may have up to 256
different
* messages.
* <li> \c sdi - This field is used for multiple receivers to
identify
* which receiever is the recipient of the avionics
data.
* <li> \c data - The avionics data, represented in a number of
different
* formats. The common ones are binary coded
decimal,
* 2's complement binary, and bit fields.
* <li> \c ssm - This field contains hardware equipment
conditions,
* operational mode, or validity of data content.
* <li> \c parity - The most significant bit is the parity bit,
typically
* used to ensure the ARINC 429 message has odd
parity.
* Whether this bit is used depends on the avionic
system
* design.
* </ul>
*
* Let's look at a real ARINC 429 standard message from an Air Data
* Computer (ADC). The labels are represented in octal form.
*
* Total Air Temperature
* <ul>
* <li> Bit: 1 - 8 Name: Label (211)
* <li> Bit: 9 - 10 Name: SDI
* <li> Bit: 11 - 17 Name: Spare
* <li> Bit: 18 - 29 Name: Total Air Temperature Value \n
* <ul>
* <li> Element Size: 12
* <li> Type: 2's Complement
* <li> Units: Degrees C
* <li> Scale: .25
* <li> State: [-60, 100]
* </ul>
* <li> Bit: 30 - 31 Name: SSM
* <li> Bit: 32 Name: Parity
* </ul>
*/
To be able to translate this into human readable form, if I have a 32
bit arinc message, I can write something like this.
\code snippet
uint32_t arinc_429_message = 0x60540089;
uint8_t label;
int16_t data;
const int field_offset = 17;
const int field_size = 12;
label = C_EXTRACT_BIT_FIELD( arinc_429_message, 0, 8 );
data = C_EXTRACT_BIT_FIELD( arinc_429_message, field_offset,
field_size );
\endcode
In my opinion, this is much more readable and requires less effort to
understand what's going on than trying to do it raw (and I have worse
macros than this). It does require some trust that the macro
implementation does what it's supposed to do correctly, but if that
can be verified, the net gain is higher than having complicated bit
manipulation spread throughout the code.
2. Encapsulating the operation in one central location, the macro
definition, makes maintenance easier.
The biggest gain from using a macro to encapsulate the bit
manipulation is that mistakes are localized to a single definition.
If I make a logic error in writing some bit manipulation, and it's
spread out over many functions and files, the penalty for fixing that
error is much higher than fixing an error in a macro definition (I
experienced this directly). Inspecting raw bit manipulation code is
tedious, and if everyone involved understands the limitations of the
macro syntax and uses it correctly, less effort is needed to verify
code in contrast with doing everything by hand.
--
The main reason that I prefer a macro over a function is its ability
to apply the same functionality over a myriad of types without the
overhead of maintaining an arbitrary set of practically duplicate
functions needed to operate on each type. It does have drawbacks like
any macro does, and the issue with how to isolate bits in words of
varying bit widths is the worst (i.e. how to encode '1' in the
expression '1 << n').
/*!
* \brief Define the integer type used to create a bitmask of zeros
with
* the least-significant bit enabled.
*
* One of the basic constructs used in the \c bits macro library is to
* create a mask that is all zero except for a single bit at a given
* index. This is often implemented using the simple expression
* <tt>1 << index</tt>. This is used to isolate single bits to be set
* or cleared within an integer.
*
* There is a caveat to consider when using this expression. The
issue
* is that the value of \c 1 is evaluated to be of type integer. For
* environments where the integer type is 16-bit, this expression will
* fail when trying to set a bit with an index >= 16 in a 32-bit long
* integer. A similar problem arises for 32-bit environments when
* trying to use \c 1 to set a bit at index >= 32 in a 64-bit integer.
* If this is an issue, there will typically be a warning that says
* that the left shift count is larger than the width of the integer
* type. If this error is found, \c C_BITS_ONE will need to be
defined
* as a larger integer type.
*
* The method to increase the width used by the macro library is to
* specify the type explicitly. This can be specified using a
stdint.h
* constant like <tt>UINT64_C(1)</tt> to enable support for 64-bit
* integers. Keep in mind that increasing the width beyond the
default
* integer type for the processor may incur a performance penalty for
* all macros.
*
* Ideally this should be configured using Autoconf or a Makefile, but
* for now its location is here. The \c C_BITS_ONE define can be \c 1
* for \c int sized masks, <tt>UINT32_C(1)</tt> to enable 32-bit
support
* on 16-bit integer platforms, or <tt>UINT64_C(1)</tt> to enable 64-
bit
* support.
*/
#define C_BITS_ONE UINT32_C(1)
#define C_BIT(INDEX) (C_BITS_ONE << (INDEX))
#define C_SET_BIT(WORD, INDEX) (WORD |= (C_BITS_ONE << (INDEX)))
#define C_CLEAR_BIT(WORD, INDEX) (WORD &= ~(C_BITS_ONE << (INDEX)))
All of my bit macros use this C_BITS_ONE definition to isolate bits,
so in a sense, C_BITS_ONE hardwires the maximum bit width used. It's
not perfect, but I'm willing to deal with the limitations for what
advantages it does provide me. There may be better ways to do it too.
One drawback of this approach is that it may force a larger bit width
than is needed to perform the operation. For example, if I want to
support 64-bit flags on when the CPU natively supports 32-bit
integers, the performance of all bit operations that go through my
macros would suffer if I define C_BITS_ONE to be UINT64_C(1).
Fortunately, in my case the performance issue is not a big deal.
(In regard to your syntactic blurring with your own #defines, you're
going to be in a very small minority of people who advocate that.
Just keep that in mind if/when you program in a team environment.)
Best regards,
John D.