Home | Projects | Notes > C Programming > Preprocessor Directives
In C programming preprocessor directives are used to affect compile-time settings.
Preprocessor directives are also used to create macros used as a textual replacement for numbers and other things.
Preprocessor directives begin with the # symbol.
Preprocessor directives are resolved or taken care of during the preprocessing stage of compilation.
Preprocessor Directives Supported in C:
| Objective | Usage | Syntax |
|---|---|---|
| Macros | Used for textual replacement | #define <identifier> <value> |
| File inclusion | Used for file inclusion | #include <std_lib_filename> #include "user_defined_filename" |
| Conditional compilation | Used to direct the compiler about code compilation | #ifdef, #endif, #if, #else, #ifndef, #undef |
| Others | #error, #pragma |
#define)Macros are written in C using #define preprocessor directive.
Macros are used for textual replacement in the code, commonly used to define constants.
Syntax:
xxxxxxxxxx11/* note that there is NO ';' at the end */Example:
xxxxxxxxxx11During the preprocessing stage of the compilation process, macro names (identifiers; NOT variable names) are replaced by the associated values inside the program.
In embedded systems programming (in C), macros are frequently used to define pin numbers, pin values, crystal speed, peripheral register addresses, memory addresses and for other configuration values.
For example,
xxxxxxxxxx712345/* UL stands for unsigned long */67By convention, macro names are defined in uppercase letters to distinguish them from the variable names which are generally defined using camel-case or snake-case.
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. For example:
xxxxxxxxxx4123
4areaCircle = AREA_OF_CIRCLE(radius); /* original statement */Preprocessor first perform the textual replacement to yield
xxxxxxxxxx11areaCircle = PI_VALUE * radius * radius; /* textual replacement step 1 */then,
xxxxxxxxxx11areaCircle = 3.1415 * radius * radius; /* textual replacement step 2 */Be extra careful with the macro values (i.e., the combination of operands and operators) when you are doing some operations using multiple operands. Safety measure is to put parentheses around each and every operand, plus the whole expression. Imagine what could possibly happen without! The following is the correct format:
xxxxxxxxxx11Summary
Use meaningful macro names
It is recommended that you use UPPERCASE letters for macro names to distinguish them from variables.
Macro names are NOT variables. They are labels or identifiers, and they don't occupy any code space or RAM space during the compile-time or run-time of the program.
Make sure to put parentheses around each and every operand, plus the whole expression.
Toggling LED program can be abstracted one step further by applying macros.
Before (without macros):
xxxxxxxxxx301// toggle_led.c2
3int main(void)4{5 RCC_AHB1ENR_t volatile *const pClkCtrlReg = (RCC_AHB1ENR_t *)0x40023830;6 GPIOx_MODER_t volatile *const pPortDModeReg = (GPIOx_MODER_t *)0x40020C00;7 GPIOx_ODR_t volatile *const pPortDOutReg = (GPIOx_ODR_t *)0x40020C14;8
9 // 1. enable the clock for GPOID peripheral in the AHB1ENR (SET the 3rd bit position)10 pClkCtrlReg->gpiod_en = 1;11
12 // 2. configure the mode of the IO pin as output13 // make 24th bit position as 1 (SET)14 pPortDModeReg->pin_12 = 1;15
16 while(1)17 {18 // 3.SET 12th bit of the output data register to make I/O pin-12 as HIGH19 pPortDOutReg->pin_12 = 1;20
21 // introduce small human observable delay22 // This loop executes for 10K times23 for(uint32_t i=0 ; i < 300000 ; i++ );24
25 // Tun OFF the LED26 pPortDOutReg->pin_12 = 0;27
28 for(uint32_t i=0 ; i < 300000 ; i++ );29 }30}After (with macros):
xxxxxxxxxx131// toggle_led.h2
3// macros4567
89101112
13// unsigned long 32-bit valuexxxxxxxxxx301// toggle_led.c2
3int main(void)4{5 RCC_AHB1ENR_t volatile *const pClkCtrlReg = ADDR_REG_AHB1ENR;6 GPIOx_MODER_t volatile *const pPortDModeReg = ADDR_REG_GPIOD_MODE;7 GPIOx_ODR_t volatile *const pPortDOutReg = ADDR_REG_GPIOD_OD;8
9 // 1. enable the clock for GPOID peripheral in the AHB1ENR (SET the 3rd bit position)10 pClkCtrlReg->gpiod_en = CLOCK_ENABLE;11
12 // 2. configure the mode of the IO pin as output13 // make 24th bit position as 1 (SET)14 pPortDModeReg->pin_12 = MODE_CONF_OUTPUT;15
16 while(1)17 {18 // 3.SET 12th bit of the output data register to make I/O pin-12 as HIGH19 pPortDOutReg->pin_12 = PIN_STATE_HIGH;20
21 // introduce small human observable delay22 // This loop executes for 10K times23 for(uint32_t i=0 ; i < DELAY_COUNT ; i++ );24
25 // Tun OFF the LED26 pPortDOutReg->pin_12 = PIN_STATE_LOW;27
28 for(uint32_t i=0 ; i < DELAY_COUNT ; i++ );29 }30}Now, the program has become more intuitive to read!
Conditional compilation directives (e.g., #if, #ifdef, #endif, #else, elif, #undef, #ifndef) help include or exclude individual code blocks based on various conditions set in the program.
#if and #endif DirectivesSyntax:
xxxxxxxxxx31/* e.g., #if 0 */2 /* code block */3This directive checks whether the constant expression is zero or non-zero value. If constant is 0, then the code block will NOT be included for the code compilation. If constant is non-zero, the code block will be included for the code compilation.
#endif directive marks the end of scope of #if, #ifdef, #ifndef, #else, #elif directives.
These directives can be nested:
xxxxxxxxxx612 /* code to be included */34 /* code to be excluded */56Also, conditional inclusion can be achieved by using #else:
xxxxxxxxxx512 /* code block 1 */34 /* code block 2 */5By setting the constant expression value of #if, we can include either the code block 1 or 2.
#ifdef and #ifndef DirectivesSyntax:
xxxxxxxxxx31/* e.g., #ifdef NEW_FEATURE */2 /* code block */3#ifdef directive checks whether the identifier is defined in the program or not. Only if the identifier is defined, the code block will be included for compilation.
Exercise:
Execute triangle area calculation code block only if AREA_TRI macro is defined in the program.
Execute circle area calculation code block only if AREA_TRI is not defined.
xxxxxxxxxx2012
3/* AREA_TRI, AREA_CIR can be defined/undefined here.4#define AREA_TRI5#define AREA_CIR6#undef AREA_CIR7*/8
9int main(int argc, char *argv[])10{1112 /* code block to calculate triangle area */1314
1516 /* code block to calculate circle area */1718 19 return 0;20}Note that instead of including #define AREA_TRI in the code, you can also pass AREA_TRI as an argument to the compiler using -D flag when compiling this program. e.g., gcc -DAREA_TRI <filename>.
#ifdef and #else DirectivesSyntax and example:
xxxxxxxxxx512 /* code block of new feature */34 /* code block for old feature */5#if Directive and defined OperatorThe defined operator is used when you want to check definitions of multiple macros using single #if, #ifdef or #ifndef directives.
You can also use C logical operators such as AND, NOT, OR with defined operator. For example:
xxxxxxxxxx512 3 /* code block to be included only if both AREA_CIR and AREA_TRI are defined */4 5can be rewritten as:
xxxxxxxxxx312 /* code block to be included only if both AREA_CIR and AREA_TRI are defined */3#error Directiv#error terminates the compilation with error message:
xxxxxxxxxx312 3This code can be placed anywhere necessary in the program. (e.g., Outside the main(), inside the main(), etc.)
#warning does NOT terminate the compilation but does give the warning message:
xxxxxxxxxx312 3
Nayak, K. (2022). Microcontroller Embedded C Programming: Absolute Beginners [Video file]. Retrieved from https://www.udemy.com/course/microcontroller-embedded-c-programming/