Skip to content

C Preprocessors

The C Preprocessor (CPP) is a program that processes your source code before it is passed to the actual compiler. It acts as a text manipulation tool, interpreting special instructions called preprocessor directives.

Preprocessor directives start with a hash symbol (#) and are typically placed at the beginning of a line. They are not C statements and don’t end with semicolons.

Common uses of the preprocessor include:

  • File Inclusion: Including header files (#include).
  • Macro Definition: Defining symbolic constants and function-like macros (#define).
  • Conditional Compilation: Including or excluding parts of the code based on conditions (#if, #ifdef, #ifndef, #else, #elif, #endif).
  • Other Directives: Removing macro definitions (#undef), emitting errors (#error), providing compiler-specific instructions (#pragma).
DirectiveDescription
#includeInserts the content of a specified header file. <filename.h> searches standard system directories; "myheader.h" searches the current directory first, then standard directories.
#defineDefines a preprocessor macro. Can define symbolic constants (#define PI 3.14) or function-like macros (#define MAX(a,b) ((a) > (b) ? (a) : (b))). Text substitution occurs before compilation.
#undefRemoves a previously defined macro definition.
#ifdef MACRO_NAMEConditional compilation: Includes subsequent code if MACRO_NAME is defined (#defined). Ends with #endif.
#ifndef MACRO_NAMEConditional compilation: Includes subsequent code if MACRO_NAME is not defined. Often used for include guards in header files. Ends with #endif.
#if constant_expressionConditional compilation: Includes subsequent code if the constant_expression evaluates to non-zero (true). Ends with #endif.
#elseUsed within #if, #ifdef, #ifndef blocks to provide alternative code if the condition is false.
#elif constant_expressionElse if. Used within #if blocks to test another condition if the previous #if/#elif was false.
#endifMarks the end of a conditional compilation block (#if, #ifdef, #ifndef).
#error messageCauses the preprocessor to stop and issue an error message containing the specified text. Useful for sanity checks during compilation.
#pragma directiveProvides implementation-defined instructions to the compiler (e.g., controlling optimizations, warnings, structure packing). Highly compiler-specific.

Essential for using standard library functions and declarations from custom header files.

#include <stdio.h> // Include standard input/output library
#include <stdlib.h> // Include standard library (malloc, exit, etc.)
#include "my_utils.h" // Include a custom header file from the project
  1. Symbolic Constants: Replaces occurrences of the macro name with the defined text.
#define BUFFER_SIZE 1024
#define COMPANY_NAME "My Tech Corp."
char buffer[BUFFER_SIZE];
printf("Welcome to %s\n", COMPANY_NAME);
  1. Function-Like Macros: Can accept arguments. Warning: Be careful with parentheses to avoid operator precedence issues, and be aware that arguments might be evaluated multiple times.
#define SQUARE(x) ((x) * (x)) // Parentheses are crucial!
#define IS_ODD(n) ((n) % 2 != 0)
int main(void) {
int a = 5;
printf("Square of %d is %d\n", a, SQUARE(a)); // Output: 25
printf("Square of %d is %d\n", a + 1, SQUARE(a + 1)); // Expands to ((a + 1) * (a + 1)) -> 36
if (IS_ODD(a)) {
printf("%d is odd\n", a); // Output: 5 is odd
}
return 0;
}

Modern C often prefers const variables for constants and inline functions (or regular functions) over complex function-like macros due to better type safety and debugging.

Conditional Compilation (#if, #ifdef, etc.)

Section titled “Conditional Compilation (#if, #ifdef, etc.)”

Allows compiling different code sections based on conditions evaluated at compile time. Useful for platform-specific code, debugging builds, or feature flags.

#include <stdio.h>
#define DEBUG_MODE 1 // Set to 1 for debug builds, 0 for release
#define TARGET_OS "Linux"
int main(void) {
#if DEBUG_MODE == 1
printf("Debug mode is ON\n");
#else
printf("Release mode\n");
#endif
#ifdef TARGET_OS
printf("Target OS defined as: %s\n", TARGET_OS);
#else
printf("Target OS not defined.\n");
#endif
#if defined(TARGET_OS) && (TARGET_OS[0] == 'L') // Check if defined and starts with 'L'
printf("Looks like a Linux target.\n");
#elif defined(TARGET_OS) && (TARGET_OS[0] == 'W')
printf("Looks like a Windows target.\n");
#endif
#ifndef LICENSE_KEY // Check if LICENSE_KEY is NOT defined
#error "License key must be defined!" // Stop compilation
#endif
return 0;
}

You can define macros via #define or externally using compiler flags (e.g., gcc -DDEBUG_MODE=1 mycode.c).

C standards define several useful macros that provide information about the compilation environment. These should not be modified.

MacroDescription
__DATE__The compilation date as a string literal (e.g., “Jan 01 2023”).
__TIME__The compilation time as a string literal (e.g., “10:30:00”).
__FILE__The current source filename as a string literal.
__LINE__The current line number in the source file as an integer constant.
__STDC__Defined as 1 if the compiler conforms to the C standard (specific version may depend on compiler flags).
__STDC_VERSION__(C95+) Defined as a long integer representing the standard version (e.g., 201112L for C11, 201710L for C17).

Example:

printf("Compiled on %s at %s\n", __DATE__, __TIME__);
printf("Function '%s' is at %s:%d\n", __func__, __FILE__, __LINE__); // __func__ (C99) provides function name

The preprocessor is a powerful tool for code organization and conditional compilation in C.