Embedded C: Preprocessor

First step during the completion of c program is preprocessing, where in the preprocessor modifies the code according based on use input for further compilation process. Preprocessors enables developers to write efficient software’s. Below I have tried to explain how preprocessors can be sued in real time coding, capabilities and syntax.

#include

Well to start with, everyone should by default used #include directive in their code. Every file begins with the series of #include, this instructs the preprocessor to import header files/libraries into your code.

#include “stdio.h”

When preprocessor sees this directive, it copies the content of stdio.h file and places the same at the location of directive. Usually *.h files/libraries will contain the function implementation, variable references from other files that will be accessed by the file where it is included.

Correct Syntax:
#include “stdio.h”
Error syntax:

This is directive, not a statement hence semicolon is not required, and also its should be declared in single line

  • #include “stdio.h” ; //semi colon shouldn’t be used
  • #include   
    "stdio.h"               // should be written in single line

#define

Another preprocessor directive mostly used is #define, its more used for value assignment. More like how the search and replace function works in Microsoft word.

#define expression replacement

#define factor 10
Int main()
{
                Int x = factor / 10;
}

In above example during preprocessing, factor will be replaced with 10. If in a program, same value/character/string needs to be used multiple locations, its advisable to use #define because if value/character/string has to be changed now you can change this @ one place i.e #define XXX xxx and it gets reflected in all other places during preprocessing.

#define USDcurrencyconversionfactor 62.5
int main()
{
    int INRValue = 100;
    int USDValue = INRValue * USDcurrencyconversionfactor;

     printf(“USD value of INR %d is %d”,INRValue,USDValue);

     INRValue = USDValue/ USDcurrencyconversionfactor;
     printf(“INR value of USD %d is %d”,USDValue,INRValue);
}

In above example, if the conversion factor gets changed in future. Just change “replacement”  with new value, and this gets updated automatically wherever “expression” is referenced in the code.

#define USDcurrencyconversionfactor 65.2

Correct Syntax:
#define expression replacement
Error syntax:
  • it’s a directive, hence semicolon is not required
#define value 10;  
int main()
{
    int I = value * 10;  //this will throw compilation error
}

int I = 10; * 10; //when expression gets replaces causes compilation error
  • if the intention below is to replace first name, it will be wrong because preprocessor  replaces first with name David. So always avoid spaces in expression.
#define first name David //wrong
#define firstname David //correct
  • Due to operator precedence, not using #define correctly might alter the mathematical computation. in below example expected i & j should be value 8, but its not the case
#define MAX(a,b) a>b?a:b
int main()
{
     int i = MAX(2,3)+5;
     int j = MAX(3,2)+5;
}
int i = 2>3?2:3+5;  //expression replaced during prepossessing 
int j = 3>2?3:2+5;  //expression replaced during prepossessing 

#define MAX(a,b) ((a)>(b)?(a):(b)) //correct way of defining 

 #if, #elif, #else, and #endif

Like how the if and else is used across the program, to include and exclude part of the code (flow control), preprocessor can use a set of preprocessor directives to conditionally include or exclude a section of code based on #defined values. Syntax remains pretty much same as if, else statements provided by C. Also rules remain same, #elif or #else should have prior #if defined. #if should have associated #endif

#if statement

#elif statement

#else

#endif

Example:  in below example, if one is defined then preprocessor evaluates the of condition and only included first printf  in the final code for compilation and so on.

#define one
#if one
    printf(“One is defined”);
#elif two
    printf(“Two is defined”);
#else
    printf(“One & Two not defined”);
#endif

Correct syntax

Preprocessor directives can only refer to #defined constants, integers, arithmetic and logical expressions of those values.

#if Value > 100

#if value * 1 == value

#if !value
Error syntax
  • Calling functions are prohibited
#if sqrt(value) > 10
  • Only interger values should be used, using decimal is illegal
#if Value == 1.30

Macros

Macros are the most complex ones, which can be sued to create compile time function which can accepts parameters and generate outputs. Like always, when a preprocessor encounters a macro it will replace the text with macro text. Consider below macro definition, INT acts as a function call where it accepts argument and increments the value of argument.

#define INC(x) ((x) + 1)

If examined closely, you can see that macro cant return. Only way you can get the return value is through assignment. Refer example below.

#define INC(x) ((x) + 1)
int main()
{
    int x = 10;
    x = INC(x);
}
Disadvantage & limitations

Although there are advantages of using macro, below I have tried to explain the limitations.

#define ABS(x) ((x) < 0 ? –(x) : (x))
int main()
{
       int x = 5;
       int result = ABS(x++);
       printf("RESULT: %d\n", result);
       printf("X IS: %d\n", x);
}

Expected value of x is 6, but it will be 7. Its all because how the text will be replaced,  refer below. X is incremented twice, once during checking in ternary operator and once during assignment

ABS(x++) —> ((x++) < 0 ? -(x++) : (x++))

Typical scenarios/use case where the preprocessors are effective

Debug messages on/off

During development, one might need printf’s to print out the debug messages, this comes handy for troubleshooting and understanding the code flow. But these debug messages should be disabled in final code, this can be easily done with #define. Below once can #define debug, and use conditional preprocessor directive to condition compile, to omit the printf or not based on coder requirement.

#define debug
…..
……
…..
# ifdef debug
    printf(“Debug message”);
#endif
Multiple same file inclusion

In large and complex SW programs, where the huge file numbers are present. There could be chances that we include same header file twice, which leads to compilation error. This can be avoided by using #define, example below

header.h

int a = 100;
int y = 200;

test.c

#include “header.h”
#include “header.h”
int main()
{
   retrun;
}

after preprocessing

test.c

int a = 100;
int y = 200;
int a = 100;
int y = 200;
int main()
{
   retrun;
}
This leads to compilation error, because same variable is declared twice

This can be overcome by using preprocessor directive like below

header.h

#ifndef include_header
#define include_header

int a = 100;
int y= 200;

#endif

When the preprocessing happens, for the very first time since the “include_header” is not defined, content below will be replaced, and for the second time since “include_header” will be defined, replacement will be skipped.

 

Advertisements


Categories: programming

Tags: , , ,

%d bloggers like this: