Microcontroller Embedded C Programming Lecture 181| Pre-Processor directives in ‘C’

  • Post author:
  • Post category:Blog

 

Pre-Processor directives in ‘C’

 

Pre-Processor directives

  • In C programming pre-processor directives are used to affect compile time settings of the program. 
  • Pre-processor directives are also used to create macros used as a textual replacement for numbers and other things.
  • Pre-processor directives begin with the ‘#’ symbol.

For example, #include, #define, #if, #else if like that. So, those are different types of pre-processor directives supported by the ‘C’ programming language.

So, in all your program you used these pre-processor directives, at least you used #include. So, #include is one type of pre-processor directive, which instructs the compiler to do the file inclusion. 

  • Pre-processor directives are resolved or taken care during the pre-processing stage of compilation.

And pre-processor directives direct the compiler to take certain actions during compile time.

 

Pre-processor directives supported in ‘C’ programming language

Figure 1. Pre-processor directives supported in 'C'
Figure 1. Pre-processor directives supported in ‘C’

We most commonly use pre-processor directives while we write macros and when we want to do file inclusion.

 

Macros

If you want to write macros in your program, then you have to use #define. 

Macros are nothing but they are just a textual replacement for numbers and other things. #define is one type of pre-processor directive and it is used to write macros in ‘C’. 

#define Directive:

 

File inclusion

#include Directive:

  • #include is another type of pre-processor directive supported by ‘C’, which is used for file inclusion. A file may be a header file or any other file you can include a standard library header file or user-defined file etc.
  • Example: #include <stdio.h>

 

Conditional compilation

#ifdef, #endif, #if, #else, #ifndef, #undef  Directives:

  • These directives are used for conditional compilation. They allow you to include or exclude blocks of code based on whether certain macros are defined.
  • These are used to direct the compiler about code compilation. 

And there are other pre-processor directives which are used for conditional compilation. 

 

Other

And there are other pre-processor directives such as #pragma, #warning, #line and #error, etc.

 

 

Macros in ‘C’

  • Macros are written in ‘C’ using the #define pre-processor directive.
  • Macros are used for textual replacement in the code, commonly used to define constants.
  • Syntax: #define <Identifier> <value>  

please note that there is a space between the pre-processor directive and the identifier and there is a space between the identifier and a value.

  • Example: #define MAX_RECORD 10

 

 

Now let’s consider one simple use case.

Figure 2. Use case
Figure 2. Use case

if(age<18) this expression variable is compared with a constant.

if(age< MIN AGE) this expression is written here using a macro, and MIN_AGE 18 this macro is defined here #define MIN_AGE 18. 

So, during the pre-processing stage of the compilation, the identifier MIN_AGE will be replaced by number 18 by the pre-processing engine of the compiler.

 

Figure 3. Use case
Figure 3. Use case

Note: During the pre-processing stage of the compilation process, macro names or identifiers are replaced by the associated values inside the program

And you should also note that MIN_AGE is not a variable. This is an identifier(macro name). That is just a textual replacement for a number. 

 

In Embedded system programming, we use lots of ‘C’ macros to define pin numbers, pin values, crystal speed, peripheral register addresses, memory addresses and for various other configuration values.

These are some examples, as shown in Figure 4.

Figure 4. Examples
Figure 4. Examples

Here macro names are written using uppercase letters. That is a convention. 

You can either use capital letters or lowercase letters, it doesn’t matter actually. But we usually use capital letters to distinguish between a variable name and a macro. Because variable names we don’t write in capital letters. So, we always use a combination of capital and small letters or something like that.

If you use all capital letters for the macros, then you can easily distinguish between a variable and a macro name. 

 

And you should also note that there is no semicolon to terminate the statement”. You should not give any semicolons. 

Example: #define XTAL_SPEED 8000000UL

Here, the crystal speed is 8 mega, and it is terminated with the letter UL. UL means unsigned long, so that is allowed. You can use those characters. So, with the number, tell the compiler that this number is of type unsigned long. So, the compiler considers this macro as an unsigned long value.

 

function-like-macros

To define a function-like macro, use the same ‘#define‘ directive, but put a pair of parentheses immediately after the macro name.  

Pre-Processor directives in 'C'
Figure 5. Example

Here #define PI_VALUE 3.1415 is a normal macro definition.

#define AREA_OF_CIRCLE(r) PI_VALUE * r * r is a function like macro. 

AREA_OF_CIRCLE(r) is a macro name and PI_VALUE * r * r is a value.

Here that is a function-like macro, because we have used parentheses and that is one argument ‘r’, and that ‘r’ is used for the expression of the value.

 

The pre-processor directives in this code are #define statements. 

The first statement, #define PI_VALUE 3.1415, creates a macro that substitutes the symbol “PI_VALUE” with the value 3.1415 whenever it appears in the code. 

The second statement, #define AREA_OF_CIRCLE(r) PI_VALUE * r * r, creates a macro that calculates the area of a circle when given the radius “r”.

In the original “C” statement, areaCircle is assigned the value of the macro AREA_OF_CIRCLE with the argument “radius”. 

However, the pre-processor will replace AREA_OF_CIRCLE with its definition (PI_VALUE * r * r) and substitute “radius” for “r”. This results in the processed statement:

areaCircle = PI_VALUE * radius * radius;

The pre-processor will then replace PI_VALUE with its value, 3.1415, leading to the final statement:

areaCircle = 3.1415 * radius * radius;

 

This is very important. 

#define AREA_OF_CIRCLE(r) PI_VALUE * r * r this macro is poorly written and it is dangerous. You should not write a function like macro like this, as shown in Figure 6.

Pre-Processor directives in 'C'
Figure 6. Example

Why is that? 

You have to be careful with macro ‘values’ when you are doing some “operations” using multiple “operands”.

Here PI_VALUE * r* r is a macro value.

Consider PI_VALUE * r* r value. This value is a combination of operators and operands. So, when the value is a combination of operands and operators you have to be careful. You should not write something like this.

 

Now let’s see why it is bad. So, we will write one small piece of code to understand why exactly this is poorly written and how it is dangerous.

Write a program to calculate the area of a circle. I create a variable float area_circle. And I’m going to use a function like macro.

Figure 7. Code
Figure 7. Code

The first two lines define two macros: PI_VALUE and AREA_OF_CIRCLE.

#define PI_VALUE 3.1415f

This line defines the macro PI_VALUE as a float type constant with a value of 3.1415. I consider 3.1415 as a float. You can give a Capital ‘F’ or a small ‘f’ here. It tells the compiler that this is a ‘float’ value, otherwise, the compiler will treat this as a double. 

 

#define AREA_OF_CIRCLE(x) PI_VALUE * x * x

This line defines the macro AREA_OF_CIRCLE that calculates the area of a circle based on the given radius ‘x’. The formula used is the value of PI_VALUE multiplied by the radius squared.

 

In the main function:

float area_circle;

This line declares a variable area_circle to store the calculated area.

area_circle = AREA_OF_CIRCLE(2);

This line calls the macro AREA_OF_CIRCLE with a value of 2 for the radius, which calculates the area of a circle with a radius of 2 and stores the result in area_circle.

printf(“Area = %f\n”, area_circle);

This line prints the result stored in area_circle to the console. The %f format specifier is used to print a float value.

So, you got the answer 12.566. That’s correct, as shown in Figure 7.

 

Here, instead of passing 2, I pass 1 + 1. People may use your macro in various ways. So, you never know how the user is going to use your macro.

The answer changes, as shown in Figure 8. That means something is wrong with the macro.

Pre-Processor directives in 'C'
Figure 8. Code

What happens when we do this? There is a replacement. AREA_OF_CIRCLE(1+1) is replaced by PI_VALUE * x * x. 

 

So, let’s do that.

Here, x is replaced by 1+1, and PI_VALUE is replaced by 3.1415f. So, the final code looks something like this, as shown in Figure 9.

Pre-Processor directives in 'C'
Figure 9. Code

Here 3.1415f * 1+1 * 1+1 this expression will be evaluated according to the precedents rule. That’s why the result that you get here is an error. Area = 5.141500 is a wrong result. 

So, that’s what I wanted to say “Never write values like this for a macro” (Figure 6). 

 

You write something like this, as shown in Figure 10. You have to use parentheses generously. So, use parentheses for every operand.  

Figure 10. Example
Figure 10. Example

This is the correct and safest method.

 

Let’s do that. Here I use parentheses. This is the best way to write a macro. Let’s check this. You get the correct answer, as shown in Figure 11.

Pre-Processor directives in 'C'
Figure 11. Code

 

Best practices while writing macros in ‘C’

  1. Use meaningful macro names
  2. It’s recommended that you use UPPER case letters for macro names to distinguish them from variables. 
  3. Remember, macro’s names are not variables. They are labels or identifiers, and they don’t consume any code space or ram space during compile time or runtime of the program.
  4. Make sure that parentheses surround the macro value. 
  5. While using function-like macros or when you are using macros along with any operators, always surround the operands with parentheses. 

In the following article, let’s learn about conditional compilation using pre-processor directives. 

 

FastBit Embedded Brain Academy Courses

Click here: https://fastbitlab.com/course1

 

FastBitLab

The FastBit Embedded Brain Academy uses the power of internet to bring the online courses related to the field of embedded system programming, Real time operating system, Embedded Linux systems, etc at your finger tip with very low cost. Backed with strong experience of industry, we have produced lots of courses with the customer enrolment over 3000+ across 100+ countries.