Everything for Electronics

C Preprocessor Reduces Debug Headaches

C Preprocessor Reduces Debug Headaches

By Jon Titus    View In Digital Edition  


In my experience, few hobbyists or experimenters take advantage of C preprocessor operations. You’ve probably seen preprocessor directives such as #include <stdio.h> or #include “servo.h” among the first lines in programs. However, they are not part of the C language. When you compile a program, it first goes through a preprocessor that handles housekeeping tasks such as including files that operate a peripheral or that provide a function such as printf.

The preprocessor “dictionary” lists many directives that help with test and debug tasks, among others. As an added benefit, you may include debug operations in your programs and enable or disable them as you choose. You no longer need to find and remove code added solely as debug or test tools. (For more information about the preprocessor directives, see Resources.)

I often put several LEDs on unused MCU (microcontroller) outputs to indicate that code execution reached — or didn’t reach — certain instructions. When the program finally worked properly, I went through the code and removed — or commented-out — all the LED control instructions. That’s a pain! Plus, it’s easy to forget where I put all the LED statements or how I used them.

Define Substitution Statements

I’ll start this tutorial with a simple example of how the #define directive works:

#define  alpha   beta

When you include the directive above at the start of a program, it causes the preprocessor to substitute the text “beta” whenever it finds the text “alpha” in the program. Only the compiler sees the substitution; we don’t. 

Suppose you define the text “ONE_PI” and its replacement, the text 3.14159 as shown here:

#define ONE_PI  3.1416        / Just pi

This #define directive does not create a constant nor does it create a variable and assign it a value. It simply tells the preprocessor, “Find every instance of ONE_PI in the program and replace it with 3.14159.”
Then, in a C statement such as:

circ = ONE_PI * dia;

the preprocessor makes the replacement and gives the C compiler the statement that follows, although we don’t see it this way in a listing:

circ = 3.1416 * dia;

Here’s a code snippet that shows how to use the ONE_PI substitution in practice:

#define ONE_PI  3.1416      / Just a text substitution
float circ;          / Circle circumference
float dia = 11;          / Circle diameter
int main ()
{
   circ = ONE_PI * dia;
   print(“Circumference = %f \n” circ);
     /Print answer
}

Remember, the directive performs only a text substitution.

Debug with the Preprocessor

Although you may put preprocessor directives anywhere in your code, I recommend you place them at the program’s start, prior to the main.c code. That approach makes them easy to find and ensures you have defined a text substitution before you try to use it in a program.

For the examples that follow, I used a Parallax Propeller QuickStart board and the Parallax SimpleIDE software for C programming. On a breadboard, I connected I/O pin P16 to a red LED. A high(16) command turns the red LED on and a low(16) turns it off. (Instead of controlling an LED with an output pin, you could use a pin’s signal to trigger a logic analyzer or an oscilloscope.) The next code snippet shows how I used a directive to define RED_LED_ON as high(16). You could use any similar descriptive name; perhaps DBUG_LED_RED. The preprocessor substitutes high(16) for every RED_LED_ON here:

#include  “simpletools.h”   /Parallax C libraries
#define RED_LED_ON  high(16) /Definition

int main()                 /Main program
{
print(“Hello!”);           /Print statement
RED_LED_ON;                /Turn on red debug LED
}

This program compiled without errors, printed “Hello!” on the SimpleIDE terminal, and turned on the red LED.  That test worked, but now I no longer need to turn the LED on after the print statement. I can “disable” the LED in one of three ways:

  1. Delete the RED_LED_ON; statements in the main program. If I need to use it again, I might forget I put it here, though; or
  2. Comment out the RED_LED_ON; statement in the main program. That way, I remember where I controlled the LED; or
  3. Remove only the high(16) command from the #define directive to leave #define RED_LED_ON.

In the third case, the preprocessor replaces the RED_LED_ON; statement in my C code with “nothing.” The compiler will never “see” RED_LED_ON; thus, it compiles the code as if RED_LED_ON doesn’t exist. In other words, you can leave RED_LED_ON; statements in your C code; they simply become inactive.

If you must go back and debug again, the RED_LED_ON; statements remain in your program. You can re-enable them when you restore the preprocessor directive to:

#define RED_LED_ON  high(16)

That’s the beauty of using the preprocessor directives for debugging. You can leave debug operations in a program and “enable” or “disable” them as needed via simple changes to the preprocessor directives.

You no longer must hunt through your code and remove all the LED control or other debug functions before you release a final version of your software.

Of course, you could use a search-and-replacement operation to remove all RED_LED_ON statements, but if you must debug or test a program again, you might not remember where you had the LED control instructions and what they indicated when you ran the last tests.

Important: DO NOT comment-out the entire #define RED_LED_ON high(16) directive. If you do, the compiler displays an error message because it will find the RED_LED_ON statement in your main program and tell you it never got defined!

I often use preprocessor directives to enable and disable LEDs or I/O pins used for tests as my programs evolve. Two directives in my software simplify LED control.

The statements in Example 1 substitute high(16) for RED_LED_ON later in my program. On the other, the statements in Example 2 substitute “nothing” for RED_LED_ON, so the LED control statements have no effect. 

If I have six or seven debug LEDs and perhaps a logic analyzer, I can control them individually as needed for tests.

Example 1.

#define RED_LED_ON   high(16)  /For debug purposes
//#define RED_LED_ON           /use the red LED

Example 2.

//#define RED_LED_ON  high(16)   /For final code
#define RED_LED_ON       /do not use the red LED

You can take another approach that lets the preprocessor enable or disable groups of I/O pins.

Use Conditional Debugging

The preprocessor can handle conditional directives that are similar to the if-else statements in C. For the following example, I put a yellow and a green LED on my breadboard at MCU pins P17 and P18, respectively. I then added the following lines for the preprocessor at the start of a program:

#define DBUG                     /Define DBUG.
#ifdef DBUG                     /Debug is defined,
  #define RED_LED_ON  high(16)   /so allow use
  #define YELLOW_LED_ON high(17) /of the LED
  #define GREEN_LED_ON  high(18) /statements.
#endif                        /End of if section.

The first line defines DBUG, which simply tells the preprocessor that DBUG now exists. There’s nothing special about the name DBUG, and you may substitute another name if you wish.

Next, the #ifdef directive determines whether the preprocessor has defined DBUG. In this example, it has. So, the #ifdef statement evaluates as true, and the preprocessor executes the indented directives that define high(16) as the replacement text for RED_LED_ON, high(17) as the replacement text for YELLOW_LED_ON, and so on.

The following snippet of code shows how you might use the red and green LEDs:

RED_LED_ON;  /Red LED on when execution gets here
while (alpha > beta)
   {
     do this..;
     do other things..;
     beta++;
   }
GREEN_LED_ON; /Green LED on at end of while loop

The red LED should turn on just before the MCU’s processor reaches the while command. The green LED will turn on to show completion of the while loop. If the green LED doesn’t light, we likely have a problem in the while loop. We could put a YELLOW_LED_ON statement within the loop to help us find incorrect instructions or variables.

After we fix any bugs and the program behaves properly, how can we disable all three LED-on statements? We don’t need them in the final code. Again, do not comment-out the #define DBUG directive. As explained earlier, you’ll get an error message.

Instead, we add directives that take effect only when we don’t define DBUG, as shown in the next code snippet. In this case, I may now safely comment-out the first directive: //#define DBUG. Then, with DBUG not defined, the #ifdef DBUG directive evaluates as false, so the second set of directives after the #else take effect.

The directives in bold in the following code define the LED control words, but they assign “nothing” to each.  Now, the preprocessor ignores the corresponding statements in the C code:

//#define DBUG              /Commented out, don’t
                           /define DBUG        
#ifdef DBUG                /If DEBUG is defined...
    #define RED_LED_ON    high(16)   / continue.
    #define YELLOW_LED_ON high(17)
    #define GREEN_LED_ON  high(18)
#else                      /If DBUG is not defined
    #define RED_LED_ON      /substitute “nothing”
    #define YELLOW_LED_ON   /for the LED commands
    #define GREEN_LED_ON
#endif

Because the #define operation substitutes one section of text for another prior to compilation of a program, you may create detailed substitutions such as:

#define PRINT_TEST  print(“Test Data %d \n”, XYZ)

Then, you assign the variable XYZ a value and insert PRINT_TEST; in the program. When you run the program, it prints the text “Test Data” followed by the value assigned to variable XYZ (you must define a data type earlier; in this case, an integer for XYZ):

total = weight * units;
     /Calculate total weight
XYZ = total;           
     /assign total value to XYZ
PRINT_TEST;            
     /print test statement

You can enable or disable the PRINT_TEST directive as needed.

Write Multiline Macros

Preprocessor macros (several lines of code in a contiguous block) may include complete C functions; perhaps to calculate an answer or to control an external device as you test and debug a program. Again, the preprocessor replaces the defined text name with the lines of C code text.

Why wouldn’t programmers put such tasks in a normal C function? They might. There’s no reason to create a macro if library functions will work. Some library functions — think of printf, for example — consume large quantities of memory. You don’t want to include them in an embedded MCU’s memory just for debugging. Why use printf for debugging when an MCU includes a basic UART that might do the job? 

Aim for simplicity in your programs and in your test and debug toolbox. Also, you might create macros for specific tasks and reuse them in other software or keep them proprietary. (Frankly, to minimize errors, we want to modify our application code as little as possible.)

It’s easy to create one macro and then have the preprocessor insert it — or not — as required. If you disable the macro, it’s still available if you need it at another time.

As an example of a macro, I created one to flash an LED on a Propeller QuickStart board 20 times (Figure 1). Note the use of a semicolon at the end of the macro name in the main program, and the lack of a semicolon at the end of the macro itself.

Figure 1. This multiline macro flashes an LED 20 times in a C program.


To use the LED_FLASH macro, just put the statement LED_FLASH; on a line in your software. The C language statements in the while-loop macro flash the LED at pin P16 20 times.

The preprocessor replaces LED_FLASH with the C code you created. The last brace } in the macro code doesn’t need a semicolon because one exists at the end of the LED_FLASH; statement in the main program.

This loop demonstrates how to create a macro, but keep in mind it causes the MCU processor to spend eight seconds flashing an LED before it moves on to the next instruction! Your application might not tolerate such a delay, even when you test it.

CAUTION: When you create a preprocessor C code macro, you must end each line with a backslash \ followed immediately by a “newline,” or a “return.” (Press the Enter key immediately after you type the backslash.) Do not leave whitespace after the backslash!

The compiler will produce a warning if spaces occur between the backslash and a new line. In the Propeller SimpleIDE, for example, the warning text reads in part:

“warning: backslash and newline separated by space [enabled by default]”

Remove any extra spaces and the warning disappears. Use semicolons to end statements in the macro just as you would in a C program. You may leave spaces between the semicolon and the backslash, though.

Construct Macros with Care

When you need a multiline macro, you must construct it carefully and consider what happens during the preprocessor’s substitution of the macro text for the macro name in a C program.

The format might seem complicated at first, but in practice, you’ll find it easy to implement. Bruce Blinn, a software developer, explains some of the traps on his website (see Resources). I’ll briefly describe several.

The following two-line macro should print a message and then an integer value, my_value:

#define MY_TEST \
  print(“This is a test! \n”); \
  print(“Value at this point %d \n”, my_value)\   /No semicolon!

Note there’s no semicolon at the end of the second print statement. When you use the two-line macro immediately above in an if statement such as:

my_value = 10;      /Value for a test run
if (counter == 0)   /if counter equals zero,
     MY_TEST;          /print my_value on terminal.

The preprocessor substitutes the macro’s text for MY_TEST; so, the if statement looks like this to the compiler:

my_value = 10;
if (counter == 0)
   print(“This is a test!” \n);
   print(“Value at this point %d \n”, my_value);
   /...here.   

When counter equals zero, you’ll see only “This is a test!” on a terminal. The second print statement never executes. Why?

The semicolon at the end of the first print statement terminates the if section of the C code (the semicolon at the end of the second print statement is the same semicolon that ended the macro name, MY_TEST;).

Take a look at a typical if statement that involves two operations:

my_value = 10;
if (counter == 0)
{
  code here..;
  more code here..;
}

Braces — { and } — enclose a block of code that will run when counter equals zero. A multiline macro must do something similar so all its lines execute properly no matter where you have the macro name in a program. Some programmers define multiline macros with a bottom-driven do-while loop such as this:

#define MY_TEST \
do{\
 printf(“This is a test.” \n); \
 printf(“Value at this point %d \n”, my_value); \
} while(0)

Now, we have the two lines of macro code enclosed in braces within a do-while loop. Because the second printf statement exists within the loop, now you must end it with a semicolon.

The while(0) condition lets the macro’s do loop run only once. When the preprocessor analyzes the statement:

my_value = 10;
if (counter == 0)
     MY_TEST;

it substitutes the new macro text and yields this intermediate result, although we don’t see it:

my_value = 10;
if (counter == 0)
   {
     printf(“This is a test.” \n);
     printf(“Value at this point %d \n”, my_value);
   }

According to Blinn, the current gcc [gnu compiler collection] version provides another method you can use in place of the do-loop, as shown in the following example. Simply put a parenthesis before the first brace and after the last. I find this method easy to remember. Test this technique with your C compiler:

#define MY_TEST \
 ({ \
 printf(“This is a test.” \n); \
 printf(“Value at this point %d \n”, my_value); \
 })

Make Friends with Parentheses

When you use a math operation in a macro, pay careful attention to how the preprocessor handles the expression or you can get odd results. Remember, the #define directive performs only a text substitution. Here’s a simple math macro that will multiply two values:

#define MULT(x, y)    x * y

What happens when you use the macro this way in a program:

int z = MULT(3 + 2, 4 + 2);

You expect that z equals 30, from (3 + 2) * (4 + 2) but instead you get 13. The #define directive doesn’t take into account math-operation precedence — the order in which math operations get done. As a result, the macro MULT substitution will look like this to the compiler:

int z = 3 + 2 * 4 + 2

Multiplication and division take precedence over addition and subtraction, so the 2 * 4 gets calculated first to yield 8, and then 3 and 2 get added to the result to give us 13.

Information on a programming website recommends programmers, “...force the arguments themselves to be evaluated before the rest of the macro body” (refer again to References). You accomplish this task by surrounding the arguments with parentheses in the macro definition:

#define MULT(x, y) ((x)*(y))

Then, MULT(3 + 2, 4 + 2) becomes ((3 + 2) * (4 + 2)) and you get the expected answer: 30. Parentheses are your friends.

Get Detailed Debug Data

The C preprocessor provides predefined macros such as __FILE__, __LINE__, __DATE__, and __TIME__. Two underscore characters precede and follow each macro name. The FILE and LINE macros come in handy because the former identifies the file in use, and the latter gives you the line number in that file. These two macros provide a helping hand in test and debug steps.

As an example, the macro that follows displays the name of the file in use and the line number in the code where you put this PRINT_OUT macro:

#define PRINT_OUT  printf(“File: %s at line %d \n”, __FILE__, __LINE__)

Insert the PRINT_OUT; macro in your code and it will give you information such as:

File: Blink_Light_1.c at line 36

Often, you’ll find this type of debug information more useful than a flashing LED because it identifies a specific file and execution at a particular line in a program.  NV


Resources

Information about the preprocessor directives and macros
https://en.wikibooks.org/wiki/C_Programming/Preprocessor_directives_and_macros

Information about the C preprocessor
https://en.wikipedia.org/wiki/C_preprocessor

Bruce Blinn’s descriptions of preprocessor macros
https://bruceblinn.com/

How improper arrangement of math operations can lead to unexpected results and how to avoid them
www.cprogramming.com/tutorial/cpreprocessor.html

Information about C and C++ predefined macros
https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html#Common-Predefined-Macros
https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html#Standard-Predefined-Macros

I recommend, “An Introduction to GCC for the GNU Compilers gcc and g++,” by Brian Gough, Network Theory Limited, Bristol, UK. 2005. ISBN: 9780954161798.




Comments