🛠️ Advanced C Preprocessor
The C preprocessor is a program that processes source code before the actual C compilation stage. It handles directives such as #include, #define, conditional compilation, macro expansion, and several compiler-provided predefined macros.
You already learned the fundamentals of preprocessing and header files. This topic goes deeper into the techniques used in real C projects: function-like macros, token pasting, stringification, variadic macros, conditional compilation, feature detection, include guards, predefined macros, debugging, and macro safety.
22.1 What Happens Before Compilation?
C source file | ▼ Preprocessor | ├── #include ├── #define ├── #if / #ifdef ├── macro expansion └── conditional removal | ▼ Preprocessed translation unit | ▼ Compiler | ▼ Object code | ▼ Linker | ▼ Executable The preprocessor does not understand your program in the same semantic way that the compiler does. It transforms preprocessing tokens according to preprocessing rules.
22.2 Object-Like Macros
An object-like macro has an identifier followed by a replacement list.
#define PI 3.14159 #define MAX_STUDENTS 100 #define COMPANY_NAME "CodeBhavya" Whenever the macro identifier is encountered in an appropriate preprocessing context, it can be replaced by its replacement list.
22.3 Function-Like Macros
A function-like macro is defined using parentheses immediately after the macro name.
#define SQUARE(x) ((x) * (x)) Example:
int result = SQUARE(5); After macro expansion, the compiler effectively sees:
int result = ((5) * (5)); 22.4 Why Parentheses Matter in Macros
Unsafe macro:
#define SQUARE(x) x * x Consider:
int result = SQUARE(2 + 3); Expansion becomes:
2 + 3 * 2 + 3 Because multiplication has higher precedence, this does not mean (2 + 3) * (2 + 3).
Safer form:
#define SQUARE(x) ((x) * (x)) 22.5 The Double-Parentheses Rule
For a simple expression macro, a strong general pattern is:
#define NAME(parameter) ((parameter) expression (parameter)) For example:
#define DOUBLE(x) ((x) * 2) #define ABS(x) (((x) < 0) ? -(x) : (x)) #define MAX(a, b) (((a) > (b)) ? (a) : (b)) Parenthesizing both the parameters and the complete replacement expression reduces precedence-related surprises.
22.6 Macro Arguments Are Not Evaluated Like Function Arguments
Consider:
#define SQUARE(x) ((x) * (x)) int i = 5; int result = SQUARE(i++); Expansion is conceptually:
((i++) * (i++)) The argument appears twice. This creates a serious problem because modifying the same scalar object more than once without an appropriate sequencing relationship can result in undefined behavior.
22.7 Macro vs Function
| Feature | Macro | Function |
|---|---|---|
| Processed by | Preprocessor | Compiler |
| Type checking | Not performed by the preprocessor | Compiler performs normal type checking |
| Argument evaluation | Replacement-based | Normal function-call semantics |
| Debugging | Can be harder | Usually easier |
| Can have side effects from repeated arguments? | Yes | Normal parameter evaluation rules apply |
22.8 The Stringification Operator #
The preprocessor provides the # operator inside a macro replacement list to convert a macro argument into a string literal.
#define SHOW_NAME(x) printf(#x "\n") Then:
SHOW_NAME(CodeBhavya); produces code equivalent in effect to:
printf("CodeBhavya" "\n"); 22.9 Stringification Example
#include <stdio.h> #define SHOW_VALUE(x) \ printf("%s = %d\n", #x, (x)) int main(void) { int marks = 95; SHOW_VALUE(marks); return 0; } Output:
marks = 95 22.10 Important Stringification Behavior
The # operator stringifies the spelling of the argument passed to the macro. This is useful for diagnostics, logging, assertions, and debugging helpers.
#define TRACE_VALUE(x) \ printf("%s = %d\n", #x, (x)) 22.11 The Token-Pasting Operator ##
The ## operator joins two preprocessing tokens.
#define JOIN(a, b) a ## b For example:
int JOIN(student, 1) = 90; can produce an identifier equivalent to:
int student1 = 90; 22.12 Token Pasting Example
#include <stdio.h> #define MAKE_NAME(prefix, number) prefix ## number int main(void) { int student1 = 95; printf("%d\n", MAKE_NAME(student, 1)); return 0; } Output:
95 22.13 Stringification vs Token Pasting
| Operator | Purpose | Example |
|---|---|---|
# | Convert macro argument spelling to string literal | #x |
## | Join preprocessing tokens | a ## b |
Macro argument | +---- # ----► string | +---- ## ---► combined token 22.14 Two-Level Macro Expansion
When token pasting or stringification is involved, understanding macro expansion order becomes important.
A common helper pattern is:
#define CAT(a, b) CAT_IMPL(a, b) #define CAT_IMPL(a, b) a ## b This two-level structure allows an argument to be expanded before it is pasted in situations where direct ## use would inhibit that expansion.
22.15 Variadic Macros
Modern C supports variadic macros using ... and __VA_ARGS__.
#define LOG(...) printf(__VA_ARGS__) Usage:
LOG("Hello %s\n", "C"); This is useful when the number of arguments varies.
22.16 Practical Variadic Logging Macro
#include <stdio.h> #define LOG(fmt, ...) \ printf("[LOG] " fmt "\n", __VA_ARGS__) int main(void) { int score = 95; LOG("Score = %d", score); return 0; } Variadic macro behavior has details that depend on the exact C standard version and compiler support, especially when the variable argument list is empty. Design such macros carefully for the target language mode.
22.17 Conditional Compilation
Conditional compilation allows portions of source code to be included or excluded before compilation.
#if CONDITION /* compiled when CONDITION is true */ #endif Common directives include:
#if#ifdef#ifndef#elif#else#endif
22.18 #ifdef
#ifdef tests whether a macro is defined.
#define DEBUG #ifdef DEBUG printf("Debug mode\n"); #endif 22.19 #ifndef
#ifndef tests whether a macro is not defined.
#ifndef VALUE #define VALUE 100 #endif 22.20 #if and Numeric Conditions
#define VERSION 3 #if VERSION >= 3 printf("Version 3 or later\n"); #else printf("Older version\n"); #endif The condition is evaluated during preprocessing.
22.21 #elif
#if defined(WINDOWS) printf("Windows\n"); #elif defined(LINUX) printf("Linux\n"); #else printf("Other platform\n"); #endif This is useful when source code must support multiple environments.
22.22 defined Operator
Inside a preprocessing conditional expression, defined can test whether a macro exists.
#if defined(DEBUG) printf("Debug enabled\n"); #endif It can also be written as:
#if defined DEBUG printf("Debug enabled\n"); #endif 22.23 Feature Flags
Conditional compilation can implement feature switches.
#define ENABLE_LOGGING #ifdef ENABLE_LOGGING #define LOG(message) printf("%s\n", message) #else #define LOG(message) #endif When logging is disabled, the macro expands to nothing.
22.24 Debug and Release Builds
A common project technique is to compile diagnostic code only in development builds.
#ifdef DEBUG #define DEBUG_PRINT(x) printf("DEBUG: %s\n", x) #else #define DEBUG_PRINT(x) #endif The build system can define DEBUG using compiler options instead of modifying the source file.
22.25 Predefined Macros
The C implementation provides several useful predefined macros.
| Macro | Meaning |
|---|---|
__FILE__ | Name of the current source file. |
__LINE__ | Current source line number. |
__DATE__ | Date of preprocessing/translation as provided by the implementation. |
__TIME__ | Time of preprocessing/translation as provided by the implementation. |
__STDC__ | Indicates standard C implementation support according to the applicable standard rules. |
22.26 __FILE__ and __LINE__
#include <stdio.h> int main(void) { printf("File: %s\n", __FILE__); printf("Line: %d\n", __LINE__); return 0; } These macros are especially useful in diagnostic messages.
22.27 Building a Diagnostic Macro
#include <stdio.h> #define REPORT(message) \ printf("File: %s | Line: %d | %s\n", \ __FILE__, __LINE__, message) int main(void) { REPORT("Program started"); return 0; } 22.28 __func__
__func__ is a predefined identifier provided inside functions. It is not a preprocessor macro.
#include <stdio.h> void test(void) { printf("Function: %s\n", __func__); } __FILE__ and __LINE__ are predefined macros, while __func__ is a predefined identifier supplied for use within a function. 22.29 Include Guards
Include guards prevent a header's contents from being processed repeatedly within the same translation unit.
#ifndef STUDENT_H #define STUDENT_H typedef struct { int id; char name[50]; } Student; void printStudent(Student student); #endif 22.30 Why Include Guards Matter
Suppose several source files indirectly include the same header:
main.c | +---- student.h | +---- college.h | +---- student.h Without guard: student.h may be processed repeatedly With guard: first inclusion → contents included later inclusion → contents skipped 22.31 #pragma once
Many compilers support:
#pragma once as a convenient way to request one-time inclusion of a header.
However, traditional include guards use standard preprocessor facilities and are highly portable.
#ifndef MY_HEADER_H #define MY_HEADER_H /* declarations */ #endif #pragma once 22.32 Header File Design
A well-designed header usually contains declarations, type definitions, macros that form part of the interface, and other information required by users of the module.
Implementation details that do not need to be exposed can remain in the corresponding source file.
22.33 Conditional Header Content
#ifndef CB_CONFIG_H #define CB_CONFIG_H #define CB_VERSION_MAJOR 1 #define CB_VERSION_MINOR 0 #endif This pattern prevents repeated inclusion and can also provide project-wide configuration information.
22.34 Compile-Time Assertions
Modern C provides _Static_assert for compile-time checks.
#include <limits.h> _Static_assert(sizeof(int) >= 2, "int must be at least 2 bytes"); This is a language feature, not a preprocessor directive, but it is closely related to compile-time validation.
22.35 Compile-Time Configuration
Conditional preprocessing can select configuration-specific code.
#if defined(CB_SMALL_BUILD) #define MAX_BUFFER 128 #else #define MAX_BUFFER 1024 #endif The build system can define CB_SMALL_BUILD when a smaller configuration is required.
22.36 Platform Selection
Large C applications sometimes need platform-specific implementations.
#if defined(_WIN32) printf("Windows build\n"); #elif defined(__linux__) printf("Linux build\n"); #elif defined(__APPLE__) printf("Apple build\n"); #else printf("Unknown platform\n"); #endif 22.37 Compiler Detection
Projects sometimes use compiler-specific predefined macros:
#if defined(__GNUC__) printf("GNU-compatible compiler\n"); #endif Such checks should be isolated carefully because compiler-specific branches can reduce portability.
22.38 Feature Detection vs Compiler Detection
When possible, it is better to determine whether the required feature exists rather than simply identifying a compiler.
| Approach | Idea |
|---|---|
| Compiler detection | "Am I compiling with compiler X?" |
| Feature detection | "Does the implementation provide feature X?" |
Feature-oriented checks generally make portable code easier to maintain.
22.39 Macro Naming Conventions
Project-wide macros are commonly written in uppercase:
#define CB_MAX_STUDENTS 100 #define CB_ENABLE_LOGGING A project-specific prefix reduces the chance of collisions with identifiers from libraries or other headers.
22.40 Reserved Macro Names
CB_... instead. 22.41 Multi-Line Macros
A backslash at the end of a preprocessing line can continue a macro replacement list onto the next physical source line.
#define PRINT_SUM(a, b) \ do { \ printf("%d\n", (a) + (b)); \ } while (0) 22.42 The do-while(0) Macro Pattern
Consider a macro containing multiple statements:
#define BAD_PRINT(x) printf("%d\n", x); printf("done\n"); Using it inside an if can produce surprising structure.
A safer multi-statement pattern is:
#define GOOD_PRINT(x) \ do { \ printf("%d\n", (x)); \ printf("done\n"); \ } while (0) The macro behaves syntactically like one statement when used with a trailing semicolon.
22.43 Macro Safety Example
if (condition) GOOD_PRINT(value); else printf("Other\n"); The do { ... } while (0) structure makes this usage much safer.
22.44 Macro Scope and Lifetime
Macros do not have C variable scope or storage duration. They exist in the preprocessing environment from their definition until they are undefined or preprocessing reaches the end of the translation unit.
#define VALUE 100 printf("%d\n", VALUE); #undef VALUE /* VALUE is no longer defined here */ 22.45 #undef
The #undef directive removes a macro definition.
#define SIZE 10 #undef SIZE #define SIZE 20 After the second definition, SIZE expands to 20.
22.46 Redefining Macros
Macro redefinition should be done carefully.
#define VALUE 10 #define VALUE 20 Compilers can diagnose incompatible macro redefinitions. Avoid unnecessary redefinitions because they make code difficult to reason about.
22.47 Push/Pop Macro State
Some compilers provide non-standard pragmas for temporarily saving and restoring macro definitions.
Because these facilities are compiler-specific, portable C code should not depend on them without a clear platform requirement.
22.48 Macro Expansion Trace
#define VALUE 10 #define DOUBLE(x) ((x) * 2) int result = DOUBLE(VALUE); DOUBLE(VALUE) | ▼ ((VALUE) * 2) | ▼ ((10) * 2) | ▼ 14? ← No | ▼ 20 The important lesson is that macro expansion happens before normal compiler expression evaluation.
22.49 Nested Macros
#define BASE 10 #define DOUBLE(x) ((x) * 2) #define RESULT DOUBLE(BASE) Using:
int x = RESULT; allows nested macro replacement to produce the final source expression.
22.50 Macro Constants vs const Variables
| Property | Macro | const object |
|---|---|---|
| Processed by preprocessor? | Yes | No |
| Has a C type? | Replacement text itself has no C object type | Yes |
| Debugger visibility | Often less convenient | Normal object semantics |
| Scope | Preprocessing region | C language scope |
For typed constants, a const object or enumeration may often be clearer than a macro.
22.51 Macro Constants vs enum
#define RED 1 #define GREEN 2 #define BLUE 3 can sometimes be better represented by:
enum Color { RED, GREEN, BLUE }; The appropriate choice depends on the requirement.
22.52 Generic-Looking Macros
Macros can accept many kinds of expressions:
#define MAX(a, b) (((a) > (b)) ? (a) : (b)) This appears generic, but it has limitations because arguments can be evaluated more than once and the expressions may have unusual side effects or type interactions.
22.53 Advanced Macro Debugging
When a macro behaves unexpectedly, mentally perform these steps:
- Write the macro definition.
- Replace the macro call with the replacement list.
- Substitute the arguments.
- Check parentheses.
- Check whether an argument appears more than once.
- Then analyze the resulting C expression normally.
22.54 Macro Debugging Example
#define ADD(a, b) a + b int result = ADD(10, 20) * 2; Expansion:
10 + 20 * 2 Result:
50 Safer:
#define ADD(a, b) ((a) + (b)) Now:
((10) + (20)) * 2 Result:
60 22.55 Advanced Preprocessor Workflow
Source Code | ▼ Identify # directives | ├── #include | └── insert header content | ├── #define | └── register macro | ├── macro call | └── expand replacement | ├── #if / #ifdef | └── choose source sections | └── #undef └── remove macro | ▼ Preprocessed source | ▼ Compiler 22.56 Practical Example — Debug Logger
#include <stdio.h> #ifdef DEBUG #define LOG(message) \ printf("[DEBUG] %s:%d: %s\n", \ __FILE__, __LINE__, message) #else #define LOG(message) do { } while (0) #endif int main(void) { LOG("Program started"); printf("Application running\n"); return 0; } This pattern demonstrates conditional compilation, predefined macros, and a safe empty macro branch.
22.57 Practical Example — Assertion-Like Macro
#include <stdio.h> #define CB_REQUIRE(condition) \ do { \ if (!(condition)) { \ printf("Requirement failed: %s\n", #condition); \ } \ } while (0) int main(void) { int age = 17; CB_REQUIRE(age >= 18); return 0; } Stringification allows the diagnostic to display the condition that failed.
22.58 Practical Example — Token Generation
#include <stdio.h> #define CREATE_VARIABLE(prefix, number) prefix ## number int main(void) { int value1 = 100; printf("%d\n", CREATE_VARIABLE(value, 1)); return 0; } The ## operator constructs the token value1.
22.59 Macro Pitfalls Checklist
Missing parentheses.
Multiple evaluation of arguments.
Macros containing multiple statements without a safe wrapper.
Unexpected name collisions.
Overusing conditional compilation.
Using implementation-specific macros without documentation.
23.15 Quick Revision
#define → Defines a macro.
📌
#undef → Removes a macro definition.
📌
#include → Includes a header.
📌
#ifdef → Checks whether a macro is defined.
📌
#ifndef → Checks whether a macro is not defined.
📌
#if → Conditional preprocessing expression.
📌
#elif → Additional condition.
📌
#else → Alternative branch.
📌
#endif → Ends conditional compilation.
📌
# → Stringizes a macro argument.
📌
## → Joins preprocessing tokens.
📌
__FILE__ → Current source file.
📌
__LINE__ → Current source line.
📌
__DATE__ → Compilation date.
📌
__TIME__ → Compilation time.
🎬 Advanced Preprocessor — Preprocessing Pipeline
Follow how directives, headers, macros and conditional compilation transform a C source file before normal compilation.
🎬 C Preprocessing Pipeline Visualizer
🔎 Program Tracing — Advanced C Preprocessor
See the preprocessing effect first, then trace only the executable C statements that remain at runtime.
—
23.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.
#define introduces an object-like or function-like macro.
#undef causes the specified macro name to stop being defined from that point onward.
The # operator converts a macro argument's spelling into a string literal during macro replacement.
The ## operator combines adjacent preprocessing tokens in a macro replacement list.
A traditional include guard begins with #ifndef, then #define, and ends with #endif.
__LINE__ expands to the current source line number as an integer constant.
__FILE__ expands to a character string literal naming the current source file.
Wrapping multiple statements in do { } while (0) makes the macro behave like one statement in many control-flow contexts.
#define is handled during preprocessing before normal compilation.
If a macro replacement uses an argument more than once, an argument expression with side effects can also be evaluated more than once.
23.17 🎯 Practice Problems
Practice macro definitions, macro safety, stringizing, token pasting, multi-statement macros, include guards, conditional compilation, predefined macros, debugging macros, and small multi-file designs. Use 💻 Solve It Yourself first, open Hint only when needed, and use Show Program after attempting the problem.
Problem 1: Define a macro named PI and use it to calculate the area of a circle.
Input: One floating-point radius.
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.141592653589793
int main()
{
double r;
scanf("%lf", &r);
printf("%.2f", PI * r * r);
return 0;
}
Problem 2: Create a function-like macro SQUARE(x) and use it to square 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 x;
scanf("%d", &x);
printf("%d", SQUARE(x));
return 0;
}
Problem 3: Demonstrate why #define SQUARE(x) x * x is unsafe by comparing the unsafe and safe macro for the expression 2 + 3.
Input: No input.
Output: Print Unsafe=11 and Safe=25.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#define BAD_SQUARE(x) x * x
#define GOOD_SQUARE(x) ((x) * (x))
int main()
{
printf("Unsafe=%d\n", BAD_SQUARE(2 + 3));
printf("Safe=%d", GOOD_SQUARE(2 + 3));
return 0;
}
Problem 4: Create a macro named MAX(a,b) and print the larger 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 5: Create a macro named MIN(a,b) and print the smaller 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 6: Write a macro that prints the name and value of a variable using the # stringizing operator.
Input: One integer value stored in variable marks.
Output: Print marks=value.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#define SHOW(x) printf("%s=%d", #x, (x))
int main()
{
int marks;
scanf("%d", &marks);
SHOW(marks);
return 0;
}
Problem 7: Write a macro that combines two tokens using ## and use it to access a variable named value1.
Input: One integer.
Output: Print the value stored in value1.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#define CONCAT(a,b) a##b
int main()
{
int value1;
scanf("%d", &value1);
printf("%d", CONCAT(value, 1));
return 0;
}
Problem 8: Create a multi-statement macro using do { } while (0) that increments two integers.
Input: Two integers x and y.
Output: Print the incremented values.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#define INCREMENT_BOTH(a,b) \
do { \
(a)++; \
(b)++; \
} while (0)
int main()
{
int x, y;
scanf("%d %d", &x, &y);
INCREMENT_BOTH(x, y);
printf("%d %d", x, y);
return 0;
}
Problem 9: Create a traditional include guard for college.h and demonstrate a macro protected by that guard.
Input: No input.
Output: Print CodeBhavya College.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
/* college.h concept */
#ifndef COLLEGE_H
#define COLLEGE_H
#define COLLEGE_NAME "CodeBhavya College"
#endif
int main()
{
printf("%s", COLLEGE_NAME);
return 0;
}
Problem 10: Write a program using #ifdef DEBUG to include a debugging message.
Input: No input.
Output: Print Debug mode.
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 mode");
#else
printf("Release mode");
#endif
return 0;
}
Problem 11: Write a program using #if, #elif and #else to select one compile-time level.
Input: No input.
Output: Print Intermediate.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#define LEVEL 2
int main()
{
#if LEVEL == 1
printf("Beginner");
#elif LEVEL == 2
printf("Intermediate");
#else
printf("Advanced");
#endif
return 0;
}
Problem 12: Write a program using __FILE__. For deterministic online judging, use a #line directive to give the source a known logical file name.
Input: No input.
Output: Print demo.c.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
int main()
{
#line 100 "demo.c"
printf("%s", __FILE__);
return 0;
}
Problem 13: Write a program using __LINE__. Use #line so the expected line number is deterministic.
Input: No input.
Output: Print 700.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
int main()
{
#line 700
printf("%d", __LINE__);
return 0;
}
Problem 14: Use __DATE__ and __TIME__. Because their actual text changes at compile time, print their standard string lengths so the answer can be checked automatically.
Input: No input.
Output: Print DATE_LEN=11 TIME_LEN=8.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#include <string.h>
int main()
{
const char *dateText = __DATE__;
const char *timeText = __TIME__;
printf("DATE_LEN=%zu TIME_LEN=%zu",
strlen(dateText), strlen(timeText));
return 0;
}
Problem 15: Create a macro DOUBLE(x) that correctly handles DOUBLE(2 + 3).
Input: No input.
Output: Print 10.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#define DOUBLE(x) ((x) * 2)
int main()
{
printf("%d", DOUBLE(2 + 3));
return 0;
}
Problem 16: Explain why SQUARE(i++) can be dangerous when SQUARE uses its argument more than once. Do not execute the unsafe expression.
Input: No input.
Output: Print a concise explanation.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
int main()
{
printf("SQUARE(i++) is unsafe because the macro may evaluate i++ more than once.");
return 0;
}
Problem 17: Create a project containing main.c, student.c and student.h. For the online judge, represent the three-file design in one runnable file while preserving the header declaration and source implementation roles.
Input: Student roll number and marks.
Output: Print roll and marks.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
/* student.h concept */
#ifndef STUDENT_H
#define STUDENT_H
typedef struct
{
int roll;
float marks;
} Student;
void printStudent(Student s);
#endif
/* student.c concept */
void printStudent(Student s)
{
printf("%d %.1f", s.roll, s.marks);
}
/* main.c concept */
int main()
{
Student s;
scanf("%d %f", &s.roll, &s.marks);
printStudent(s);
return 0;
}
Problem 18: Create a debugging macro using __FILE__, __LINE__ and #. Use #line to make the judged output deterministic.
Input: One integer stored in variable x.
Output: Print debug_demo.c:900 x=value.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
#define DEBUG_VAR(x) \
printf("%s:%d %s=%d", __FILE__, __LINE__, #x, (x))
int main()
{
int x;
scanf("%d", &x);
#line 900 "debug_demo.c"
DEBUG_VAR(x);
return 0;
}
Problem 19: Explain the difference between a macro and a function in two concise lines.
Input: No input.
Output: Print one line for macros and one line for functions.
C Code Editor
Sample Input
Program Output
Run your program to see the output.
Test Cases
#include <stdio.h>
int main()
{
printf("Macro: preprocessing replacement.\n");
printf("Function: compiled C code with typed parameters and return behavior.");
return 0;
}
Problem 20: Create a small program demonstrating #define, #undef, conditional compilation, #, ##, __FILE__ and __LINE__.
Input: No input.
Output: Print a deterministic summary of all demonstrated features.
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
#define STRINGIZE(x) #x
#define CONCAT(a,b) a##b
#define FEATURE 1
#if FEATURE
#define FEATURE_TEXT "ON"
#else
#define FEATURE_TEXT "OFF"
#endif
int main()
{
int item1 = 7;
#line 1000 "advanced_demo.c"
printf("VALUE=%d FEATURE=%s NAME=%s TOKEN=%d FILE=%s LINE=%d",
VALUE,
FEATURE_TEXT,
STRINGIZE(CodeBhavya),
CONCAT(item, 1),
__FILE__,
__LINE__);
return 0;
}
23.18 Interview Questions
23.19 Key Takeaway
🎯 Use parentheses carefully in function-like macros.
🎯 Avoid expressions with side effects as macro arguments when they may be evaluated multiple times.
🎯 Use include guards in reusable header files.
🎯
# converts a macro argument into a string.
🎯
## combines preprocessing tokens.
🎯 Conditional compilation is useful for debugging, platform-specific code and optional features.
🎯 Prefer normal functions when they provide clearer and safer behavior than macros.
✍️ C Programming Practice
Strengthen your C programming skills by solving problems based on the concepts you have learned.
💡 Advanced C Preprocessor — Placement Tips
- For function-like macros, parenthesize each parameter and the complete replacement expression whenever the macro is intended to behave like an expression.
- Never assume a macro argument is evaluated once. Check how many times the parameter appears in the replacement list before passing expressions with side effects.
- Know the difference between
#stringizing and##token pasting; both are frequent interview questions. - Use include guards for reusable headers and keep declarations in headers while definitions generally remain in source files.
- Use conditional compilation for debug builds, platform-specific sections and optional features, but avoid making the codebase unnecessarily difficult to follow.
- Prefer a normal function or inline function when it provides clearer type checking and safer single evaluation than a macro.
✍️ Advanced C Preprocessor — Extra Practice Questions
- Create a
CLAMP(x, low, high)macro and discuss its multiple-evaluation risk. - Create a debug macro that is completely removed when
DEBUGis not defined. - Use
##to generate three related variable names from one macro pattern. - Create a header with an include guard and declarations for a small calculator module.
- Use platform-selection macros to choose between Windows-like and Linux-like messages without changing
main(). - Rewrite three unsafe function-like macros as ordinary functions or inline functions and compare readability.