Lesson 55 of 60 – Preprocessor Directives in C
92%

Preprocessor Directives in C

The C preprocessor processes source code before the actual compilation takes place. Preprocessor directives are instructions that begin with the # symbol and are used for tasks such as including header files, defining macros, and conditionally compiling code.

Note: Preprocessor directives are handled before the compiler processes the main C source code. They normally do not end with a semicolon.

1. What is the C Preprocessor?

The preprocessor is a program that processes C source code before it is passed to the compiler.

It performs tasks such as:

  • Including header files
  • Replacing macros
  • Conditional compilation
  • Removing comments
  • Handling predefined macros

2. What is a Preprocessor Directive?

A preprocessor directive is an instruction for the preprocessor. It begins with the # symbol.

#include <stdio.h>

The directive tells the preprocessor to include the required header information before compilation.

3. Common Preprocessor Directives

Some commonly used directives are:

  • #include
  • #define
  • #undef
  • #if
  • #ifdef
  • #ifndef
  • #else
  • #elif
  • #endif
  • #pragma

4. #include Directive

The #include directive is used to include the contents or interface declarations of a header file.

#include <stdio.h>

This allows a program to use declarations provided by the standard I/O header.

5. System Header Files

Standard library headers are commonly written using angle brackets.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

These headers provide declarations for standard library facilities.

6. User-Defined Header Files

A project can include its own header file using double quotes.

#include "myheader.h"

This is commonly used for header files created as part of a project.

7. #define Directive

The #define directive creates a macro.

#define PI 3.14159

Before compilation, occurrences of PI can be replaced by the macro replacement text according to the preprocessor rules.

8. Object-Like Macro

A macro without parameters is called an object-like macro.

#define MAX_STUDENTS 100
#define COMPANY "Soopro Pathshala"

Macros can represent numeric values, strings, or other preprocessing tokens.

9. Using a #define Macro

#include <stdio.h>

#define PI 3.14159

int main()
{
    float radius = 5.0f;
    float area = PI * radius * radius;

    printf("Area = %.2f", area);

    return 0;
}

The preprocessor handles the PI macro before compilation.

10. Function-Like Macros

A macro can also accept parameters.

#define SQUARE(x) ((x) * (x))

Example:

int result = SQUARE(5);

Parentheses are important when writing expression-like macros.

11. Macro with Two Parameters

#define MAX(a, b) ((a) > (b) ? (a) : (b))

Example:

int largest = MAX(10, 20);

The macro replacement text uses parentheses to reduce unexpected precedence problems.

12. Macro vs Function

Macro Function
Handled by the preprocessor Handled by the compiler as a function
Text/token substitution Has normal function call semantics
No normal parameter type checking Parameters have declared types
Can cause repeated evaluation of an argument Arguments are evaluated according to function-call rules

For many ordinary calculations, a function is often easier to reason about.

13. #undef Directive

The #undef directive removes a macro definition.

#define VALUE 100

#undef VALUE

After #undef, the macro VALUE is no longer defined for subsequent preprocessing.

14. Conditional Compilation

Conditional compilation allows selected sections of source code to be included or excluded based on preprocessing conditions.

#if CONDITION

/* code */

#endif

The preprocessor evaluates the condition before compilation.

15. #if Directive

The #if directive includes code when its preprocessing expression evaluates to a nonzero value.

#define DEBUG 1

#if DEBUG
printf("Debug mode");
#endif

16. #ifdef Directive

The #ifdef directive checks whether a macro is defined.

#define DEBUG

#ifdef DEBUG
printf("Debugging enabled");
#endif

The code inside the conditional section is included because DEBUG is defined.

17. #ifndef Directive

The #ifndef directive checks whether a macro is not defined.

#ifndef VERSION
#define VERSION 1
#endif

This is frequently used in header guards.

18. #else Directive

The #else directive provides an alternative section when a preprocessing condition is false.

#ifdef DEBUG
printf("Debug mode");
#else
printf("Normal mode");
#endif

19. #elif Directive

The #elif directive allows another preprocessing condition to be tested.

#if VERSION == 1
printf("Version 1");
#elif VERSION == 2
printf("Version 2");
#else
printf("Other version");
#endif

20. #endif Directive

The #endif directive ends a conditional preprocessing block.

#ifdef DEBUG

printf("Debug information");

#endif

Every conditional block started by directives such as #if, #ifdef, or #ifndef must be properly terminated.

21. Header Guards

Header guards prevent the same header contents from being processed multiple times in a single translation unit.

#ifndef MYHEADER_H
#define MYHEADER_H

void display(void);

#endif

This pattern is commonly used in user-defined header files.

22. Predefined Macros

C implementations provide several predefined macros. Common examples include:

  • __FILE__ – source file name
  • __LINE__ – current source line number
  • __DATE__ – compilation date
  • __TIME__ – compilation time
printf("File: %s\n", __FILE__);
printf("Line: %d\n", __LINE__);

23. __FILE__ and __LINE__

These predefined macros are useful for diagnostics and debugging.

#include <stdio.h>

int main()
{
    printf("File: %s\n", __FILE__);
    printf("Line: %d\n", __LINE__);

    return 0;
}

24. Stringizing Operator #

Inside a function-like macro, the # operator can convert a macro argument into a string literal.

#define SHOW(x) printf(#x " = %d\n", x)

SHOW(10 + 20);

The stringizing operation is a preprocessor feature and is different from the #include directive.

25. Token Pasting Operator ##

The ## operator combines two preprocessing tokens into one token.

#define JOIN(a, b) a ## b

int JOIN(value, 1) = 100;

This can create an identifier such as value1.

26. Common Macro Mistake

Function-like macros can produce unexpected results if parameters are not properly parenthesized.

A risky macro is:

#define SQUARE(x) x * x

For example, SQUARE(2 + 3) can expand in an unexpected way. A safer form is:

#define SQUARE(x) ((x) * (x))

27. Complete Macro Program

#include <stdio.h>

#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int main()
{
    int a = 5;
    int b = 10;

    printf("Square of %d = %d\n", a, SQUARE(a));
    printf("Largest = %d\n", MAX(a, b));

    return 0;
}

28. Complete Conditional Compilation Program

#include <stdio.h>

#define DEBUG

int main()
{
#ifdef DEBUG
    printf("Debug information is enabled.\n");
#else
    printf("Debug information is disabled.\n");
#endif

    printf("Program is running.");

    return 0;
}

The section selected by the preprocessor depends on whether DEBUG is defined.

29. Practical Uses of the Preprocessor

  • Including standard and project header files
  • Creating reusable macros
  • Conditional compilation
  • Debug and release configurations
  • Header guards
  • Platform-specific code
  • Build configuration
  • Using predefined source information

30. Practice Program

Create a C program that:

  1. Creates a macro named PI.
  2. Creates a function-like macro to calculate a square.
  3. Uses #ifdef for a debug message.
  4. Uses #if with a version macro.
  5. Displays __FILE__ and __LINE__.
  6. Creates a small header file with a header guard.
  7. Includes the custom header using #include.
  8. Uses the defined macros in the main program.

This exercise will help you understand macros, conditional compilation, header guards, and predefined macros.

📌 Key Points

  • The preprocessor works before the compiler processes the source code.
  • Preprocessor directives begin with the # symbol.
  • #include includes header files.
  • #define creates macros.
  • #undef removes a macro definition.
  • #if, #ifdef, and #ifndef are used for conditional compilation.
  • #else and #elif provide alternative conditions.
  • #endif ends a conditional block.
  • Header guards help prevent repeated header contents.
  • __FILE__ and __LINE__ are useful predefined macros.
  • The # operator can stringify a macro argument.
  • The ## operator can paste preprocessing tokens together.
  • Macros should be written carefully, especially when parameters are expressions.

🧠 Quick Quiz

Question: Which preprocessor directive is used to include a header file?