Chapter 15: The Preprocessor
- LO 15.1: Understand the role of the C preprocessor
- LO 15.2: Implement macro substitution using #define
- LO 15.3: Use file inclusion directives (#include)
- LO 15.4: Apply conditional compilation directives
- LO 15.5: Work with ANSI additions like #pragma and #error
15.1 Introduction to the Preprocessor
The C preprocessor is a program that processes the source code before it passes through the compiler. It operates under the control of preprocessor directives, which begin with the # symbol in column one.
Key characteristics of preprocessor directives:
- All directives begin with # in the first column
- No semicolon is required at the end
- They are processed before compilation
- They can be placed anywhere in the program
15.2 Preprocessor Directives
| Directive | Function |
|---|---|
#define |
Defines a macro substitution |
#undef |
Undefines a macro |
#include |
Specifies files to be included |
#ifdef |
Tests if a macro is defined |
#ifndef |
Tests if a macro is not defined |
#if |
Tests a compile-time condition |
#else |
Provides alternatives when #if fails |
#elif |
Establishes if-else-if sequence |
#endif |
Specifies end of #if |
#error |
Produces diagnostic messages |
#pragma |
Implementation-oriented instructions |
15.3 Macro Substitution (#define)
Macro substitution is a process where an identifier is replaced by a predefined string. The general form is:
#define identifier string
Simple Macro Substitution
#define PI 3.14159 #define STRENGTH 100 #define PASS_MARK 50 area = PI * r * r; if (marks >= PASS_MARK) ...
📝 Worked-Out Problem 15.1
Using symbolic constants with #define:
#include <stdio.h>
#define PI 3.14159
#define HEIGHT 10
int main() {
float radius, area, volume;
printf("Enter radius: ");
scanf("%f", &radius);
area = PI * radius * radius;
volume = area * HEIGHT;
printf("Area of circle = %.2f\n", area);
printf("Volume of cylinder = %.2f\n", volume);
return 0;
}
Enter radius: 5
Area of circle = 78.54
Volume of cylinder = 785.40
- No semicolon at the end of #define line
- The # symbol must be in the first column
- Macros are not variables - cannot be changed by assignment
- Use parentheses to avoid evaluation errors
Macros with Arguments
Macros can take arguments, making them behave like functions (but with text substitution).
#define CUBE(x) ((x) * (x) * (x)) #define MAX(a,b) ((a) > (b) ? (a) : (b)) #define IS_EVEN(x) ((x) % 2 == 0)
📝 Worked-Out Problem 15.2
Macros with arguments - importance of parentheses:
#include <stdio.h>
// Without parentheses - WRONG!
#define SQUARE_BAD(x) x * x
// With parentheses - CORRECT
#define SQUARE(x) ((x) * (x))
int main() {
int a = 5, b = 2;
printf("SQUARE(5) = %d\n", SQUARE(5));
printf("SQUARE(a+b) = %d\n", SQUARE(a+b));
printf("SQUARE_BAD(a+b) = %d\n", SQUARE_BAD(a+b)); // Wrong!
printf("MAX(10,20) = %d\n", MAX(10,20));
return 0;
}
SQUARE(5) = 25
SQUARE(a+b) = 49
SQUARE_BAD(a+b) = 17
MAX(10,20) = 20
Note: SQUARE_BAD(5+2) expands to 5+2*5+2 = 5+10+2 = 17, not 49!
Nesting of Macros
Macros can be nested, with one macro used in the definition of another.
#define SQUARE(x) ((x) * (x)) #define CUBE(x) (SQUARE(x) * (x)) #define FOURTH(x) (SQUARE(x) * SQUARE(x))
Undefining a Macro: #undef
A macro can be undefined using #undef, which restricts its definition to a particular part of the program.
#define TEMP 100 /* TEMP can be used here */ #undef TEMP /* TEMP is no longer defined */
15.4 File Inclusion (#include)
The #include directive inserts the contents of another file into the source code.
Two forms:
#include <filename> // Searches in standard directories #include "filename" // Searches in current directory first, then standard
Common header files:
| Header File | Purpose |
|---|---|
<stdio.h> | Standard I/O functions |
<stdlib.h> | Memory allocation, conversions |
<string.h> | String handling functions |
<math.h> | Mathematical functions |
<ctype.h> | Character handling functions |
<time.h> | Date and time functions |
📝 Worked-Out Problem 15.3
Creating and using user-defined header files:
/* myheader.h */
#define PI 3.14159
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
/* main.c */
#include <stdio.h>
#include "myheader.h"
int main() {
int x = 10, y = 20;
float r = 5.5;
printf("PI = %.5f\n", PI);
printf("Area of circle = %.2f\n", PI * r * r);
printf("MAX(%d,%d) = %d\n", x, y, MAX(x,y));
printf("MIN(%d,%d) = %d\n", x, y, MIN(x,y));
return 0;
}
PI = 3.14159
Area of circle = 95.03
MAX(10,20) = 20
MIN(10,20) = 10
15.5 Conditional Compilation
Conditional compilation directives allow parts of the code to be compiled only if certain conditions are met.
#ifdef and #ifndef
#ifdef TEST
printf("Debugging mode\n");
#endif
#ifndef HEADER_H
#define HEADER_H
/* header file contents */
#endif
📝 Worked-Out Problem 15.4
Using #ifdef for debugging:
#include <stdio.h>
#define DEBUG // Comment this line to disable debugging
int main() {
int x = 10, y = 20, z;
z = x * y;
#ifdef DEBUG
printf("Debug: x = %d, y = %d\n", x, y);
printf("Debug: z = %d\n", z);
#endif
printf("Result = %d\n", z);
return 0;
}
Debug: x = 10, y = 20
Debug: z = 200
Result = 200
With DEBUG commented:
Result = 200
#if, #else, #elif Directives
#if MACHINE == 1
#include "machine1.h"
#elif MACHINE == 2
#include "machine2.h"
#else
#include "default.h"
#endif
📝 Worked-Out Problem 15.5
Conditional compilation for different systems:
#include <stdio.h>
#define SYSTEM 2 // 1 for Windows, 2 for Linux, 3 for Mac
int main() {
#if SYSTEM == 1
printf("Running on Windows\n");
printf("Using Windows-specific features\n");
#elif SYSTEM == 2
printf("Running on Linux\n");
printf("Using Linux-specific features\n");
#elif SYSTEM == 3
printf("Running on Mac\n");
printf("Using Mac-specific features\n");
#else
printf("Unknown system\n");
#endif
return 0;
}
Running on Linux
Using Linux-specific features
Using #if with expressions
#if VERSION > 3
/* New features */
#endif
#if defined(DEBUG) && LEVEL > 1
/* Detailed debugging */
#endif
15.6 ANSI Additions
The #elif Directive
Combines else and if for multiple conditions:
#if OS == 1
/* code for OS 1 */
#elif OS == 2
/* code for OS 2 */
#elif OS == 3
/* code for OS 3 */
#else
/* default code */
#endif
The #pragma Directive
Provides implementation-oriented instructions to the compiler. Syntax varies by compiler.
#pragma once // Include file only once #pragma pack(1) // Pack structures tightly #pragma warning(disable:4996) // Disable specific warnings
The #error Directive
Produces a diagnostic message and terminates compilation.
#ifndef HEADER_FILE #error "HEADER_FILE is not defined" #endif
Stringizing Operator (#)
The # operator converts a macro parameter to a string constant.
#define STR(x) #x
#define PRINT(x) printf(#x " = %d\n", x)
int main() {
int value = 10;
printf("%s\n", STR(Hello)); // Prints "Hello"
PRINT(value); // Prints "value = 10"
return 0;
}
📝 Worked-Out Problem 15.6
Stringizing operator demonstration:
#include <stdio.h>
#define TO_STRING(x) #x
#define PRINT_INT(x) printf(#x " = %d\n", x)
#define PRINT_FLOAT(x) printf(#x " = %.2f\n", x)
int main() {
int age = 25;
float salary = 45000.50;
printf("%s\n", TO_STRING(Hello World));
PRINT_INT(age);
PRINT_FLOAT(salary);
return 0;
}
Hello World
age = 25
salary = 45000.50
Token Pasting Operator (##)
The ## operator concatenates two tokens to form a single token.
#define CONCAT(a,b) a##b
#define MAKE_VAR(name, num) name##num
int main() {
int CONCAT(var,1) = 10; // creates var1 = 10
int MAKE_VAR(x,5) = 20; // creates x5 = 20
printf("var1 = %d, x5 = %d\n", var1, x5);
return 0;
}
📝 Worked-Out Problem 15.7
Token pasting for creating variables:
#include <stdio.h>
#define CREATE_VAR(name, num) name##num
#define PRINT_VAR(name, num) printf(#name #num " = %d\n", name##num)
int main() {
int CREATE_VAR(score,1) = 95;
int CREATE_VAR(score,2) = 87;
int CREATE_VAR(score,3) = 92;
PRINT_VAR(score,1);
PRINT_VAR(score,2);
PRINT_VAR(score,3);
return 0;
}
score1 = 95
score2 = 87
score3 = 92
15.7 Predefined Macros
ANSI C defines several predefined macros that provide useful information:
| Macro | Value |
|---|---|
__LINE__ | Current line number |
__FILE__ | Current file name |
__DATE__ | Compilation date (MMM DD YYYY) |
__TIME__ | Compilation time (HH:MM:SS) |
__STDC__ | 1 if ANSI C standard is followed |
📝 Worked-Out Problem 15.8
Using predefined macros:
#include <stdio.h>
int main() {
printf("File: %s\n", __FILE__);
printf("Line: %d\n", __LINE__);
printf("Date: %s\n", __DATE__);
printf("Time: %s\n", __TIME__);
printf("ANSI C: %d\n", __STDC__);
#line 100 "newfile.c"
printf("Now at line %d in file %s\n", __LINE__, __FILE__);
return 0;
}
File: program.c
Line: 6
Date: Mar 15 2024
Time: 14:30:25
ANSI C: 1
Now at line 100 in file newfile.c
Important Points to Remember
- Preprocessor directives begin with # in column one.
- No semicolon is used at the end of preprocessor directives.
- Use parentheses liberally in macros with arguments to avoid evaluation errors.
- Macros are text substitutions, not functions - be careful with side effects.
- Use
#include <file>for standard headers and#include "file"for user-defined headers. - Conditional compilation is useful for debugging, portability, and multiple configurations.
- Stringizing (#) and token pasting (##) are powerful but can make code harder to read.
- Predefined macros help with debugging and program information.
Chapter Exercises
Review Questions
- State true or false:
a) The keyword#definemust be written starting from the first column.
b) Like other statements, preprocessor directives must end with a semicolon.
c) All preprocessor directives begin with #.
d) We cannot use a macro in the definition of another macro.
e) Preprocessor directives are executed by the compiler with the same priority as other statements.
f)#include <stdio.h>checks for stdio.h in the standard directory only. - What is a macro? How is it different from a variable?
- What precautions should be taken when using macros with arguments?
- What is conditional compilation? How does it help?
- Distinguish between
#ifdefand#ifdirectives. - Why are some file names enclosed in angle brackets while others in double quotes?
Multiple Choice Questions
- Which is a file inclusion preprocessor directive?
a) #include
b) #define
c) #pragma
d) Both 1 and 2 - Which statement about compiler control directives is incorrect?
a) #ifdef must be followed by #endif
b) #ifndef must be followed by #endif
c) #undef undefines a macro
d) #if must be followed by #else - Which directive provides alternative test facility by establishing if-else-if sequence?
a) #elif
b) #if
c) #else
d) #pragma - The operator ________ converts its operand to a string.
a) #
b) ##
c) %
d) $ - The ________ directive causes implementation-oriented action.
a) #error
b) #pragma
c) #line
d) #include
Debugging Exercises
Find errors in the following macro definitions:
#define until(x) while(!x) // Valid?
#define ABS(x) (x > 0) ? (x) : (-x) // Missing parentheses?
#ifdef(FLAG) // Error?
#undef FLAG
#endif
#if n == 1 update(item) // Missing parentheses?
#else print(item)
#endif
#define SQUARE(x) x * x // What's wrong with this?
Programming Exercises
- Define a macro
PRINT_VALUEthat can print two values of arbitrary type. - Write a nested macro that gives the minimum of three values.
- Define a macro with one parameter to compute the volume of a sphere. Write a program to compute volumes for radii 5, 10, and 15.
- Define a macro that receives an array and the number of elements and prints all elements.
- Write symbolic constants for binary arithmetic operators +, -, *, /. Write a program to illustrate their use.
- Define symbolic constants for { and } and use them in a program.
- Write a program to illustrate the stringizing operator.
- Write a C program that uses the #error directive to stop execution when an error is encountered.
- Write a program using macro substitution to compute the area of a rectangle.
- Implement a macro named
NOTEQUALthat performs logical NOT operation.
Interview Questions
- What will be the types of i1 and i2 in:
#define INTPTR int* INTPTR i1, i2;
- What is the difference between #include <file> and #include "file"?
- What will be the output of:
#define X -Y #define Y -Z #define Z 10 printf("%d", X); - What is the difference between a macro and a function?
- What are the advantages and disadvantages of using macros?
- What does #pragma once do?
- What is the purpose of the #line directive?
Case Study: Conditional Compilation for Different Platforms
📝 Case Study: Cross-platform development using conditional compilation
#include <stdio.h>
// Define the target platform - uncomment one
#define WINDOWS
// #define LINUX
// #define MAC
// Platform-specific headers
#ifdef WINDOWS
#include <windows.h>
#define CLEAR "cls"
#elif defined(LINUX) || defined(MAC)
#include <unistd.h>
#define CLEAR "clear"
#endif
// Platform-specific functions
#ifdef WINDOWS
#define SLEEP(ms) Sleep(ms)
#else
#define SLEEP(ms) usleep((ms) * 1000)
#endif
// Debug levels
#define DEBUG_LEVEL 2
void log_message(int level, const char *msg) {
#if DEBUG_LEVEL >= level
printf("[LOG] %s\n", msg);
#endif
}
int main() {
printf("Running on ");
#ifdef WINDOWS
printf("Windows\n");
#elif defined(LINUX)
printf("Linux\n");
#elif defined(MAC)
printf("Mac\n");
#else
printf("Unknown platform\n");
#endif
log_message(1, "Program started");
printf("Waiting...\n");
SLEEP(2000);
log_message(2, "Sleep completed");
// Use the appropriate clear command
// system(CLEAR); // Would clear the screen
printf("Done!\n");
log_message(1, "Program ended");
return 0;
}
Case Study 2: Debugging Macros
📝 Case Study: Creating comprehensive debugging macros
#include <stdio.h>
#include <time.h>
// Debug levels
#define DEBUG_NONE 0
#define DEBUG_ERROR 1
#define DEBUG_WARN 2
#define DEBUG_INFO 3
#define DEBUG_ALL 4
// Set debug level
#define DEBUG_LEVEL DEBUG_ALL
// Timestamp macro
#define TIMESTAMP() \
do { \
time_t now = time(NULL); \
printf("[%s]", ctime(&now)); \
} while(0)
// Debug printing macros
#if DEBUG_LEVEL >= DEBUG_ERROR
#define LOG_ERROR(msg) \
do { \
TIMESTAMP(); \
printf(" [ERROR] %s:%d - %s\n", __FILE__, __LINE__, msg); \
} while(0)
#else
#define LOG_ERROR(msg)
#endif
#if DEBUG_LEVEL >= DEBUG_WARN
#define LOG_WARN(msg) \
do { \
TIMESTAMP(); \
printf(" [WARN] %s - %s\n", __func__, msg); \
} while(0)
#else
#define LOG_WARN(msg)
#endif
#if DEBUG_LEVEL >= DEBUG_INFO
#define LOG_INFO(msg) \
printf("[INFO] %s\n", msg)
#else
#define LOG_INFO(msg)
#endif
// Value printing macro
#define PRINT_VAR(x) printf(#x " = %d\n", x)
// Assertion macro
#define ASSERT(cond) \
do { \
if (!(cond)) { \
LOG_ERROR("Assertion failed: " #cond); \
return -1; \
} \
} while(0)
int divide(int a, int b) {
LOG_WARN("divide function called");
PRINT_VAR(a);
PRINT_VAR(b);
ASSERT(b != 0);
int result = a / b;
LOG_INFO("Division successful");
return result;
}
int main() {
LOG_INFO("Program starting");
int x = 10, y = 2;
int z = divide(x, y);
printf("Result: %d\n", z);
LOG_WARN("Testing division by zero");
z = divide(10, 0); // This will trigger assertion
return 0;
}