Skip to content

Chapter 16 of 21

Preprocessor Directives

Use includes, object and function macros, conditions, and header guards.

26 minutes 10 quick checksBy Subha Prasad
Lesson 16 of 21Course navigation

Lesson content

Read, practise, then check your understanding

Before the compiler parses C, the preprocessor handles directives beginning with #. It includes headers, expands macros, and selects conditional source. Because macros perform token substitution rather than typed function calls, use them deliberately.

Include headers

#include <stdio.h>       // implementation search path
#include "project/io.h" // usually search project path first

A header should declare a cohesive public interface and be safe to include more than once. Use a portable include guard:

#ifndef PROJECT_BUFFER_H
#define PROJECT_BUFFER_H

#include <stddef.h>
size_t buffer_capacity(void);

#endif

Headers should include what their own declarations require. Do not rely on another header being included first.

Object-like and function-like macros

#define APP_VERSION "2.1.0"
#define ARRAY_COUNT(array) (sizeof(array) / sizeof((array)[0]))

Parenthesize each parameter and the complete replacement expression:

#define SQUARE(value) ((value) * (value))

Even this macro evaluates its argument twice, so SQUARE(i++) is wrong. Prefer a real function or static inline function when type checking and single evaluation matter.

The # operator stringifies a macro parameter; ## pastes tokens. These are useful for carefully designed logging, testing, and code-generation utilities, but can make diagnostics harder.

Conditional compilation

#if defined(_WIN32)
    #include <windows.h>
#elif defined(__unix__)
    #include <unistd.h>
#else
    #error Unsupported platform
#endif

Use build-system feature checks rather than assuming platforms from names where possible. #if DEBUG tests a numeric macro; #ifdef DEBUG tests only whether it is defined.

Built-in context and diagnostics

__FILE__ and __LINE__ help diagnostics. #error stops translation with a message. #pragma is implementation-defined, though standardized #pragma STDC forms and widely supported pragmas exist.

Keep macros uppercase when they behave like constants or syntax, keep their scope small, and never use a macro merely to save a few typed lines.

Knowledge check

Answer every question correctly to complete this chapter.

Which directive prevents repeated header contents when used as a guard?
Why should macro parameters be parenthesized?
Which include form normally searches implementation header paths?
What is a major risk of SQUARE(i++) as a macro that uses its parameter twice?
What does the # macro operator do to a parameter?
What does ## do in a macro replacement list?
How does #ifdef DEBUG differ from #if DEBUG?
What does #error do?
Which predefined macros identify source location?
When is a static inline function usually better than a function-like macro?

0 of 10 checks passed

Your progress is saved on this device.