14. 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 The preprocessor handles these directives before the normal compilation of the resulting source is performed.
14.1 What Is the C Preprocessor?
The C preprocessor is the part of the implementation that handles preprocessing directives before the compiler translates the resulting source.
Common directives include:
#include#define#undef#if#ifdef#ifndef#elif#else#endif#error#line#pragma
14.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.
14.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 #define MAX 100; when you intend MAX to represent the token sequence 100. The semicolon would become part of the macro replacement. 14.4 #include
The #include directive causes the named header or source file to be processed as part of the current translation unit according to the include rules.
#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. 14.5 <...> vs "..."
Two common forms are:
#include <stdio.h> #include "myheader.h" The exact search behavior is implementation-defined by the standard's include rules and implementation configuration, but the common intention is:
<header.h>
Commonly used for library/system headers.
"header.h"
Commonly used for project headers, with local-file searching behavior before or in addition to the implementation's configured include paths.
14.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 facilitiesstdlib.h— general utilitiesstring.h— string handlingmath.h— mathematics functionsctype.h— character classificationtime.h— date and time facilitieslimits.h— integer limitsfloat.h— floating-point characteristics
14.7 #define
The #define directive defines a macro.
#define MAX_MARKS 100 Later occurrences of the macro name in eligible source tokens are replaced according to the macro definition.
#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); 14.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 often easier to reason about and debug.
14.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)); 14.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
14.11 Macro Side Effects
Even a correctly parenthesized macro can evaluate its argument more than once.
#define SQUARE(x) ((x) * (x)) Consider:
int i = 3; int result = SQUARE(i++); The macro expansion contains two occurrences of i++. This makes the expression problematic because the same scalar object is modified more than once without the required sequencing.
14.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 |
14.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. 14.14 Conditional Compilation
Conditional directives allow parts of the source to be included or excluded based on preprocessing conditions.
#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.
14.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
14.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?” 14.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.
14.18 Header Include Guards
A header may be included through multiple paths. An include guard prevents the header's contents from being processed repeatedly within the same 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
14.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; } 14.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.
14.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; } 14.22 #error
The #error directive requests a diagnostic containing the specified preprocessing tokens.
#ifndef REQUIRED_FEATURE #error "Required feature is not enabled" #endif This can stop compilation when a required configuration condition is not satisfied.
14.23 #pragma
#pragma provides implementation-defined instructions to the compiler.
#pragma The actual effect depends on the compiler and platform.
#pragma works on every compiler. Pragmas are commonly used for implementation- specific features. 14.24 #line
The #line directive can alter the line number and optionally the source-file name reported by diagnostics and predefined macros from that point onward.
#line 100 "generated.c" This is primarily useful in generated source and specialized tooling.
14.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.
14.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.
14.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
14.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" # character used to start a preprocessing directive. 14.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 14.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
A project header may be included through multiple paths, potentially causing repeated declarations or definitions. Use a suitable include guard.
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.
14.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. |
14.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
stdio.his included.PASS_MARKSis defined as40.DEBUGis defined.- The
#ifdef DEBUGblock is included. PASS_MARKSis replaced by its macro replacement.- The resulting C source is compiled normally.
14.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; } 14.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.15 Quick Revision
📌 #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.
🎬 Preprocessor — Source Transformation Flow
Follow what happens to directives before ordinary C execution begins.
🎬 C Preprocessing Visualizer
Each active card represents one major preprocessing stage.
🔎 Program Tracing — Preprocessor & Header Files
Trace the runtime program after macro expansion and conditional compilation have already taken effect.
—
15.16 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.
Preprocessor directives begin with the # symbol.
#include requests inclusion of a header or source file according to preprocessing rules.
#define creates an object-like or function-like macro.
#undef removes the current definition of a macro name.
#ifdef NAME conditionally includes code when NAME is defined as a macro.
#ifndef NAME conditionally includes code when NAME is not defined.
The declaration of printf() is provided by stdio.h.
#endif closes conditional preprocessing blocks such as #if, #ifdef, and #ifndef.
15.17 🎯 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.
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.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#define PI 3.14159
int main()
{
double radius;
scanf("%lf", &radius);
printf("%.2f", PI * radius * radius);
return 0;
}
Problem 2: Create a macro to calculate the square of an integer.
Input: One integer.
Output: Print its square.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#define SQUARE(x) ((x) * (x))
int main()
{
int n;
scanf("%d", &n);
printf("%d", SQUARE(n));
return 0;
}
Problem 3: Create a macro to find the maximum of two integers.
Input: Two integers.
Output: Print the larger value.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
int main()
{
int a, b;
scanf("%d %d", &a, &b);
printf("%d", MAX(a, b));
return 0;
}
Problem 4: Create a macro to find the minimum of two integers.
Input: Two integers.
Output: Print the smaller value.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
int main()
{
int a, b;
scanf("%d %d", &a, &b);
printf("%d", MIN(a, b));
return 0;
}
Problem 5: Create a macro to calculate the cube of an integer.
Input: One integer.
Output: Print its cube.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#define CUBE(x) ((x) * (x) * (x))
int main()
{
int n;
scanf("%d", &n);
printf("%d", CUBE(n));
return 0;
}
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.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#define VALUE 10
#undef VALUE
#define VALUE 20
int main()
{
printf("%d", VALUE);
return 0;
}
Problem 7: Use #ifdef to check whether DEBUG is defined.
Input: No input.
Output: Print "Debug Enabled".
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#define DEBUG
int main()
{
#ifdef DEBUG
printf("Debug Enabled");
#else
printf("Debug Disabled");
#endif
return 0;
}
Problem 8: Use #ifndef to compile a block only when FEATURE is not defined.
Input: No input.
Output: Print "Feature Not Defined".
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
int main()
{
#ifndef FEATURE
printf("Feature Not Defined");
#else
printf("Feature Defined");
#endif
return 0;
}
Problem 9: Use #if and #else to select between two versions of a program.
Input: No input.
Output: Print "Version 2".
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#define VERSION 2
int main()
{
#if VERSION == 2
printf("Version 2");
#else
printf("Version 1");
#endif
return 0;
}
Problem 10: Use #elif to select among three program modes.
Input: No input.
Output: Print "Mode 3".
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#define MODE 3
int main()
{
#if MODE == 1
printf("Mode 1");
#elif MODE == 2
printf("Mode 2");
#else
printf("Mode 3");
#endif
return 0;
}
Problem 11: Create a header-style declaration for add() and use the function in a program.
Input: Two integers.
Output: Print their sum.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
int add(int a, int b);
int main()
{
int a, b;
scanf("%d %d", &a, &b);
printf("%d", add(a, b));
return 0;
}
int add(int a, int b)
{
return a + b;
}
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.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#define PI 3.14159
int main()
{
double r;
scanf("%lf", &r);
printf("%.2f", 2.0 * PI * r);
return 0;
}
Problem 13: Create a header-style declaration for a string utility function that counts vowels.
Input: One word.
Output: Print the number of vowels.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
int countVowels(const char text[]);
int main()
{
char text[100];
scanf("%99s", text);
printf("%d", countVowels(text));
return 0;
}
int countVowels(const char text[])
{
int count = 0;
for (int i = 0; text[i] != '\0'; i++)
{
char ch = text[i];
if (ch=='a' || ch=='e' || ch=='i' || ch=='o' || ch=='u' ||
ch=='A' || ch=='E' || ch=='I' || ch=='O' || ch=='U')
{
count++;
}
}
return count;
}
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.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#ifndef MATHUTILS_H
#define MATHUTILS_H
int multiply(int a, int b);
#endif
int main()
{
int a, b;
scanf("%d %d", &a, &b);
printf("%d", multiply(a, b));
return 0;
}
int multiply(int a, int b)
{
return a * b;
}
Problem 15: Create a program that displays __FILE__ and __LINE__ using a controlled #line directive.
Input: No input.
Output: Print "codebhavya.c 500".
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
int main()
{
#line 500 "codebhavya.c"
printf("%s %d", __FILE__, __LINE__);
return 0;
}
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.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
int main()
{
printf("%zu %zu", sizeof(__DATE__), sizeof(__TIME__));
return 0;
}
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.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#define ABS(x) (((x) < 0) ? -(x) : (x))
int main()
{
int n;
scanf("%d", &n);
printf("%d", ABS(n));
return 0;
}
Problem 18: Create a macro to check whether an integer is even.
Input: One integer.
Output: Print "Even" or "Odd".
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#define IS_EVEN(x) (((x) % 2) == 0)
int main()
{
int n;
scanf("%d", &n);
printf("%s", IS_EVEN(n) ? "Even" : "Odd");
return 0;
}
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.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#define SQUARE_MACRO(x) ((x) * (x))
int squareFunction(int x)
{
return x * x;
}
int main()
{
int n;
scanf("%d", &n);
printf("%d %d", SQUARE_MACRO(n), squareFunction(n));
return 0;
}
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.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
/* mathutils.h */
int add(int a, int b);
int subtract(int a, int b);
/* mathutils.c */
int add(int a, int b)
{
return a + b;
}
int subtract(int a, int b)
{
return a - b;
}
/* main.c */
int main()
{
int a, b;
scanf("%d %d", &a, &b);
printf("%d\n", add(a, b));
printf("%d", subtract(a, b));
return 0;
}
15.18 Key Takeaway
#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
🎤 Preprocessor & Header Files — Interview Questions
💡 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
#ifcannot directly test a normal runtime variable.
✍️ Preprocessor & Header Files — Extra Practice Questions
- Create a macro that returns the smaller of three values without using a function.
- Create a DEBUG macro that enables extra diagnostic output only in debug builds.
- Write a header guard for a hypothetical
student.hheader containing a structure declaration and function prototypes. - Use nested
#ifdirectives to select code based on two compile-time configuration macros. - Experiment with
#lineand observe how it changes__FILE__and__LINE__. - Create a three-file calculator project consisting of
calculator.h,calculator.c, andmain.c.