ProgramGuru

C Preprocessor, Macros and Header Guards

C Preprocessor, Macros and Header Guards

A school-wide constant should be named once. Preprocessor substitution can do that, but a careless arithmetic macro can evaluate arguments more than once.

The preprocessor handles directives such as include, define and conditional inclusion before ordinary C translation. Object-like macros substitute tokens; function-like macros do not behave like ordinary function calls.

  1. Explain

    Use a constant macro and identify repeated-evaluation hazards.

  2. Trace

    Compute the square of a value obtained by incrementing i, using the function safely.

  3. Apply

    Choose a function when a macro could repeat an argument’s side effects.

Preprocessor: follow the data

  1. Start

    A school-wide constant should be named once. Preprocessor substitution can do that, but a careless arithmetic macro can evaluate arguments more than once.

  2. Operation

    The preprocessor handles directives such as include, define and conditional inclusion before ordinary C translation. Object-like macros substitute tokens; function-like macros do not behave like ordinary function calls.

  3. Result

    Places: 2 Square: 9

A guided model of this example. The illustration explains the process; run the complete C program to check its output.

Understand the operation

CLASS_CAPACITY is replaced by its defined tokens before the compiler analyzes expressions. A header guard combines #ifndef, #define and #endif to avoid reprocessing guarded declarations in one translation unit.

Parenthesizing arithmetic macro parameters prevents some precedence surprises, but a macro such as SQUARE(x) ((x) * (x)) still substitutes x twice. SQUARE(i++) can introduce unsequenced side effects. Use a function for operations that should evaluate an argument once.

Read the complete C program

This complete C17 program uses a small school example. Read declarations before the statements that use their values. Each printf format describes how the corresponding value is written to standard output.

C17 · complete program

#include <stdio.h>
#define CLASS_CAPACITY 30
static int square(int value) { return value * value; }
int main(void) {
  int students = 28;
  printf("Places: %d\n", CLASS_CAPACITY - students);
  printf("Square: %d\n", square(3));
  return 0;
}

Expected output

Places: 2
Square: 9
Run in C compiler →

Follow the values through the code

C17 · code used in this explanation

#include <stdio.h>
#define CLASS_CAPACITY 30
static int square(int value) { return value * value; }
int main(void) {
  int students = 28;
  printf("Places: %d\n", CLASS_CAPACITY - students);
  printf("Square: %d\n", square(3));
  return 0;
}

The preprocessor substitutes 30 for CLASS_CAPACITY.

The function receives the value three once and calculates nine from its local parameter.

Follow the worked calculation

  1. Read the starting values

    The preprocessor substitutes 30 for CLASS_CAPACITY.

  2. Perform the operation

    The function receives the value three once and calculates nine from its local parameter.

  3. Check the observable result

    Places: 2 Square: 9

    Places: 2
    Square: 9
A guided model of this example. The illustration explains the process; run the complete C program to check its output.

Try a small variation

C17 · code used in this explanation

#include <stdio.h>
#define CLASS_CAPACITY 30
static int square(int value) { return value * value; }
int main(void) {
  int students = 28;
  printf("Places: %d\n", CLASS_CAPACITY - students);
  printf("Square: %d\n", square(3));
  return 0;
}

Compute the square of a value obtained by incrementing i, using the function safely.

Worked answer

The argument evaluation supplies three and increments i once. The called function reuses its parameter without repeating the caller’s increment.

C17 · complete program

#include <stdio.h>
#define CLASS_CAPACITY 30
static int square(int value) { return value * value; }
int main(void) {
  int i = 3;
  int result = square(i++);
  printf("Square: %d; next i: %d\n", result, i);
  return 0;
}

Expected output

Square: 9; next i: 4
Run in C compiler →

Recognize the important boundaries

A macro is token substitution, not a typed runtime parameter contract. Prefer a function when substitution offers no specific needed benefit.

An include guard does not prevent a definition copied into several separately compiled translation units from creating multiple external definitions.

Check your understanding

Choose the answer, then compare the reasoning for every option.

Does adding parentheses to SQUARE(x) guarantee x is evaluated once?
Choose one answer. You can try again.

Answer and explanation

No; a body containing x twice can evaluate the supplied expression twice.

Correct. Grouping and evaluation count are separate issues.

Use the idea independently

The preprocessor handles directives such as include, define and conditional inclusion before ordinary C translation. Object-like macros substitute tokens; function-like macros do not behave like ordinary function calls.

Continue with testing changed implementations.

Language reference: WG14 C11 committee draft. These examples use the C17-compatible subset of the explained language features.

Reaching the recap and completing the quizzes are separate milestones.

    Next lesson: Test C Programs with Cases, Warnings and Assertions →