Part of C in 100s

C in 100 Seconds: Preprocessor Directives

Celest KimCelest Kim

Video: C in 100 Seconds: Preprocessor Directives — define, ifdef, ifndef | Episode 40 by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

C Preprocessor Directives: define, ifdef, ifndef

#define X 5 for constants. #ifdef DEBUG for conditional compilation. #ifndef GUARD for include guards. The text-substitution layer that runs before the C compiler.

The preprocessor is a separate program (or stage) that runs before compilation. It manipulates source text — substituting macros, including files, conditionally compiling. It doesn't understand C; it's a glorified search-and-replace.

The basic shape

#include <stdio.h>

#define PI 3.14159
#define MAX_SIZE 100

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

int main() {
  printf("PI: %f\n", PI);
  printf("MAX_SIZE: %d\n", MAX_SIZE);
  printf("SQUARE(5): %d\n", SQUARE(5));

#ifdef DEBUG
  printf("Debug mode is ON\n");
#else
  printf("Debug mode is OFF\n");
#endif

#ifndef VERSION
  #define VERSION "1.0.0"
#endif
  printf("Version: %s\n", VERSION);

#if MAX_SIZE > 50
  printf("Large buffer mode\n");
#else
  printf("Small buffer mode\n");
#endif

  return 0;
}

#define — substitution

#define PI 3.14159

After preprocessing, every occurrence of PI in the source is replaced with 3.14159. Pure text substitution.

double area = PI * r * r;
// becomes:
double area = 3.14159 * r * r;

The compiler never sees PI — only 3.14159.

Macros with arguments

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

int s = SQUARE(5);
// becomes:
int s = ((5) * (5));

Function-like macros take arguments. The arguments are substituted as text, not evaluated.

The parens matter:

#define BAD_SQUARE(x) x * x
int b = BAD_SQUARE(2 + 3);   // 2 + 3 * 2 + 3 = 11, not 25!

BAD_SQUARE(2 + 3) becomes 2 + 3 * 2 + 3. Operator precedence makes that 11. With parens ((2 + 3) * (2 + 3)), you get 25.

Rule: wrap each argument in parens, and wrap the whole result in parens.

Macros vs functions

Aspect Macro Function
Type checking None Yes
Multiple types Works for any Need overloading or generic
Side effects Re-evaluates args Single evaluation
Debugging Hard (substituted text) Easy (real call)
Inlining Always Depends on compiler

SQUARE(i++) doubles the increment because i++ appears twice. Functions evaluate args once.

For type-safe alternatives:

static inline int square(int x) { return x * x; }

Inline functions get the speed of macros with the safety of functions. Modern C: prefer inline functions for the math; reserve macros for things functions can't do (cross-type, code-gen, conditionals).

#ifdef and #ifndef

#ifdef DEBUG
  printf("Debug: x = %d\n", x);
#endif

#ifdef NAME is true if NAME is defined; false otherwise. The block between #ifdef and #endif is included or excluded from the compiled output.

#ifndef NAME is the opposite — true if NOT defined.

Activated via:

gcc -DDEBUG main.c   # define DEBUG

-DNAME defines a macro from the command line. -DNAME=value defines with a value.

#if — numeric conditions

#if MAX_SIZE > 50
  // ... included if MAX_SIZE > 50 ...
#elif MAX_SIZE > 10
  // ...
#else
  // ...
#endif

#if evaluates an arithmetic expression involving #define'd constants. More flexible than #ifdef for value-based decisions.

defined(NAME) lets you combine:

#if defined(DEBUG) && !defined(NDEBUG)
  // debug output
#endif

NDEBUG (no-debug) is the convention used by assert.h — defining it disables assert.

Include guards

// myfile.h
#ifndef MYFILE_H
#define MYFILE_H

// declarations

#endif

The pattern from episode 16. Without it, multiple #include "myfile.h" cause re-declarations.

Modern alternative: #pragma once (compiler-specific, but widely supported).

#define for "feature flags"

#define MAX_USERS 100
#define ENABLE_LOGGING

#ifdef ENABLE_LOGGING
void log_event(const char *msg) {
  fprintf(stderr, "[LOG] %s\n", msg);
}
#else
#define log_event(msg) ((void)0)   // no-op
#endif

log_event("started");

When ENABLE_LOGGING is defined, real logging. Otherwise, the macro expands to a no-op. The compiler optimizes away the no-op.

Heavy in embedded code where every byte matters.

Stringification (#)

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

int n = 42;
LOG(n);   // prints: "n = 42"

#x in a macro turns the argument into a string literal. LOG(n) becomes printf("%s = %d\n", "n", n);.

Useful for debug-print macros that show the variable name.

Token pasting (##)

#define MAKE_VAR(name) int var_##name = 0

MAKE_VAR(foo);   // int var_foo = 0;
MAKE_VAR(bar);   // int var_bar = 0;

## joins two tokens into one identifier. Used for code generation patterns.

Common preprocessor macros (built-in)

__FILE__   // current source file path (string)
__LINE__   // current line number (int)
__DATE__   // compilation date
__TIME__   // compilation time
__func__   // current function name (C99+)

Useful for debug logging:

#define DEBUG_LOG(msg) \
  fprintf(stderr, "%s:%d: %s\n", __FILE__, __LINE__, msg)

Common mistakes

Missing parens in macros. #define DOUBLE(x) x * 2 then DOUBLE(1+1) is 1+1*2 = 3, not 4.

Side effects in macro args. SQUARE(i++) increments twice. MIN(a++, b) may compare wrong.

Missing \ for multi-line macros. Macros are one-line by default. Continue with \.

#define shadows parameters. #define PI 3.14; void foo(int PI)PI is replaced inside foo too. Confusing.

Stale #ifdefs. Code blocks behind dead #defines rot — never compiled, never tested.

#include cycles. A.h includes B.h includes A.h. Guards prevent infinite expansion but the result depends on order.

What's next

Episode 41: multi-file projects. Multiple .c files compiled together, Makefile-driven builds.

Recap

#define NAME value for constants; #define NAME(arg) ... for function-like macros (always parenthesise!). #ifdef/#ifndef/#endif for conditional compilation. #if expression for value-based conditions. Include guards #ifndef X #define X ... #endif (or #pragma once). #x for stringification; ## for token pasting. Built-in macros: __FILE__, __LINE__, __func__. Modern C: prefer static inline functions over function-like macros where possible.

Next episode: multi-file projects.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.