Avoiding #defines for constant data and using enums instead

I think that the C preprocessor is a very powerful tool, but I like to limit my use of #defines. I have already touched on this when i talked about why I liked FORCEINLINE and I want to talk about it some more. I realize I can't eliminate the use of #defines throughout all of my code for various reasons, among them being:

  • They can have unintended side affects. The classic example of this is #define MAX(a, b) (a >= b ? a : b) and invoking it with MAX(foo++, bar) such that foo++ is evaluated twice leading to a double increment.
  • Other headers use them to enable/disable certain pieces of functionality or functions.
  • They are the only way to wrap up other preprocessor macros like __FILE__ and __LINE__ to allow for easier logging without more pushing more typing onto the caller.
  • They are the only way you can generate code on the fly in C (a poor man's C++ template if you will)
  • They are the only way you can use the quotify (#) or concat (##) functions within the preprocessor

The main reason I try to limit my usage of the preprocessor is that because it is a pure substitution, you generally don't have type information available to you when debugging the application or driver. A great example of this is constant numeric values within your application. You could do this to define values in a bit field:

 #define OPTION_ONE   (0x00000001)
#define OPTION_TWO   (0x00000002)
#define OPTION_THREE (0x00000004)

but you will not be able to evaluate OPTION_ONE in the debugger because there is no OPTION_ONE symbol in the PDB. The preprocessor substituted OPTION_ONE with 0x0000001 before the linker was ever involved. To look up the value of OPTION_ONE you would have to open up a source file. Instead, I do the following when I have bit field values that I want to define and use within my app/driver:

 #include <windows.h>

typedef enum _OPTION_FLAGS {
    OptionOne =   0x00000001,
    OptionTwo =   0x00000002,
    OptionThree = 0x00000004,
} OPTION_FLAGS;

typedef union _OPTIONS {
    ULONG Bits;

    // NOTE:  this is for debugging only, *always* use Bits in code
    struct {
        ULONG One : 1;   // OptionOne
        ULONG Two : 1;   // OptionTwo
        ULONG Three: 1;  // OptionThree
    } BitsByName;
} OPTIONS;

int _cdecl main(int argc, char *argv[])
{
    OPTIONS o;

    o.Bits = OptionTwo;

    return 0;
}

For now, let's ignore the fact that I can use protected or public to abstract how the values are set and read and that I am using an unnamed union. (Some folks feel that unnamed unions are unholy, but I find them very practical in this pattern.) There are 2 key concepts to what I am doing here:

  1. The enumeration remains in the symbols so you can dump it within the debugger [1]. Now you don't have to look up the value in a source file.
  2. By creating a union of the true value (Bits) [3] and a bit field of the value (BitsByName) [4] I can see what each bit means without having to look at the enum at all! This means that you can see the value without digging into the structure at all if you don't need to [2].
 I have broken into the debugger right before returning.

[1] 0:000> dt OptionsFlags
   OptionOne = 1
   OptionTwo = 2
   OptionThree= 4

[2] 0:000> dt o Bits
Local var @ 0x6ff78 Type Options
   +0x000 Bits : 2

[3] 0:000> dt o BitsByName.
Local var @ 0x6ff78 Type Options
   +0x000 BitsByName  :
      +0x000 One         : 0y0
      +0x000 Two         : 0y1
      +0x000 Three       : 0y0

[4] 0:000> dt o
Local var @ 0x6ff78 Type Options
   +0x000 Bits             : 2
   +0x000 BitsByName       : Options::<unnamed-tag>

March 28, 2006: Updated the enum and union definitions to be C compliant and match my style guidelines.