Skip to main content
Engineering LibreTexts

4.6: define

  • Page ID
  • Very often it is desirable to use symbolic constants in place of actual values. For example, you’d probably prefer to use a symbol such as PI instead of the number 3.14159. You can do this with the #define preprocessor directive. These are normally found in header files (such as stdio.h or math.h) or at the top of a module of C source code. You might see something like:

    #define PI 3.14159

    Once the compiler sees this, every time it comes across the token PI it will replace it with the value 3.14159. This directive uses a simple substitution but you can do many more complicated things than this. For example, you can also create something that looks like a function:

    #define parallel((x),(y))     ((x)*(y))/((x)+(y))

    The x and y serve as placeholders. Thus, the line

    a = parallel( b, c );

    gets expanded to:

    a = (a*b)/(a+b);

    Why do this? Because it’s an in-line expansion or macro. That means that there’s no function call overhead and the operation runs faster. At the same time, it reads like a function, so it’s easier for a programmer to follow. OK, but why all the extra parentheses? The reason is because x and y are placeholders, and those items might be expressions, not simple variables. If you did it this way you might get in trouble:

    #define parallel(x,y)   x*y/(x+y)

    What if x is an expression, as in the following example?

    a = parallel(2+b,c);

    This would expand to:

    a = 2+b*c/(2+b+c);

    As multiplication is executed before addition, you wind up with 2 being added to the product of b times c after the division, which is not the same as the sum of 2 and b being multiplied by c, and that quantity then being divided. By using the extra parentheses, the order of execution is maintained.

    Referring back to the bit field operations, here are some useful definitions for what appear to be functions but which are, in fact, bitwise operations expanded in-line:

    #define bitRead(value, bit)    (((value) >> (bit)) & 0x01)
    #define bitSet(value, bit)     ((value) |= (1UL << (bit)))
    #define bitClear(value, bit)   ((value) &= ~(1UL << (bit)))

    The 1UL simply means 1 expressed as an unsigned long. Finally, bit could also be defined as a symbol which leads to some nice looking self-documenting code:

    #define LEDBIT 7
    // more code here...
    bitSet( DDR, LEDBIT );

    #define expansions can get quite tricky because they can have nested references. This means that one #define may contain within it a symbol which is itself a #define. Following these can be a little tedious at times but ultimately are worth the effort. We shall look at a few down the road. Remember, these are done to make day-to-day programming easier, not to obfuscate the code. For now, start with simple math constant substitutions. They are extremely useful and easy to use. Just keep in the back of your mind that, with microcontrollers, specific registers and ports are often given symbolic names such as PORTB so that you don't have to remember the precise numeric addresses of them. The norm is to place these symbolic constants in ALL CAPS.

    • Was this article helpful?