🛠️ 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.

Core idea: The preprocessor works primarily with tokens and source text before the compiler performs normal C semantic analysis. A macro is not a function and should never be treated as one.

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));
A macro does not perform a normal function call. It performs preprocessing replacement. Therefore macro arguments can behave differently from function arguments.

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.

Placement rule: Do not design ordinary expression macros that evaluate an argument multiple times unless you deliberately understand the consequences.

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__); }
Interview point: __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.

Portable pattern
#ifndef MY_HEADER_H #define MY_HEADER_H /* declarations */ #endif
Compiler-supported shortcut
#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
Do not assume that every compiler uses exactly the same implementation-defined platform macros. Check the compiler/platform documentation when writing portable platform detection code.

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

Avoid defining identifiers beginning with underscores in ways reserved by the implementation. Names beginning with an underscore followed by an uppercase letter, and names beginning with two underscores, are reserved in many contexts. Use project-specific names such as 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.

When type safety and single evaluation are important, consider a normal function or another language feature instead of forcing everything into a macro.

22.53 Advanced Macro Debugging

When a macro behaves unexpectedly, mentally perform these steps:

  1. Write the macro definition.
  2. Replace the macro call with the replacement list.
  3. Substitute the arguments.
  4. Check parentheses.
  5. Check whether an argument appears more than once.
  6. 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

Pitfall 1

Missing parentheses.

Pitfall 2

Multiple evaluation of arguments.

Pitfall 3

Macros containing multiple statements without a safe wrapper.

Pitfall 4

Unexpected name collisions.

Pitfall 5

Overusing conditional compilation.

Pitfall 6

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.
INTERACTIVE LEARNING

🎬 Advanced Preprocessor — Preprocessing Pipeline

Follow how directives, headers, macros and conditional compilation transform a C source file before normal compilation.

PROGRAM TRACING

🔎 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.

1. Which directive defines a macro?
2. Which directive removes a macro definition?
3. Which operator stringizes a macro argument?
4. Which operator performs token pasting?
5. Which directive is commonly used for include guards?
6. What does __LINE__ represent?
7. What does __FILE__ provide?
8. Which technique is commonly used for multi-statement macros?
9. Which stage processes #define?
10. What is a common problem with function-like macros?
PRACTICE

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.

📈 Advanced C Preprocessor Practice Progress
Solved 0 / 20
Completed with Solution 0
Total Score 0 / 2000
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 — Area of a Circle

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.

2. Safe SQUARE(x) Macro

Problem 2: Create a function-like macro SQUARE(x) and use it to square an integer.

Input: One integer.

Output: Print its square.

3. Why SQUARE(x) x * x is Unsafe

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.

4. MAX(a,b) Macro

Problem 4: Create a macro named MAX(a,b) and print the larger of two integers.

Input: Two integers.

Output: Print the larger value.

5. MIN(a,b) Macro

Problem 5: Create a macro named MIN(a,b) and print the smaller of two integers.

Input: Two integers.

Output: Print the smaller value.

6. Stringize Variable Name and Value

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.

7. Token Pasting with ##

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.

8. Multi-Statement Macro with do { } while (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.

9. Include Guard for college.h

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.

10. Conditional Debug Build with #ifdef DEBUG

Problem 10: Write a program using #ifdef DEBUG to include a debugging message.

Input: No input.

Output: Print Debug mode.

11. #if / #elif / #else

Problem 11: Write a program using #if, #elif and #else to select one compile-time level.

Input: No input.

Output: Print Intermediate.

12. Print __FILE__

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.

13. Print __LINE__

Problem 13: Write a program using __LINE__. Use #line so the expected line number is deterministic.

Input: No input.

Output: Print 700.

14. Use __DATE__ and __TIME__

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.

15. Correct DOUBLE(x) Macro

Problem 15: Create a macro DOUBLE(x) that correctly handles DOUBLE(2 + 3).

Input: No input.

Output: Print 10.

16. Why SQUARE(i++) is Dangerous

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.

17. main.c + student.c + student.h Project

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.

18. Debugging Macro with __FILE__, __LINE__ and #

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.

19. Macro vs Function

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.

20. Advanced Preprocessor Mini Demonstration

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.

23.18 Interview Questions

1. What is the C preprocessor?
2. What is the difference between a macro and a function?
3. What is an object-like macro?
4. What is a function-like macro?
5. Why should macro arguments normally be parenthesized?
6. What is the purpose of the # operator?
7. What is the purpose of ##?
8. What are include guards?
9. Why is do { } while (0) used in multi-statement macros?
10. What is conditional compilation?
11. What is the difference between #ifdef and #if?
12. What does #undef do?
13. What are predefined macros?
14. What is the difference between #define and const?
15. Why can macros be dangerous when arguments have side effects?

23.19 Key Takeaway

🎯 The preprocessor runs before compilation.

🎯 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.

01 Understand Read the problem carefully.
02 Think Develop your approach before coding.
03 Code Write and test your solution.
04 Improve Compare your solution and learn.
PLACEMENT TIPS

💡 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.
EXTRA PRACTICE

✍️ Advanced C Preprocessor — Extra Practice Questions

  1. Create a CLAMP(x, low, high) macro and discuss its multiple-evaluation risk.
  2. Create a debug macro that is completely removed when DEBUG is not defined.
  3. Use ## to generate three related variable names from one macro pattern.
  4. Create a header with an include guard and declarations for a small calculator module.
  5. Use platform-selection macros to choose between Windows-like and Linux-like messages without changing main().
  6. Rewrite three unsafe function-like macros as ordinary functions or inline functions and compare readability.
← Previous Topic: Storage Classes Next Topic: C Practice →