โš™๏ธ 15. Preprocessor & Header Files

Before the C compiler translates your program into machine code, another stage can process special instructions called preprocessing directives.

These directives begin with the character #.

#include <stdio.h>
#define MAX 100

These directives affect the source before the resulting C statements are compiled. They are not instructions that run each time the program executes.

Core idea: Use directives to include declarations, replace macro names, and choose which source sections will be compiled.

15.1 What Is the C Preprocessor?

The preprocessor handles directives and macro expansion before the compiler translates the resulting C code. For example, it can replace a symbolic limit with its defined value.

Common directives include:

  • #include
  • #define
  • #undef
  • #if
  • #ifdef
  • #ifndef
  • #elif
  • #else
  • #endif
  • #error
  • #line
  • #pragma
A directive is not a normal C statement. It normally ends at the end of its source line, not at a semicolon. Put each directive on its own line.

15.2 Where Does the Preprocessor Fit?

 C Source File โ”‚ โ–ผ Preprocessing โ”‚ โ–ผ Translation / Compilation โ”‚ โ–ผ Object Code โ”‚ โ–ผ Linking โ”‚ โ–ผ Executable Program 

For learning purposes, it is useful to think of preprocessing as the stage that handles directives such as #include and #define before the compiler translates the resulting source.

This diagram shows the stages used to explain a build. A real compiler may combine some stages internally, but directives are still handled before the resulting C code is translated.

15.3 Preprocessor Directive Basics

A directive normally begins with #.

#define VALUE 10

The directive is recognized by the preprocessing stage.

Unlike an ordinary C statement, it does not require a semicolon at the end.

#include <stdio.h>
#define MAX 100
Do not write: #define MAX 100; when you intend MAX to represent the token sequence 100. The semicolon would become part of the macro replacement.

15.4 #include

The #include directive brings the named headerโ€™s contents into the source being processed. The header can supply the declarations needed by the code that follows.

#include <stdio.h>

This is why a program can use declarations associated with standard input/output facilities such as printf and scanf.

#include does not call a function. It is a preprocessing directive.

15.5 <...> vs "..."

Two common forms are:

#include <stdio.h>
#include "myheader.h"

Angle brackets are normally used for standard or library headers, while quotes are normally used for project headers. The compilerโ€™s documented search paths determine where each form looks.

<header.h>

Commonly used for library/system headers.

"header.h"

Used for project headers. Compilers commonly search near the including file before trying other configured locations.

15.6 What Is a Header File?

A header file commonly contains declarations, type definitions, macros and other information intended to be shared by multiple source files.

Examples from the C standard library include:

  • stdio.h โ€” input/output facilities
  • stdlib.h โ€” general utilities
  • string.h โ€” string handling
  • math.h โ€” mathematics functions
  • ctype.h โ€” character classification
  • time.h โ€” date and time facilities
  • limits.h โ€” integer limits
  • float.h โ€” floating-point characteristics
Including a header tells the compiler about the functions and types you use. It does not by itself supply a compiled libraryโ€™s function bodies. The linker resolves those separately when required.

15.7 #define

The #define directive defines a macro.

#define MAX_MARKS 100

When an eligible macro name appears later, its replacement tokens are inserted. Macro names inside string literals are not replaced as ordinary identifiers.

#include <stdio.h>

#define MAX_MARKS 100

int main(void)
{
    printf("%d\n", MAX_MARKS);
    return 0;
}

Conceptually, the preprocessor produces source equivalent to:

printf("%d\n", 100);
Defining MAX does not create a variable named MAX. The compiler sees the expanded source, so the macro has no separate runtime storage just because it was defined.

15.8 Object-Like Macros

A macro without parameters is commonly called an object-like macro.

#define PI 3.14159265358979323846
#define MAX_SIZE 100
#define COMPANY_NAME "CodeBhavya"

These macros replace the identifier with their replacement token sequence.

Good use

Compile-time constants, feature flags and short configuration values.

Alternative

For typed constant values, const is useful when you want a typed object. In C17, a const int is not generally an integer constant expression for uses such as a case label, so it does not replace every use of a macro or enumerator.

15.9 Function-Like Macros

A function-like macro has parameters.

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

Example:

int result = SQUARE(5);

Conceptually becomes:

int result = ((5) * (5));
A function-like macro substitutes its argument tokens into the replacement. It has no parameter types of its own, and an argument can appear more than once after expansion. The compiler checks the expanded C code, not a normal function call.

15.10 Why Macro Parentheses Matter

Consider this unsafe macro:

#define SQUARE(x) x * x

Now:

SQUARE(2 + 3)

can become:

2 + 3 * 2 + 3

which is not the intended mathematical grouping.

Prefer:

#define SQUARE(x) ((x) * (x))
 Macro โ”‚ โ–ผ Token substitution โ”‚ โ–ผ Operator precedence still applies โ”‚ โ–ผ Parentheses protect the intended expression 

15.11 Macro Side Effects

Parentheses protect grouping, but a macro can still use an argument twice. Be careful with an argument that changes a variable, such as i++.

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

Consider:

int i = 3; int result = SQUARE(i++);

The macro expansion contains two occurrences of i++. In this multiplication, the changes to i are unsequenced, so the expression has undefined behavior. Do not run it and treat one compilerโ€™s output as a valid answer.

Use parentheses for grouping, and avoid arguments with side effects when a macro repeats them. A function can be clearer when each argument must be evaluated once.

15.12 Macro vs Function

Feature Macro Function
Handled as Preprocessing token replacement Normal C function semantics
Type checking No function-parameter type checking Compiler can check parameter types from declarations
Argument evaluation May occur multiple times Arguments are evaluated for the call according to C rules
Debugging Can be harder to inspect Usually easier to debug as a function
Recursion Not a normal recursive function mechanism Functions can be recursive

15.13 #undef

The #undef directive removes a macro definition.

#define LIMIT 100
#undef LIMIT

After #undef, the macro name is no longer defined by that macro definition.

#undef does not erase an ordinary variable. It operates on a macro definition.

15.14 Conditional Compilation

Conditional compilation chooses source code before the program runs. This differs from an if statement, which chooses a path while the compiled program is running.

#if CONDITION
/* selected source */
#endif

If the condition evaluates to nonzero after preprocessing, the controlled section is included.

Example

#define DEBUG 1
#if DEBUG
printf("Debug mode\n");
#endif

Because DEBUG expands to 1, the controlled section is included.

15.15 #if, #elif and #else

#define LEVEL 2
#if LEVEL == 1
/* beginner */
#elif LEVEL == 2
/* intermediate */
#else
/* other */
#endif

The preprocessor selects one appropriate branch.

 #if โ”‚ โ”œโ”€โ”€ condition true โ”‚ โ†“ โ”‚ include branch โ”‚ โ””โ”€โ”€ condition false โ†“ #elif? โ”‚ โ”œโ”€โ”€ true โ†’ include branch โ”‚ โ””โ”€โ”€ false โ†’ continue โ†“ #else 

15.16 #ifdef

#ifdef tests whether a macro is defined.

#define DEBUG
#ifdef DEBUG
printf("Debugging enabled\n");
#endif

The value of the macro is not the point here. The test asks whether the macro name is defined.

#ifdef DEBUG means: โ€œHas a macro named DEBUG been defined?โ€

15.17 #ifndef

#ifndef tests whether a macro is not defined.

#ifndef MAX_SIZE
#define MAX_SIZE 100
#endif

This pattern is especially important for protecting header files from repeated inclusion.

15.18 Header Include Guards

If two headers both include the same project header, its definitions could be seen twice. A guard macro makes the second inclusion skip the guarded contents within that translation unit.

#ifndef STUDENT_H
#define STUDENT_H
struct Student { int id; int marks; };
void displayStudent(struct Student s);
#endif
 First inclusion โ”‚ โ–ผ STUDENT_H not defined โ”‚ โ–ผ Define STUDENT_H โ”‚ โ–ผ Process header contents Second inclusion โ”‚ โ–ผ STUDENT_H already defined โ”‚ โ–ผ Skip contents 
Choose a distinct guard name for each header. A guard prevents repeated processing within one translation unit; it does not fix ordinary global definitions duplicated across several source files.

15.19 How Project Header Files Work

A larger C project can separate declarations and implementations.

student.h

#ifndef STUDENT_H
#define STUDENT_H

struct Student {
    int id;
    int marks;
};

void displayStudent(struct Student s);

#endif

student.c

#include <stdio.h>
#include "student.h"

void displayStudent(struct Student s)
{
    printf("ID = %d\n", s.id);
    printf("Marks = %d\n", s.marks);
}

main.c

#include "student.h"

int main(void)
{
    struct Student s = {101, 90};
    displayStudent(s);
    return 0;
}
Put shared declarations in student.h and the function body in student.c. Include the header from both source files, then compile and link the source files together. Including the header alone does not link student.c.

15.20 Predefined Macros

C implementations provide several predefined macros. Common examples include:

__FILE__ __LINE__ __DATE__ __TIME__ __STDC__ __STDC_VERSION__

Their meanings are useful for diagnostics and implementation/language-version information, although availability and exact predefined macro set can depend on the language standard and implementation.

Example

#include <stdio.h>

int main(void)
{
    printf("File: %s\n", __FILE__);
    printf("Line: %d\n", __LINE__);
    return 0;
}

__FILE__ identifies the source file and __LINE__ identifies the current source line in the preprocessing context.

15.21 __STDC_VERSION__

When provided by a conforming implementation for the applicable standard mode, __STDC_VERSION__ can be used to identify the C language standard version.

#include <stdio.h>

int main(void)
{
#ifdef __STDC_VERSION__
    printf("C standard version: %ld\n", (long)__STDC_VERSION__);
#else
    printf("C standard version macro not available\n");
#endif
    return 0;
}
Select the intended language mode when compiling. For example, these checks use C17; do not infer the language version from a compilerโ€™s name or its default settings.

15.22 #error

The #error directive requests a diagnostic containing the specified preprocessing tokens.

#ifndef REQUIRED_FEATURE
#error "Required feature is not enabled"
#endif

Use it for an unsupported configuration so the compiler reports the problem during translation, before a program is run.

15.23 #pragma

#pragma provides implementation-defined instructions to the compiler.

#pragma

The actual effect depends on the compiler and platform.

Do not assume that every #pragma is supported by every compiler. Check the compilerโ€™s documentation before relying on a particular pragma.

15.24 #line

The #line directive changes the logical line number and optionally the filename used in later diagnostics and predefined macros. It does not move execution to another line.

#line 100 "generated.c"

This is primarily useful in generated source and specialized tooling.

15.25 Program Tracing: Macro Replacement

#define BONUS 10

int main(void)
{
    int marks = 80;
    marks = marks + BONUS;
    return 0;
}
 Original source marks = marks + BONUS; โ”‚ โ–ผ Macro replacement โ”‚ โ–ผ marks = marks + 10; โ”‚ โ–ผ Compiler processes resulting source 

The macro does not create a runtime variable called BONUS.

15.26 Program Tracing: Conditional Compilation

#define MODE 2
#if MODE == 1
printf("Basic");
#elif MODE == 2
printf("Advanced");
#else
printf("Unknown");
#endif
 MODE โ”‚ โ–ผ 2 โ”‚ โ–ผ #if MODE == 1 โ”‚ โ””โ”€โ”€ false #elif MODE == 2 โ”‚ โ””โ”€โ”€ true โ”‚ โ–ผ printf("Advanced"); #else Skipped 

The non-selected source branches are excluded from the resulting translation unit.

15.27 Program Tracing: Include Guard

#ifndef CONFIG_H
#define CONFIG_H
#define MAX_USERS 100
#endif
 Include config.h โ”‚ โ–ผ Is CONFIG_H defined? โ”‚ โ”Œโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ No Yes โ”‚ โ”‚ โ–ผ โ–ผ Define Skip CONFIG_H contents โ”‚ โ–ผ Process header 

15.28 Macro Stringification โ€” Preview

Function-like macros can use # in a replacement list to convert a macro argument into a string literal.

#define SHOW_NAME(x) #x

For example:

SHOW_NAME(CodeBhavya)

produces a string-literal representation equivalent to:

"CodeBhavya"
This is a macro operator, not the same # character used to start a preprocessing directive.

15.29 Token Pasting ## โ€” Preview

The ## operator in a macro replacement list combines preprocessing tokens.

#define JOIN(a, b) a##b

For example:

JOIN(total, 1)

can form the preprocessing token:

total1
Stringification and token pasting are powerful macro facilities, but they are more advanced and will be explored further in the Advanced C Preprocessor topic.

15.30 Common Mistakes

Mistake 1 โ€” Adding a semicolon to a macro value

#define MAX 100;

The semicolon becomes part of the macro replacement.

Prefer:

#define MAX 100

Mistake 2 โ€” Unsafe function-like macro

#define SQUARE(x) x * x

Use proper parentheses:

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

Mistake 3 โ€” Forgetting include guards

Repeated inclusion can repeat type definitions or other declarations. Put a distinct include guard around a project headerโ€™s contents.

Mistake 4 โ€” Confusing #ifdef with value testing

#ifdef FLAG asks whether FLAG is defined. It does not ask whether its replacement value is nonzero.

Mistake 5 โ€” Using -> or . incorrectly in macro code

Preprocessor substitution does not change C's normal operator rules. After expansion, the resulting source must still be valid C.

15.31 Common Confusions

Confusion Correct Understanding
#include is a function It is a preprocessing directive.
#define creates a variable It defines a macro.
Macro and function are identical Macros are preprocessing substitutions; functions have normal C call semantics.
#ifdef X checks whether X equals 1 It checks whether macro X is defined.
Header file is the compiled library A header supplies source-level declarations, definitions of macros/types, etc.; library linkage is a separate concern.
sizeof can be used in preprocessing Preprocessing conditions do not generally evaluate arbitrary C expressions such as sizeof.

15.32 Complete Program: Macro + Conditional Compilation

#include <stdio.h>

#define PASS_MARKS 40
#define DEBUG

int main(void)
{
    int marks = 75;
#ifdef DEBUG
    printf("Checking marks...\n");
#endif
    if (marks >= PASS_MARKS) {
        printf("Pass\n");
    } else {
        printf("Fail\n");
    }
    return 0;
}

Trace

  1. stdio.h is included.
  2. PASS_MARKS is defined as 40.
  3. DEBUG is defined.
  4. The #ifdef DEBUG block is included.
  5. PASS_MARKS is replaced by its macro replacement.
  6. The resulting C source is compiled normally.

15.33 Good Header File Design

A useful project header should expose information that other source files need.

For example:

#ifndef CALCULATOR_H
#define CALCULATOR_H
int add(int a, int b);
int subtract(int a, int b);
#endif

The implementation can remain in a source file:

#include "calculator.h"

int add(int a, int b)
{
    return a + b;
}

int subtract(int a, int b)
{
    return a - b;
}
A header describes what other source files may use. The source file supplies the implementation. Keeping these roles clear makes a multi-file program easier to build and maintain.

15.34 Placement-Important Macro Patterns

#define MAX(a, b) ((a) > (b) ? (a) : (b))

This demonstrates parentheses around both parameters and the complete replacement expression.

However, repeated evaluation is still possible:

MAX(i++, j++)

Therefore macros should be designed carefully.

Remember

Parenthesize macro parameters.

Remember

Parenthesize the complete expression.

Remember

Watch for repeated evaluation and side effects.

Remember

Use functions when normal function semantics are more appropriate.

15.35 The _Generic Selection Expression (C11)

_Generic is a C11 selection expression, not a preprocessing directive. It chooses a listed expression using the type of its controlling expression. That controlling expression is not evaluated, so using it for type selection does not perform its side effects.

Complete C program:

#include <stdio.h>

#define TYPE_NAME(x) _Generic((x), int: "int", double: "double", default: "other")

int main(void)
{
    printf("%s\n", TYPE_NAME(5));
    printf("%s\n", TYPE_NAME(2.5));
    return 0;
}

Expected output:

int
double

Step-by-step explanation:

  1. The integer constant 5 selects the int association.
  2. The floating literal 2.5 has type double and selects that association.
  3. default is chosen for any type without a matching association.
Remember: The controlling expression chooses an association by type, not by its runtime value. The selected result expression can still be evaluated when the surrounding expression runs.

15.36 Quick Revision

๐Ÿ“Œ Preprocessor โ†’ Processes source before compilation.

๐Ÿ“Œ #include โ†’ Includes a file.

๐Ÿ“Œ #define โ†’ Defines a macro.

๐Ÿ“Œ #undef โ†’ Removes a macro.

๐Ÿ“Œ #if โ†’ Conditional compilation.

๐Ÿ“Œ #ifdef โ†’ Macro is defined.

๐Ÿ“Œ #ifndef โ†’ Macro is not defined.

๐Ÿ“Œ #else โ†’ Alternative condition.

๐Ÿ“Œ #elif โ†’ Additional condition.

๐Ÿ“Œ #endif โ†’ Ends conditional block.

๐Ÿ“Œ Header guard โ†’ Prevents repeated inclusion.
INTERACTIVE LEARNING

๐ŸŽฌ Preprocessor โ€” Source Transformation Flow

Follow what happens to directives before ordinary C execution begins.

PROGRAM TRACING

๐Ÿ”Ž Program Tracing โ€” Preprocessor & Header Files

Trace the runtime program after macro expansion and conditional compilation have already taken effect.

15.37 Quick MCQs

Select an answer first, then click Check Answer. A correct choice becomes green. If the answer is wrong, your choice becomes red and the correct option becomes green. The explanation appears below.

1. Which symbol begins a preprocessor directive?
2. Which directive includes a header file?
3. Which directive defines a macro?
4. Which directive removes a macro definition?
5. Which directive checks whether a macro is defined?
6. Which directive checks whether a macro is not defined?
7. Which header is commonly used for printf()?
8. Which directive ends a conditional compilation block?
PRACTICE

15.38 ๐ŸŽฏ Practice Problems

Practice macros, conditional compilation, header-style declarations, predefined macros, include guards, and project organization. Use ๐Ÿ’ป Solve It Yourself first, open Hint only when needed, and use Show Program after attempting the problem.

๐Ÿ“ˆ Preprocessor & Header Files Practice Progress
Solved 0 / 20
Completed with Solution 0
Total Score 0 / 400
Completion 0%
A problem counts as Solved when all test cases pass without opening the full solution. Problems completed after viewing the official solution are tracked separately.
1. PI Macro and Circle Area

Problem 1: Define a macro PI and calculate the area of a circle.

Input: Radius as a floating-point number.

Output: Print the area to two decimal places.

2. Square Macro

Problem 2: Create a macro to calculate the square of an integer.

Input: One integer.

Output: Print its square.

3. Maximum Macro

Problem 3: Create a macro to find the maximum of two integers.

Input: Two integers.

Output: Print the larger value.

4. Minimum Macro

Problem 4: Create a macro to find the minimum of two integers.

Input: Two integers.

Output: Print the smaller value.

5. Cube Macro

Problem 5: Create a macro to calculate the cube of an integer.

Input: One integer.

Output: Print its cube.

6. Use #undef

Problem 6: Use #undef to remove a macro definition and then define the same name with a new value.

Input: No input.

Output: Print the new macro value.

7. Check DEBUG with #ifdef

Problem 7: Use #ifdef to check whether DEBUG is defined.

Input: No input.

Output: Print "Debug Enabled".

8. Conditional Block with #ifndef

Problem 8: Use #ifndef to compile a block only when FEATURE is not defined.

Input: No input.

Output: Print "Feature Not Defined".

9. Select Version with #if and #else

Problem 9: Use #if and #else to select between two versions of a program.

Input: No input.

Output: Print "Version 2".

10. Select Among Three Versions with #elif

Problem 10: Use #elif to select among three program modes.

Input: No input.

Output: Print "Mode 3".

11. Header-Style add() Declaration

Problem 11: Create a header-style declaration for add() and use the function in a program.

Input: Two integers.

Output: Print their sum.

12. Mathematical Constants Header

Problem 12: Create reusable mathematical constants with macros and use them in a calculation.

Input: One floating-point radius.

Output: Print circle circumference to two decimal places.

13. String Utility Header Interface

Problem 13: Create a header-style declaration for a string utility function that counts vowels.

Input: One word.

Output: Print the number of vowels.

14. Traditional Include Guard

Problem 14: Create a header-style declaration protected by a traditional include guard and use the declared function.

Input: Two integers.

Output: Print their product.

15. __FILE__ and __LINE__

Problem 15: Create a program that displays __FILE__ and __LINE__ using a controlled #line directive.

Input: No input.

Output: Print "codebhavya.c 500".

16. __DATE__ and __TIME__

Problem 16: Use the predefined __DATE__ and __TIME__ macros and verify their standard string sizes.

Input: No input.

Output: Print the sizes of the two string literals, including their null terminators.

17. Absolute Value Macro

Problem 17: Create a macro that calculates the absolute value of an integer.

Input: One integer, excluding the minimum representable int.

Output: Print its absolute value.

18. Even Number Macro

Problem 18: Create a macro to check whether an integer is even.

Input: One integer.

Output: Print "Even" or "Odd".

19. Macro vs Function

Problem 19: Demonstrate a square macro and an equivalent square function on the same input.

Input: One integer.

Output: Print the macro result and function result.

20. Small Multi-File Project Pattern

Problem 20: Model a small .h + .c + main.c project by separating declarations, implementations, and main() sections in one runnable file.

Input: Two integers.

Output: Print their sum and difference on separate lines.

15.39 Key Takeaway

๐ŸŽฏ Remember:

#include โ†’ Include files

#define โ†’ Define macros

#undef โ†’ Remove macros

#ifdef โ†’ If macro is defined

#ifndef โ†’ If macro is not defined

#if / #elif / #else โ†’ Conditional compilation

#endif โ†’ End conditional block

Header guards โ†’ Prevent repeated inclusion
INTERVIEW PREPARATION

๐ŸŽค Preprocessor & Header Files โ€” Interview Questions

1. What happens before compilation when the C preprocessor runs?
2. Why should macro parameters usually be wrapped in parentheses?
3. What is a common danger of function-like macros?
4. What is the difference between #ifdef and #ifndef?
5. What are header guards?
6. What is the difference between #include <file.h> and #include "file.h"?
7. What does #undef do?
8. Why is #pragma considered less portable?
PLACEMENT TIPS

๐Ÿ’ก Preprocessor & Header Files โ€” Placement Tips

  • For function-like macros, parenthesize both each parameter and the entire replacement expression.
  • Avoid passing expressions with side effects such as i++ to macros that use the same parameter more than once.
  • Use header guards in reusable project headers to prevent repeated declarations caused by multiple inclusion paths.
  • Keep declarations and shared interfaces in header files; keep function definitions in source files unless there is a specific reason otherwise.
  • Use conditional compilation for build-time choices such as debugging code or platform-specific sections.
  • Remember that preprocessing happens before runtime, so #if cannot directly test a normal runtime variable.
EXTRA PRACTICE

โœ๏ธ Preprocessor & Header Files โ€” Extra Practice Questions

  1. Create a macro that returns the smaller of three values without using a function.
  2. Create a DEBUG macro that enables extra diagnostic output only in debug builds.
  3. Write a header guard for a hypothetical student.h header containing a structure declaration and function prototypes.
  4. Use nested #if directives to select code based on two compile-time configuration macros.
  5. Experiment with #line and observe how it changes __FILE__ and __LINE__.
  6. Create a three-file calculator project consisting of calculator.h, calculator.c, and main.c.
โ† Previous Topic: Structures & Unions Next Topic: File Handling โ†’