Skills101

C Programming · Lesson 9 of 10 · Updated ·

File Handling and the Preprocessor in C — fopen, fprintf, Macros, Header Guards

Programs need to save data between runs — that's file handling. And before your code is even compiled, the preprocessor rewrites it — that's where macros and header files come in.

Part A — Files

Memory layout: file data you read is copied into buffers in your program's memory — the stack for small arrays or the heap for big ones

Open modes

ModeMeaning
"r"read; file must exist
"w"write; creates or erases the file
"a"append to the end
"r+", "w+", "a+"read and write variants
add b ("rb")binary mode

Write, then read

#include <stdio.h>

int main(void) {
    FILE *f = fopen("marks.txt", "w");
    if (f == NULL) { perror("open"); return 1; }
    fprintf(f, "Asha 92\nRavi 74\nNeha 85\n");
    fclose(f);

    f = fopen("marks.txt", "r");
    if (f == NULL) { perror("open"); return 1; }
    char name[20];
    int m, total = 0, count = 0;
    while (fscanf(f, "%19s %d", name, &m) == 2) {
        total += m;
        count++;
    }
    fclose(f);
    printf("%d students, average %.2f\n", count, (float)total / count);
    return 0;
}
3 students, average 83.67
  • fopen returns NULL if it fails (missing file, no permission) — always check.
  • fprintf / fscanf work like printf / scanf but on a file.
  • fgets(buf, size, f) reads a whole line safely; fputs writes a string.
  • Always fclose — it flushes data to disk and frees the file handle.
  • fscanf returns how many items it read; loop while it returns the expected count (better than while (!feof(f))).

Command-line arguments

int main(int argc, char *argv[]) {
    /* program.exe data.txt 5   →  argc = 3, argv[1] = "data.txt", argv[2] = "5" */
}

Part B — The preprocessor

Lines starting with # are handled before compilation — pure text substitution.

#define constants and macros

#include <stdio.h>

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

int main(void) {
    printf("%.2f\n", PI * SQUARE(2));
    printf("%d %d\n", SQUARE(2 + 3), BAD_SQUARE(2 + 3));
    printf("%d\n", MAX(7, 12));
    return 0;
}
12.57
25 11
12

BAD_SQUARE(2 + 3) becomes 2 + 3 * 2 + 3 = 11, not 25! Always put brackets around every parameter and the whole macro. (This exact question appears in many placement tests.)

Macro vs function: macros are copied in place (no call overhead) but have no type checking and can evaluate arguments twice (SQUARE(i++) is a bug). Modern C prefers static inline functions.

#include

  • #include <stdio.h> — search system folders.
  • #include "myfile.h" — search your project folder first.

Header guards

Stop a header from being included twice:

#ifndef STUDENT_H
#define STUDENT_H

typedef struct { int roll; char name[20]; } Student;
void print_student(const Student *s);

#endif

Conditional compilation

#ifdef DEBUG
    printf("x = %d\n", x);     /* only compiled when DEBUG is defined */
#endif

Compile with gcc -DDEBUG main.c to turn it on.

Common mistakes

  • Not checking if fopen returned NULL.
  • Opening with "w" and wiping a file you meant to add to (use "a").
  • Macros without brackets; macros with side effects like SQUARE(i++).

Quick recap

  • fopen (check NULL) → fprintf/fscanf/fgets → fclose. Modes r, w (erases), a.
  • Loop on fscanf's return value.
  • Preprocessor = text substitution before compiling.
  • Bracket every macro parameter; use header guards in .h files.

Practice what you learned

📝 Quiz: Files and Preprocessor5 questions · instant answers & explanations · +10 XP · counts towards your free certificateTake the quiz →