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:
xxxxxxxxxx
11/* note that there is NO ';' at the end */
Example:
xxxxxxxxxx
11
During 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,
xxxxxxxxxx
71
2
3
4
5/* UL stands for unsigned long */
6
7
By 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:
xxxxxxxxxx
41
2
3
4areaCircle = AREA_OF_CIRCLE(radius); /* original statement */
Preprocessor first perform the textual replacement to yield
xxxxxxxxxx
11areaCircle = PI_VALUE * radius * radius; /* textual replacement step 1 */
then,
xxxxxxxxxx
11areaCircle = 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:
xxxxxxxxxx
11
Summary
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):
xxxxxxxxxx
301// toggle_led.c
2
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 output
13 // 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 HIGH
19 pPortDOutReg->pin_12 = 1;
20
21 // introduce small human observable delay
22 // This loop executes for 10K times
23 for(uint32_t i=0 ; i < 300000 ; i++ );
24
25 // Tun OFF the LED
26 pPortDOutReg->pin_12 = 0;
27
28 for(uint32_t i=0 ; i < 300000 ; i++ );
29 }
30}
After (with macros):
xxxxxxxxxx
131// toggle_led.h
2
3// macros
4
5
6
7
8
9
10
11
12
13// unsigned long 32-bit value
xxxxxxxxxx
301// toggle_led.c
2
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 output
13 // 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 HIGH
19 pPortDOutReg->pin_12 = PIN_STATE_HIGH;
20
21 // introduce small human observable delay
22 // This loop executes for 10K times
23 for(uint32_t i=0 ; i < DELAY_COUNT ; i++ );
24
25 // Tun OFF the LED
26 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:
xxxxxxxxxx
31/* e.g., #if 0 */
2 /* code block */
3
This 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:
xxxxxxxxxx
61
2 /* code to be included */
3
4 /* code to be excluded */
5
6
Also, conditional inclusion can be achieved by using #else
:
xxxxxxxxxx
51
2 /* code block 1 */
3
4 /* code block 2 */
5
By setting the constant expression value of #if
, we can include either the code block 1 or 2.
#ifdef
and #ifndef
DirectivesSyntax:
xxxxxxxxxx
31/* 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.
xxxxxxxxxx
201
2
3/* AREA_TRI, AREA_CIR can be defined/undefined here.
4#define AREA_TRI
5#define AREA_CIR
6#undef AREA_CIR
7*/
8
9int main(int argc, char *argv[])
10{
11
12 /* code block to calculate triangle area */
13
14
15
16 /* code block to calculate circle area */
17
18
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:
xxxxxxxxxx
51
2 /* code block of new feature */
3
4 /* 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:
xxxxxxxxxx
51
2
3 /* code block to be included only if both AREA_CIR and AREA_TRI are defined */
4
5
can be rewritten as:
xxxxxxxxxx
31
2 /* 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:
xxxxxxxxxx
31
2
3
This 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:
xxxxxxxxxx
31
2
3
Nayak, K. (2022). Microcontroller Embedded C Programming: Absolute Beginners [Video file]. Retrieved from https://www.udemy.com/course/microcontroller-embedded-c-programming/