Chapter 33Advanced

C Preprocessor Directives

Code that runs before compilation! Learn #include, #define macros, and conditional compilation with #ifdef.

18 min readUpdated 2024-12-16
preprocessor#include#definemacro#ifdef#ifndefheader guards

What You Will Learn

  • Include header files correctly
  • Create macros with #define
  • Use conditional compilation
  • Understand header guards

01What is the Preprocessor?

📝 Definition

The C Preprocessor is a tool that processes your code before compilation. It handles directives that start with # - like including files, defining constants, and conditional compilation.

🔄 Compilation Process

Source Code

.c file

Preprocessor

#include, #define

Compiler

Translation

Executable

.exe / a.out

💡 Key Preprocessor Directives

  • #include - Include header files
  • #define - Define macros and constants
  • #ifdef / #ifndef / #endif - Conditional compilation
  • #pragma - Compiler-specific instructions

02#include Directive

The #include directive tells the preprocessor to copy the contents of another file into your code. There are two forms:

<angle brackets>

main.c
C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

Used for standard library headers. Searches system include directories.

"double quotes"

main.c
C
#include "myheader.h"
#include "utils/helper.h"
#include "../config.h"

Used for your own headers. Searches current directory first.

Creating Your Own Header File

mymath.h
C
1// mymath.h - Header file (declarations)
2#ifndef MYMATH_H // Header guard - prevents double inclusion
3#define MYMATH_H
4
5// Function declarations (prototypes)
6int add(int a, int b);
7int subtract(int a, int b);
8int multiply(int a, int b);
9
10#endif
mymath.c
C
1// mymath.c - Implementation file (definitions)
2#include "mymath.h"
3
4int add(int a, int b) {
5 return a + b;
6}
7
8int subtract(int a, int b) {
9 return a - b;
10}
11
12int multiply(int a, int b) {
13 return a * b;
14}
main.c
C
1// main.c - Using the header
2#include <stdio.h>
3#include "mymath.h"
4
5int main() {
6 printf("5 + 3 = %d\n", add(5, 3));
7 printf("5 - 3 = %d\n", subtract(5, 3));
8 printf("5 * 3 = %d\n", multiply(5, 3));
9 return 0;
10}

03#define Macros

The #define directive creates macros - text replacements done by the preprocessor before compilation.

Simple Constants

constants.c
C
1#include <stdio.h>
2
3// Define constants
4#define PI 3.14159
5#define MAX_SIZE 100
6#define GREETING "Hello, World!"
7
8int main() {
9 double area = PI * 5 * 5; // PI replaced with 3.14159
10 int arr[MAX_SIZE]; // MAX_SIZE replaced with 100
11 printf("%s\n", GREETING); // GREETING replaced with string
12 return 0;
13}

Macros with Parameters

macro_functions.c
C
1#include <stdio.h>
2
3// Macro functions (no function call overhead)
4#define SQUARE(x) ((x) * (x))
5#define MAX(a, b) ((a) > (b) ? (a) : (b))
6#define MIN(a, b) ((a) < (b) ? (a) : (b))
7#define ABS(x) ((x) < 0 ? -(x) : (x))
8
9int main() {
10 int n = 5;
11 printf("Square of %d: %d\n", n, SQUARE(n)); // Output: 25
12 printf("Max(3, 7): %d\n", MAX(3, 7)); // Output: 7
13 printf("Min(3, 7): %d\n", MIN(3, 7)); // Output: 3
14 printf("Abs(-10): %d\n", ABS(-10)); // Output: 10
15 return 0;
16}

⚠️ Macro Pitfall: Always Use Parentheses!

main.c
C
// BAD - No parentheses
#define SQUARE_BAD(x) x * x
// Problem: SQUARE_BAD(3+2) expands to 3+2 * 3+2 = 3+6+2 = 11 (WRONG!)
// GOOD - With parentheses
#define SQUARE_GOOD(x) ((x) * (x))
// SQUARE_GOOD(3+2) expands to ((3+2) * (3+2)) = 5 * 5 = 25 (CORRECT!)

#undef - Undefine a Macro

main.c
C
#define DEBUG 1
// ... code that uses DEBUG ...
#undef DEBUG // Now DEBUG is undefined
// DEBUG no longer exists here

04Conditional Compilation

Conditionally include or exclude code based on defined macros. Useful for debugging, platform-specific code, and feature flags.

#ifdef / #ifndef / #endif

debug_example.c
C
1#include <stdio.h>
2
3#define DEBUG // Comment this line to disable debug mode
4
5int main() {
6 int x = 10;
7
8 #ifdef DEBUG
9 printf("[DEBUG] x = %d\n", x); // Only compiled if DEBUG is defined
10 #endif
11
12 printf("Result: %d\n", x * 2);
13
14 #ifndef RELEASE
15 printf("[DEV] Development build\n"); // Only if RELEASE is NOT defined
16 #endif
17
18 return 0;
19}

#if / #elif / #else / #endif

version_check.c
C
1#include <stdio.h>
2
3#define VERSION 2
4
5int main() {
6 #if VERSION == 1
7 printf("Running Version 1\n");
8 #elif VERSION == 2
9 printf("Running Version 2\n");
10 #else
11 printf("Unknown Version\n");
12 #endif
13
14 #if defined(DEBUG) && VERSION >= 2
15 printf("Debug mode for V2+\n");
16 #endif
17
18 return 0;
19}

Platform-Specific Code

platform.c
C
1#include <stdio.h>
2
3void clearScreen() {
4 #ifdef _WIN32
5 system("cls"); // Windows
6 #else
7 system("clear"); // Linux/Mac
8 #endif
9}
10
11int main() {
12 #if defined(_WIN32) || defined(_WIN64)
13 printf("Running on Windows\n");
14 #elif defined(__linux__)
15 printf("Running on Linux\n");
16 #elif defined(__APPLE__)
17 printf("Running on macOS\n");
18 #else
19 printf("Unknown platform\n");
20 #endif
21
22 return 0;
23}

05Header Guards

Header guards prevent a header file from being included multiple times, which would cause "redefinition" errors.

❌ Without Header Guard

main.c
C
// myheader.h
struct Point {
int x, y;
};
// If included twice:
// ERROR: redefinition of 'struct Point'

✅ With Header Guard

main.c
C
// myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H
struct Point {
int x, y;
};
#endif // MYHEADER_H

💡 Modern Alternative: #pragma once

main.c
C
// myheader.h
#pragma once // Non-standard but widely supported
struct Point {
int x, y;
};

#pragma once is simpler but not part of the C standard. Traditional header guards are more portable.

06Common Mistakes

❌ Mistake 1: Semicolon after #define

main.c
C
// WRONG
#define MAX 100; // Semicolon included in replacement!
int arr[MAX]; // Becomes: int arr[100;]; - ERROR!
// CORRECT
#define MAX 100
int arr[MAX]; // Becomes: int arr[100];

❌ Mistake 2: Space in macro name

main.c
C
// WRONG
#define MAX VALUE 100 // 'MAX' is defined as 'VALUE 100'
// CORRECT
#define MAX_VALUE 100

❌ Mistake 3: Missing parentheses in macro

main.c
C
// WRONG
#define DOUBLE(x) x + x
int result = DOUBLE(5) * 2; // 5 + 5 * 2 = 5 + 10 = 15 (WRONG!)
// CORRECT
#define DOUBLE(x) ((x) + (x))
int result = DOUBLE(5) * 2; // ((5) + (5)) * 2 = 10 * 2 = 20

❌ Mistake 4: Side effects in macro arguments

main.c
C
#define SQUARE(x) ((x) * (x))
int a = 5;
int result = SQUARE(a++);
// Expands to: ((a++) * (a++))
// a is incremented TWICE! Result is undefined behavior.
// Solution: Use inline functions for complex operations

07Summary

What You Learned:

  • #include: Include header files (use <> for standard, "" for your own)
  • #define: Create constants and macro functions
  • Conditional: #ifdef, #ifndef, #if, #elif, #else, #endif
  • Header Guards: Prevent multiple inclusion with #ifndef/#define/#endif
  • Best Practices: Always use parentheses in macros, no semicolons in #define