๐Ÿ› ๏ธ 23. 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: A macro replaces preprocessing tokens before normal C type checking and execution. To understand it, first expand the macro, then read the resulting C expression.

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

Macro replacement works with token sequences, not runtime variable values. The compiler checks types and expression rules after preprocessing has produced the source it will translate.

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

23.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));
The argument tokens are substituted into the replacement list. A function call evaluates its argument expressions for one call; a macro can repeat or omit the substituted expression.

23.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))

23.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))

Put parentheses around each parameter use and around the whole expression. These protect grouping; they do not protect against repeated evaluation.

23.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++))

Here the expanded multiplication contains i++ twice without a guaranteed order between the changes. Its behavior is undefined. Do not guess a numeric output for this example.

Placement rule: If a macro repeats an argument, avoid passing an expression that changes state. A small function is often clearer when each argument must be evaluated once.

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

23.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");

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

23.10 Important Stringification Behavior

The # With #x, the argument spelling is turned into a string rather than evaluated. If x is itself a macro name, direct stringification does not first expand that argument. Use a helper macro when you want the expanded valueโ€™s spelling.

#define TRACE_VALUE(x) \
    printf("%s = %d\n", #x, (x))

23.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;

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

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

23.14 Two-Level Macro Expansion

Direct # and ## use keeps the corresponding argument from the usual expansion before that operation. A second macro layer can request expansion first.

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.

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

23.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;
}

The LOG(fmt, ...) example needs at least one extra argument in C17. LOG("Score = %d", score) fits that form; LOG("Ready") does not. The LOG(...) form can accept a format string alone because that string is itself a variadic argument.

23.17 Conditional Compilation

These conditions select which source is compiled, rather than which branch runs each time the program executes. A macro can be defined by the source or by a build option.

#if CONDITION
/* compiled when CONDITION is true */
#endif

Common directives include:

  • #if
  • #ifdef
  • #ifndef
  • #elif
  • #else
  • #endif

23.18 #ifdef

#ifdef tests whether a macro is defined.

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

23.19 #ifndef

#ifndef tests whether a macro is not defined.

#ifndef VALUE
#define VALUE 100
#endif

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

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

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

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

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

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

23.26 __FILE__ and __LINE__

#include <stdio.h>

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

These macros help a diagnostic identify where it came from. Their values describe the source location, not a line currently typed by the user.

23.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;
}

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

23.29 Include Guards

A guard prevents its header contents from being processed twice in one translation unit. It does not prevent an ordinary external variable definition in a header from being duplicated across different source files.

#ifndef STUDENT_H
#define STUDENT_H

typedef struct { int id; char name[50]; } Student;
void printStudent(Student student);

#endif

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

23.31 #pragma once

Many compilers support:

#pragma once

as a convenient way to request one-time inclusion of a header.

A conventional guard uses standard directives. #pragma once is widely supported, but it is a compiler feature rather than an ISO C directive with the same portable guarantee.

Portable pattern
#ifndef MY_HEADER_H
#define MY_HEADER_H
/* declarations */
#endif
Compiler-supported shortcut
#pragma once

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

Keep private helper functions and ordinary object definitions in the source file where appropriate. A widely included header should not give each translation unit another external definition of the same object.

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

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

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

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

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

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

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

23.40 Reserved Macro Names

Avoid names beginning with two underscores or an underscore followed by an uppercase letter. Those forms are reserved to the implementation. A project prefix is clearer; use names such as CB_... instead.

23.41 Multi-Line Macros

Place a backslash immediately before the newline to continue a directive. Do not put spaces after that backslash when writing portable source. The continued physical lines form one logical directive.

#define PRINT_SUM(a, b) \
    do { \
        printf("%d\n", (a) + (b)); \
    } while (0)

23.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 wrapper makes the replacement act as one statement when the caller adds a semicolon. It does not automatically make repeated arguments or name collisions safe.

23.43 Macro Safety Example

if (condition) GOOD_PRINT(value); else printf("Other\n");

The do { ... } while (0) structure makes this usage much safer.

23.44 Macro Scope and Lifetime

A macro definition is active from its directive until #undef or the end of the translation unit. Braces around a C function or block do not create a local scope for that macro.

#define VALUE 100
printf("%d\n", VALUE);
#undef VALUE
/* VALUE is no longer defined here */

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

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

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

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

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

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

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

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

Use a typed function when it makes the operation easier to check. If a macro is needed, inspect both how often each argument appears and how its type affects the expanded expression.

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

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

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

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

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

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

23.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.60 Compile-Time Diagnostics with _Static_assert

A static assertion asks the compiler to check a constant condition. It catches an unsupported build assumption before the program runs. _Static_assert is standard from C11; C23 also provides the static_assert keyword.

Complete C program:

#include <stdio.h>
#include <limits.h>

_Static_assert(CHAR_BIT >= 8, "At least eight bits per byte required");

int main(void)
{
    printf("Bits per byte: %d\n", CHAR_BIT);
    return 0;
}

Step-by-step explanation:

  1. The compiler checks the condition while translating the source.
  2. If the condition is false, compilation fails with a diagnostic.
  3. On typical systems the program prints Bits per byte: 8.
Remember: assert from assert.h checks a condition during execution when enabled and can be disabled with NDEBUG. A static assertion is checked during translation and does not depend on that runtime-assert setting.

23.61 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.62 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.63 ๐ŸŽฏ 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 / 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 โ€” 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.64 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.65 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 Standard Library Essentials โ†’