The Preprocessor & Macros

Compiler চালু হওয়ার আগে যা ঘটে

~30 min Intermediate 14 practice problems Live code

1. Before the Compiler Runs

Preprocessor একটি text-substitution tool যা actual compilation-এর আগে চালু হয়। # দিয়ে শুরু হওয়া সব directive এই stage-এ process হয়।

Preprocessor কখনোই C syntax বোঝে না — এটি পুরোপুরি text-level কাজ করে। একটি #define আসলে শুধু একটি search-and-replace।

2. #include and Header Guards

// task.h
#ifndef TASK_H
#define TASK_H

typedef struct { int id; char text[128]; } Task;
void add_task(const char *text);

#endif     // TASK_H

Header guard একই ফাইল একাধিকবার include হওয়া থেকে বাঁচায়। আধুনিক বিকল্প: #pragma once (widely supported, non-standard)।

3. Object-like and Function-like Macros

macros.c
#include <stdio.h>

#define PI        3.14159265
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define SQUARE(x) ((x) * (x))

int main(void) {
    printf("PI = %f\n", PI);
    printf("max(7, 13)   = %d\n", MAX(7, 13));
    printf("square(1+2) = %d\n", SQUARE(1 + 2));  // 9, not 5
    return 0;
}
Always parenthesize macro arguments #define SQUARE(x) x * x লিখলে SQUARE(1+2) হয়ে যায় 1+2*1+2 = 5। তাই সবসময় ((x) * (x)) লিখুন।

4. Conditional Compilation

conditional.c
#include <stdio.h>
#define DEBUG 1

int main(void) {
    int x = 42;

#if DEBUG
    printf("[DEBUG] x = %d\n", x);
#endif

    printf("result = %d\n", x * 2);

#if __STDC_VERSION__ >= 201112L
    puts("Running on C11 or later");
#endif
    return 0;
}

Command-line-এও define করা যায়: gcc -DDEBUG program.c।

5. Stringification (#) and Token-Pasting (##)

stringify.c
#include <stdio.h>

#define STR(x)       #x
#define STR2(x)      STR(x)
#define JOIN(a, b)   a##b
#define ADD_TEN(x)   ((x) + 10)

#define VERSION 2026

int main(void) {
    printf("STR(hello)    = %s\n", STR(hello));
    printf("STR(1+2)      = %s\n", STR(1+2));
    printf("STR2(VERSION) = %s\n", STR2(VERSION));  // "2026"

    int JOIN(count, 1) = 7;
    printf("count1        = %d\n", count1);
    printf("ADD_TEN(5)    = %d\n", ADD_TEN(5));
    return 0;
}
#x আর্গুমেন্টকে string literal বানায়। a##b দুটি token জোড়া লাগিয়ে একটি নতুন identifier তৈরি করে। STR2-এর মতো দুই স্তর দরকার যখন ভিতরের মান প্রথমে expand হতে হবে।

6. Predefined Macros

predefined.c
#include <stdio.h>

#define LOG(msg) \
    printf("[%s:%d %s] %s\n", __FILE__, __LINE__, __func__, msg)

int main(void) {
    LOG("starting up");
    LOG("doing work");
    LOG("shutting down");
    return 0;
}

বড় প্রোজেক্টে debug log-এ __FILE__, __LINE__ ও __func__ দিয়ে ঠিক কোন লাইনে কী হচ্ছে সেটি ট্র্যাক করা যায়।

7. Prefer const / enum over #define

// Weak — no type checking, no scope
#define MAX_USERS 100

// Better — typed, scoped
enum { MAX_USERS = 100 };
static const int max_users = 100;
Macro-এ type safety নেই, scope নেই, debugger-এ name-ও দেখা যায় না। তাই "constant" চাইলে const বা enum ব্যবহার করুন।

8. Practice Problems

  1. Write a MIN macro with proper parenthesization.
    সঠিকভাবে parenthesize করা MIN macro লিখুন।
    ✨ Show Answer
    min.c
    #include <stdio.h>
    #define MIN(a, b) ((a) < (b) ? (a) : (b))
    int main(void) {
        printf("%d\n", MIN(7, 3));
        printf("%d\n", MIN(2 + 3, 4));
        return 0;
    }
  2. Write a SWAP(a,b) macro. What goes wrong across types?
    SWAP(a,b) macro লিখুন — type mismatch হলে কী হয়?
    ✨ Show Answer
    swap_macro.c
    #include <stdio.h>
    #define SWAP(a, b) do { typeof(a) _t = (a); (a) = (b); (b) = _t; } while (0)
    
    int main(void) {
        int x = 1, y = 2;     SWAP(x, y);   printf("%d %d\n", x, y);
        double a = 1.5, b = 2.5; SWAP(a, b);   printf("%.1f %.1f\n", a, b);
        return 0;
    }

    typeof GCC extension। প্লেইন #define SWAP(a,b) { int t = a; a = b; b = t; } শুধু int-এ চলে; অন্য type-এ ব্যবহার করলে compile error বা truncation।

  3. Show how SQUARE(i++) causes a bug.
    SQUARE(i++) কেন ভুল ফল দেয় — দেখান।
    ✨ Show Answer
    sq_bug.c
    #include <stdio.h>
    #define SQUARE(x) ((x) * (x))
    
    int main(void) {
        int i = 3;
        int r = SQUARE(i++);     // expands to ((i++) * (i++)) — UB
        printf("r = %d, i = %d\n", r, i);
        return 0;
    }

    Macro argument side-effect সহ হলেই বিপদ। Inline function হলে argument মাত্র একবার evaluate হয় — এজন্য modern code-এ inline function preferred।

  4. Use __LINE__ and __FILE__ to build a LOG(msg) macro.
    __LINE__ ও __FILE__ দিয়ে LOG macro লিখুন।
    ✨ Show Answer

    Section 6-এর predefined.c-ই উত্তর।

  5. Conditional compile — build with and without a DEBUG flag, show the difference.
    DEBUG flag দিয়ে ও ছাড়া compile করে পার্থক্য দেখান।
    ✨ Show Answer
    gcc -DDEBUG=1 prog.c -o prog_dbg     # with DEBUG logs
    gcc            prog.c -o prog_rel     # without DEBUG logs
    
    # inside prog.c:
    #ifdef DEBUG
        printf("[DEBUG] %d\n", x);
    #endif

    Release build-এ DEBUG lines সম্পূর্ণ সরে যায় — runtime cost শূন্য।

  6. Write an assertion macro ASSERT(x) that prints file/line on failure.
    ব্যর্থ হলে file/line প্রিন্ট করে — এমন ASSERT macro লিখুন।
    ✨ Show Answer
    assert_macro.c
    #include <stdio.h>
    #include <stdlib.h>
    
    #define ASSERT(x) \
        do { if (!(x)) { \
            fprintf(stderr, "ASSERT failed: %s at %s:%d\n", #x, __FILE__, __LINE__); \
            exit(1); \
        } } while (0)
    
    int main(void) {
        int x = 10;
        ASSERT(x > 0);            // passes
        puts("reached end");
        return 0;
    }
  7. Explain the difference between #include <x.h> and #include "x.h".
    #include <x.h> আর #include "x.h"-এর পার্থক্য।
    ✨ Show Answer

    <x.h> — system include path-এ খোঁজা হয় (যেমন /usr/include)। "x.h" — আগে current source file-এর পাশে খোঁজা হয়, না পেলে system path-এ। নিজের header-এর জন্য " ", standard/system header-এর জন্য < >।

  8. Add header guards to three of your own headers.
    নিজের তিনটি header-এ guard যোগ করুন।
    ✨ Show Answer
    // task.h
    #ifndef TASK_H
    #define TASK_H
    /* declarations ... */
    #endif
    
    // or simply:
    #pragma once

    Guard-এর নাম project-ভিত্তিক unique হলে ভালো: ABCL_TASK_H।

  9. Use ## to generate register1, register2, ... names.
    ## দিয়ে register1, register2 ইত্যাদি নাম তৈরি করুন।
    ✨ Show Answer
    register.c
    #include <stdio.h>
    #define REG(n) register##n
    
    int main(void) {
        int REG(1) = 10, REG(2) = 20, REG(3) = 30;
        printf("%d %d %d\n", register1, register2, register3);
        return 0;
    }
  10. Why are macros not type-safe? Show a concrete example.
    Macro কেন type-safe নয়? উদাহরণ দিন।
    ✨ Show Answer

    Macro শুধু text replace করে — type চেক করে না। MAX("hello", 42)-ও compile হয়ে যেতে পারে এবং অদ্ভুত result দিতে পারে। এজন্যই সম্ভব হলে inline function বেশি ভালো: type checked, argument একবারই evaluate হয়।

  11. When would you choose a macro over a function?
    কখন function-এর চেয়ে macro পছন্দ করবেন?
    ✨ Show Answer

    (১) Generic code যেখানে type-independent হতে হবে (MAX, SWAP, container_of)। (২) Source-location logging (__FILE__, __LINE__ inline value-হিসেবে)। (৩) Stringification/token pasting দরকার হলে — function-এ সম্ভব নয়। অন্যসব ক্ষেত্রে inline function নিন।

  12. Run gcc -E on your program and observe the expanded output.
    gcc -E চালিয়ে preprocessor-এর output দেখুন।
    ✨ Show Answer
    gcc -E program.c | less
    # or save:
    gcc -E program.c -o program.i

    Output-এ দেখবেন — #include-এর কারণে হাজার হাজার line বসে গেছে, আপনার macro গুলো expand হয়ে গেছে। Preprocessor কী কাজ করে পুরো পরিষ্কার হয়ে যাবে।

  13. Build a platform switch: #ifdef _WIN32 ... #else ... #endif.
    Platform switch লিখুন।
    ✨ Show Answer
    platform.c
    #include <stdio.h>
    
    int main(void) {
    #ifdef _WIN32
        puts("Running on Windows");
    #elif defined(__APPLE__)
        puts("Running on macOS");
    #elif defined(__linux__)
        puts("Running on Linux");
    #else
        puts("Running on an unknown platform");
    #endif
        return 0;
    }
  14. Find a real-world macro abuse in any open-source project and explain its risk.
    কোনো real project-এ একটি macro abuse খুঁজে ঝুঁকি ব্যাখ্যা করুন।
    ✨ Show Answer

    সাধারণ ঝুঁকি: parenthesization ভুল (operator-precedence bug), multiple-evaluation (side-effect), hidden control-flow (#define CHECK(x) if (!x) return — caller-এ return চলে আসে)। এসব macro debug করা কঠিন কারণ debugger-এ source line দেখা যায় কিন্তু আসলে অন্য কিছু ঘটছে। সমাধান: inline function, অথবা অন্তত do { ... } while (0) wrapping।

Glossary (শব্দকোষ)

TermMeaningবাংলায়
PreprocessorText-substitution stage that runs before compilation.Compile-এর আগে চলা text-replacement ধাপ।
DirectiveA line starting with # that the preprocessor handles.#-শুরু-হওয়া preprocessor-নির্দেশ।
#defineDefines a macro or a constant.Macro বা constant সংজ্ঞায়িত করা।
#includePastes a header file into the source.Header file source-এ যুক্ত করা।
Object-like MacroMacro without parameters (a constant).Parameter-হীন macro।
Function-like MacroMacro with parameters — text expansion.Parameter-যুক্ত macro — text হিসেবে expand হয়।
Include Guard#ifndef ... #define ... #endif to prevent double inclusion.Header একাধিকবার include হওয়া রোধে guard।
#pragma onceCompiler-specific include guard.Compiler-specific include guard।
Conditional Compilation#ifdef, #if, #else, #endif — choose code at build time.Build-time-এ কোড বাছাই।
Stringification (#)Turns a macro arg into a string literal.Macro argument-কে string-এ রূপান্তর।
Token Pasting (##)Concatenates two tokens during expansion.Expansion-এ দুটি token জুড়ে দেওয়া।
Predefined Macros__FILE__, __LINE__, __DATE__, etc.আগে থেকেই সংজ্ঞায়িত macro।

Summary — Module 21

Preprocessor text-level, compiler-এর আগেই চলে। #include, header guard, #define যত্নে ব্যবহার করুন। প্রতিটি macro argument parenthesize করুন। Value-এর জন্য const/enum preferred। Conditional compilation platform ও debug build সামলায়।

Next Module → Function Pointers & Callbacks।