Skip to content

Chapter 6 of 7

The Preprocessor and Macros

The C preprocessor runs before compilation and handles directives such as #include, #define, #ifdef, #ifndef, #if, #elif, #else, #endif, and #pragma. Its output is a single translation unit with all directives resolved. #include <file> searches system directories first and is used for standard headers like <stdio.h>, while #include "file" searches the current source directory first and is the convention for project headers. The preprocessor evaluates only integer literals, defined macros, and a small set of operators inside #if expressions, making it a distinct mini-language focused on textual transformation. Header files containing declarations are a clean way to share interfaces across source files without exposing implementations.

#define creates macros that perform textual substitution before compilation. Constants like #define PI 3.14159 substitute the literal everywhere, while function-like macros like #define MAX(a,b) ((a) > (b) ? (a) : (b)) take parameters; always wrap parameters and the whole expression in parentheses to avoid precedence bugs. The token-pasting operator ## joins two tokens, useful for generating repetitive declarations like DECLARE(counter) expanding to int my_counter = 0;. Compared to functions, macros lack type checking, may evaluate arguments multiple times, can produce code bloat, and are harder to debug because errors point at the expansion site rather than the source; for instance, MAX(a++, b++) would increment one operand twice. Prefer static inline functions or enum and const variables where possible, reserving #define for conditional compilation, token pasting, or features that must work before the compiler runs.

Conditional compilation controls which code is compiled. #ifdef MACRO includes code when the macro is defined, #ifndef when it is not, and #if evaluates a more general preprocessor expression; #elif chains branches and #else catches everything else. The most common application is the include guard, which prevents a header from being included more than once in a translation unit and avoids duplicate-definition errors during compilation. The non-standard but widely supported #pragma once serves the same purpose with simpler syntax, and #pragma more generally provides compiler-specific instructions like #pragma pack(1) for tight struct alignment; unrecognized pragmas are silently ignored. Compared with macros, const variables are real objects with types, scoped lifetimes, and visibility in debuggers, so they are usually the better choice for named constants.

All chapters
  1. 1Pointers and Dynamic Memory
  2. 2Data Types, Variables, and User-Defined Aggregates
  3. 3Arrays, Strings, and Memory Operations
  4. 4Functions and Control Flow
  5. 5File I/O and Program Termination
  6. 6The Preprocessor and Macros
  7. 7Compilation, Linking, and Program Structure

Drill it

Reading is not remembering. These come from the C Programming deck:

Q

What is a pointer in C?

A pointer is a variable that stores the memory address of another variable.Declared using the * operator: int *ptr;The address-of operator &amp; retrieves a var...

Q

How do you dereference a pointer?

Dereferencing means accessing the value stored at the address a pointer holds.Use the * operator: int val = *ptr;This retrieves the value at the memory location...

Q

What is a NULL pointer?

A NULL pointer is a pointer that does not point to any valid memory location.Assigned with: int *ptr = NULL;Always check for NULL before dereferencing to avoid...

Q

What does malloc() do in C?

malloc() allocates a block of memory on the heap and returns a pointer to it.Syntax: int *p = (int *)malloc(n * sizeof(int));Returns NULL if allocation fails. M...