Bytebook Compiler

Structures, Files & More

Preprocessor, Macros & Command-Line Arguments

What happens before compilation: #include, #define macros and their traps, conditional compilation, header guards, and reading arguments with argc/argv.

4 min read4 runnable examples4-question quiz

Before the compiler sees your code, the preprocessor runs. It handles every line starting with #. It does pure text substitution — it does not understand C types or syntax.

#include

C
#include <stdio.h>      // angle brackets: search the system include folders
#include "mylib.h"      // quotes: search your project folder first

#define: constants and macros

C
#include <stdio.h>
#define PI 3.14159
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int main(void) {
    printf("Area of r=2: %.2f\n", PI * SQUARE(2));
    printf("MAX(7, 12) = %d\n", MAX(7, 12));
    printf("SQUARE(3 + 1) = %d\n", SQUARE(3 + 1));
    return 0;
}
Output
Area of r=2: 12.57
MAX(7, 12) = 12
SQUARE(3 + 1) = 16

The bracket trap

Because macros are text replacement, missing brackets give wrong answers:

C
#include <stdio.h>
#define BAD_SQUARE(x) x * x
#define GOOD_SQUARE(x) ((x) * (x))

int main(void) {
    printf("BAD_SQUARE(3 + 1)  = %d\n", BAD_SQUARE(3 + 1));    // 3 + 1 * 3 + 1 = 7
    printf("GOOD_SQUARE(3 + 1) = %d\n", GOOD_SQUARE(3 + 1));   // (3 + 1) * (3 + 1) = 16
    printf("100 / BAD_SQUARE(5)  = %d\n", 100 / BAD_SQUARE(5)); // 100 / 5 * 5 = 100
    printf("100 / GOOD_SQUARE(5) = %d\n", 100 / GOOD_SQUARE(5));
    return 0;
}
Output
BAD_SQUARE(3 + 1)  = 7
GOOD_SQUARE(3 + 1) = 16
100 / BAD_SQUARE(5)  = 100
100 / GOOD_SQUARE(5) = 4

Predefined macros

C
#include <stdio.h>

int main(void) {
    printf("File: %s, line: %d\n", __FILE__, __LINE__);
    printf("Compiled on %s at %s\n", __DATE__, __TIME__);
    printf("C standard version: %ld\n", __STDC_VERSION__);
    return 0;
}
Output · yours may differ
File: main.c, line: 4
Compiled on Oct 31 2026 at 13:03:21
C standard version: 201710

Conditional compilation

Include or exclude code before compiling — useful for debug output and for code that differs between Windows and Linux:

C
#include <stdio.h>
#define DEBUG 1

int main(void) {
    int total = 0;
    for (int i = 1; i <= 4; i++) {
        total += i * i;
#if DEBUG
        printf("[debug] i = %d, total = %d\n", i, total);
#endif
    }
    printf("Sum of squares: %d\n", total);
#ifdef _WIN32
    printf("Built for Windows\n");
#else
    printf("Built for Linux or macOS\n");
#endif
    return 0;
}
Output
[debug] i = 1, total = 1
[debug] i = 2, total = 5
[debug] i = 3, total = 14
[debug] i = 4, total = 30
Sum of squares: 30
Built for Linux or macOS

Header guards

When you split code into files, put declarations in a header. Wrap the header in a guard so including it twice does no harm:

C
// mathutil.h
#ifndef MATHUTIL_H
#define MATHUTIL_H

int gcd(int a, int b);
int lcm(int a, int b);

#endif

Command-line arguments

main can receive the words typed after the program name:

  • argc — argument count, including the program name.
  • argv — argument vector: an array of strings. argv[0] is the program name.
C
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    if (argc < 3) {
        printf("Usage: %s num1 num2\n", argv[0]);
        return 1;
    }
    int a = atoi(argv[1]), b = atoi(argv[2]);   // convert text to int
    printf("%d + %d = %d\n", a, b, a + b);
    return 0;
}
Terminal
gcc add.c -o add
./add 12 30
# 12 + 30 = 42

Test yourself

4 questions · pick an answer to check it instantly

  1. 1

    What does #define TRIPLE(x) x * 3 give for TRIPLE(2 + 1)?

  2. 2

    What is argv[0]?

  3. 3

    What are header guards for?

  4. 4

    When do preprocessor directives run?