C ProgrammingUnit 1010 min read
Preprocessor Directives & C Miscellany
Unit 10 of C Programming introduces preprocessor directives (macros, include, define), conditional compilation, and miscellaneous features like assert(), restrict, and static keyword nuances—essential for writing efficient, maintainable, and portable C code.
TAKEAWAYS:
- Preprocessor directives (
#define,#include,#ifdef) process code before compilation, enabling macros, modularity, and conditional compilation. - Macros (
#define) create text substitutions, but unlike functions, they lack type-checking and scope—use sparingly for performance-critical code. - Header guards (
#ifndef) prevent multiple inclusions of the same header file, avoiding redefinition errors. assert()checks program invariants during debugging but is removed in release builds via#define NDEBUG.restrictkeyword hints to the compiler that pointers do not overlap, enabling optimizations like loop unrolling.staticin functions/files limits scope, while in variables ensures storage duration—critical for modularity and thread safety.
1. Preprocessor Basics: How It Works
The C preprocessor runs before compilation, transforming source code into a cleaner form. It handles directives prefixed with # (e.g., #include, #define). Unlike the compiler, it does no type-checking—macros are pure text replacements.
Key Preprocessor Directives
| Directive | Purpose | Example |
|---|---|---|
#include |
Inserts another file’s content (headers or source). | #include <stdio.h> |
#define |
Defines macros (text substitution) or constants. | #define PI 3.14159 |
#undef |
Removes a macro definition. | #undef MAX_SIZE |
#ifdef/#ifndef |
Conditional compilation based on macro existence. | #ifndef DEBUG #define DEBUG 1 #endif |
#if/#elif/#else |
Evaluates expressions for conditional inclusion. | #if DEBUG > 0 #include "debug_log.h" #endif |
#line |
Overrides line/column numbers (useful for error messages). | #line 100 "custom_file.c" |
Visual: Preprocessor Flow
graph TD
A["Source Code"] -->|"Preprocessor"| B["Macro Expansion"]
B --> C["Header Inclusion"]
C --> D["Conditional Compilation"]
D --> E["Compiled Code"]Why it matters: The preprocessor enables modularity (via #include) and portability (via #define for platform-specific code). For example:
- eSewa’s payment gateway uses
#ifdefto compile different encryption modules for Android/iOS. - NTC’s network scripts leverage
#includeto share common functions across routers.
2. Macros: Power and Pitfalls
Macros (#define) replace text during preprocessing. They are not functions—no type safety, no scope, and no evaluation order.
Types of Macros
- Object-like macros (constants):
#define TRUE 1 #define FALSE 0 - Function-like macros (with arguments):
#define SQUARE(x) ((x)*(x))
Worked Example: Macro vs. Function
// Macro (no type-checking, evaluated twice)
#define SQUARE(x) (x)*(x)
// Function (type-checked, evaluated once)
int square(int x) { return x*x; }
Trace for SQUARE(3+1):
- Macro: Expands to
(3+1)*(3+1)→16(incorrect due to order of operations). - Function: Computes
square(4)→16(correct).
Visual: Macro Expansion
Macro: #define MAX(a,b) ((a)>(b)?(a):(b))
Before expansion:
int x = MAX(5+2, 3);
After expansion:
int x = ((5+2)>(3)?(5+2):(3));
Result: x = 7 (correct), but dangerous if arguments have side effects (e.g., MAX(f(), g()) calls f() twice).
Real-world use: Pathao’s ride-matching algorithm uses macros to define platform-specific constants (e.g., #define MAX_DRIVERS 1000) for quick compilation.
3. Header Files and Guards
Header files (*.h) declare functions/variables but avoid multiple inclusions via header guards.
Bad Practice (No Guards)
// math.h (included twice → redefinition error)
int add(int a, int b);
Good Practice (With Guards)
// math.h (safe inclusion)
#ifndef MATH_H
#define MATH_H
int add(int a, int b);
#endif
Visual: Header Inclusion Flow
graph TD
A["#include 'math.h'"] --> B["Check #ifndef MATH_H"]
B -->|"False"| C["Skip (already included)"]
B -->|"True"| D["Define MATH_H & include"]Why it matters: NEPSE’s trading scripts use header guards to share common functions (e.g., order_validation.h) without duplication.
4. Conditional Compilation
Compile different code paths based on macros or compiler flags.
Example: Debug vs. Release Builds
#define DEBUG 1
int main() {
#if DEBUG
printf("Debug mode enabled\n");
#else
printf("Release mode\n");
#endif
return 0;
}
Compile with -DDEBUG=0 to skip debug code.
Real-world use: Khalti’s payment SDK uses #ifdef MOBILE_PLATFORM to include platform-specific APIs.
5. assert() for Debugging
The assert() macro checks invariants during development (disabled in release builds via NDEBUG).
Syntax
#include <assert.h>
assert(condition); // Terminates if false
Example
int divide(int a, int b) {
assert(b != 0); // Crash if b=0
return a / b;
}
Compile with -DNDEBUG to remove all assert() calls.
Why it matters: NTC’s router firmware uses assert() to validate packet sizes before processing.
6. restrict Keyword
Hints to the compiler that pointers do not overlap, enabling optimizations like loop unrolling.
Example
void copy_array(int *restrict dest, const int *restrict src, int n) {
for (int i = 0; i < n; i++) {
dest[i] = src[i]; // Compiler assumes no overlap
}
}
Advantage: The compiler may unroll loops or use SIMD instructions for speed.
Real-world use: Google’s TensorFlow uses restrict in memory-intensive operations (e.g., matrix multiplication).
7. static Keyword Deep Dive
| Scope | static in Function |
static in Variable |
static in File |
|---|---|---|---|
| Lifetime | Function scope | Program lifetime | File scope |
| Linkage | Internal | Internal | Internal |
| Example | static int x = 5; |
static int y; |
static void foo() |
Worked Example: File-Scoped static
// math_utils.c
static int hidden_counter = 0; // Only visible in this file
void increment() { hidden_counter++; }
// main.c
#include "math_utils.h"
int main() {
increment(); // Works
// hidden_counter++; // Error: not visible
return 0;
}
Why it matters: Banks’ transaction modules use static to hide internal counters (e.g., static int last_tx_id = 0).
8. Miscellaneous Topics
(a) sizeof Operator
Returns size of a type or variable in bytes.
int a = 5;
printf("%zu\n", sizeof(a)); // 4 (on 32-bit systems)
printf("%zu\n", sizeof(int)); // 4
Use case: Daraz’s inventory system uses sizeof to calculate buffer sizes for product data.
(b) volatile Keyword
Prevents compiler optimizations for memory-mapped hardware (e.g., sensors).
volatile int *sensor_data = (int*)0x40000000; // Hardware register
Real-world use: Ncell’s signal strength monitors mark registers as volatile to ensure real-time updates.
(c) inline Functions
Suggests the compiler to replace calls with the function body (no overhead).
inline int square(int x) { return x*x; }
Limit: Only works for small, simple functions.
Comparison Table: Macros vs. Functions
| Feature | Macro (#define) |
Function |
|---|---|---|
| Type Safety | ❌ No | ✅ Yes |
| Scope | ❌ Global | ✅ Local |
| Evaluation Order | ❌ Expands arguments twice | ✅ Evaluates once |
| Overhead | ❌ None | ✅ Function call overhead |
| Example | #define SQUARE(x) (x)*(x) |
int square(int x) { ... } |
In the Real World
eSewa’s Payment Gateway: Uses
#define MAX_TRANSACTIONS 1000to set a hard limit for concurrent payments, avoiding memory leaks. Conditional compilation (#ifdef MOBILE_APP) includes platform-specific APIs for Android/iOS.NEPSE’s Trading System: Employs
assert()to validate market data (e.g.,assert(price > 0)) during development. Header guards prevent duplicate inclusions oforder_validation.hacross modules.Pathao’s Ride Matching: Leverages
staticto hide internal counters (e.g.,static int last_request_id) from other modules, ensuring thread safety in high-concurrency scenarios.
Exam Tip
Preprocessor Directives:
- Know the difference between
#define(macro) andconst(compile-time constant). - Common mistake: Forgetting
#ifndefguards in headers → redefinition errors. - Marks: 1–2 questions on macro expansion or header inclusion.
- Know the difference between
assert()andstatic:assert()is debug-only (disabled with-DNDEBUG).staticin functions/variables limits scope—critical for modularity.- Marks: 1 question on
staticscope orassert()usage.
restrictandvolatile:restrictis compiler optimization hint (rarely tested but high impact).volatileis for hardware registers (e.g., sensors).- Marks: 1 short-answer question on
restrictusage.
Practical Focus:
- Code snippets: Expect 1–2 questions on macro expansion or header guards.
- Debugging: Given a snippet with
assert()orstatic, identify the purpose. - Real-world tie: Link macros to apps like eSewa/Khalti (e.g., "How does
#definehelp in payment processing?").
Final Visual: Preprocessor + Compiler Flow
graph TD
A["Source Code"] --> B["Preprocessor"]
B --> C["Macro Expansion<br/>Header Inclusion"]
C --> D["Compiler"]
D --> E["Assembler"]
E --> F["Linker"]
F --> G["Executable"]Based on the TU BIT syllabus for C Programming (BIT102), unit 10.
Discussion
Loading…