File I/O — 10 Examples
0%
File I/O  ·  10 Examples

File I/O in C —
10 Programs

Ten real programs from opening your first file to a full student record system — write, read, append, copy, count, search, binary I/O, struct records, and error handling. Every file operation you will ever need.

1
Write a File
2
Read Chars
3
Read Lines
4
Append Mode
5
Copy File
6
Count & Stats
7
Search File
8
Binary Write
9
Struct Records
10
Student App
1
📄 Write Text to a File
fopen "w", fprintf, fputs, fclose — the complete write cycle
Basics
Every file operation in C follows the same three-step cycle: open → use → close. fopen(name, mode) returns a FILE* pointer — a handle to the file. Mode "w" creates the file if missing, or erases it completely if it already exists. Always check for NULL — fopen returns NULL on failure (wrong path, no permission). fclose is mandatory: it flushes the internal buffer to disk and releases the handle.
ModeMeaningFile existsFile missing
"r"Read onlyOpens itReturns NULL
"w"Write onlyErases itCreates it
"a"Append onlyAdds to endCreates it
"r+"Read + WriteOpens itReturns NULL
"w+"Read + WriteErases itCreates it
"a+"Read + AppendAdds to endCreates it
ex1_write_file.c
C
#include <stdio.h>
#include <stdlib.h>

int main() {
    /* Step 1 — open file for writing */
    FILE *fp = fopen("notes.txt", "w");

    if (fp == NULL) {          /* ALWAYS check for NULL */
        printf("Error: could not open file.\n");
        return 1;
    }

    /* Step 2 — write to the file */
    fprintf(fp, "Name  : Ananta\n");        /* like printf, but to file */
    fprintf(fp, "City  : Haridwar\n");
    fprintf(fp, "Score : %d\n", 95);       /* supports format specifiers */
    fprintf(fp, "Grade : %c\n", 'A');
    fputs("Status: Active\n", fp);         /* fputs also works */

    /* Step 3 — close (flushes buffer to disk) */
    fclose(fp);
    printf("File written successfully.\n");
    return 0;
}
output (console)
File written successfully.
📄 notes.txt — written to disk
Name : Ananta City : Haridwar Score : 95 Grade : A Status: Active
fclose(fp) is not optional. Without it, data sitting in the internal buffer may never reach the disk. Programs that crash before fclose often produce empty or truncated files. Always close every file you open.
example 2
2
📖 Read a File — Character by Character
fgetc loop until EOF — the simplest read pattern in C
Read Chars
Open with "r" and read one character at a time using fgetc. The loop runs until fgetc returns the special constant EOF (End Of File, typically -1). The critical detail: ch must be declared as int, not char — because EOF is -1 and on systems where char is unsigned, the comparison ch != EOF would never be true, creating an infinite loop.
ex2_read_chars.c
C
#include <stdio.h>

int main() {
    FILE *fp = fopen("notes.txt", "r");

    if (fp == NULL) {
        printf("Error: file not found.\n");
        return 1;
    }

    printf("--- Contents of notes.txt ---\n");

    int ch;                         /* int, NOT char — EOF is -1 */
    int charCount = 0;

    while ((ch = fgetc(fp)) != EOF) {
        putchar(ch);                /* print character to stdout */
        charCount++;
    }

    printf("\n--- Total characters: %d ---\n", charCount);

    fclose(fp);
    return 0;
}
output
--- Contents of notes.txt ---
Name  : Ananta
City  : Haridwar
Score : 95
Grade : A
Status: Active

--- Total characters: 67 ---
Always declare ch as int, never char. fgetc returns an int so it can return both any valid byte (0–255) and EOF (−1). If you use char and the platform treats char as unsigned, EOF gets truncated to 255 and the loop never ends.
example 3
3
📃 Read a File — Line by Line
fgets with a buffer — safe, line-aware reading with line numbers
Read Lines
fgets(buffer, size, fp) reads one line at a time into a character array. It stops at a newline, at size-1 characters, or at EOF — whichever comes first. It is always safer than fgetc for line-based text because it limits input length and prevents buffer overflow. The newline \n is kept inside the buffer — strip it with strcspn if needed. fscanf is shown as a second approach for reading structured tokens.
ex3_read_lines.c
C
#include <stdio.h>
#include <string.h>

int main() {
    FILE *fp = fopen("notes.txt", "r");
    if (!fp) { printf("Error\n"); return 1; }

    char line[100];    /* buffer — holds one line at a time */
    int  lineNo = 0;

    printf("%-4s %s\n", "Line", "Content");
    printf("------------------------------\n");

    while (fgets(line, sizeof(line), fp) != NULL) {
        lineNo++;
        /* Strip trailing newline for clean display */
        line[strcspn(line, "\n")] = '\0';
        printf("%4d | %s\n", lineNo, line);
    }

    printf("------------------------------\n");
    printf("Total lines: %d\n", lineNo);

    fclose(fp);

    /* ── fscanf approach: read word by word ── */
    fp = fopen("notes.txt", "r");
    char word[50];
    int  words = 0;
    while (fscanf(fp, "%s", word) == 1) words++;
    printf("Total words : %d\n", words);
    fclose(fp);

    return 0;
}
output
Line Content
------------------------------
   1 | Name  : Ananta
   2 | City  : Haridwar
   3 | Score : 95
   4 | Grade : A
   5 | Status: Active
------------------------------
Total lines: 5
Total words : 10
fgets keeps the \n at the end of the buffer. That's why we use line[strcspn(line,"\n")] = '\0' to strip it. Without stripping, printing with printf("%s\n") would add a blank line after every line of output.
example 4
4
➕ Append to a File Without Erasing It
fopen "a" — pointer starts at end, original data untouched
Append Mode
Mode "a" positions the file pointer at the very end of the file on every write. Original content is never touched. If the file doesn't exist, it is created — same as "w". Append mode is ideal for log files, journals, and any situation where you want to add new entries without risking the loss of existing data. The program then reads the whole file back to confirm the append worked.
ex4_append.c
C
#include <stdio.h>
#include <string.h>

void printFile(const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (!fp) return;
    char line[100];
    while (fgets(line, sizeof(line), fp))
        fputs(line, stdout);
    fclose(fp);
}

int main() {
    printf("--- notes.txt BEFORE append ---\n");
    printFile("notes.txt");

    /* Open in APPEND mode — does NOT erase */
    FILE *fp = fopen("notes.txt", "a");
    if (!fp) { printf("Error\n"); return 1; }

    fprintf(fp, "Lang  : C\n");
    fprintf(fp, "Year  : 2025\n");
    fprintf(fp, "College: Ananta Institute\n");

    fclose(fp);
    printf("\n3 lines appended successfully.\n\n");

    printf("--- notes.txt AFTER append ---\n");
    printFile("notes.txt");

    return 0;
}
output
--- notes.txt BEFORE append ---
Name  : Ananta
City  : Haridwar
Score : 95
Grade : A
Status: Active

3 lines appended successfully.

--- notes.txt AFTER append ---
Name  : Ananta
City  : Haridwar
Score : 95
Grade : A
Status: Active
Lang  : C
Year  : 2025
College: Ananta Institute
Mode cheat-sheet in one line: "w" = overwrite · "r" = read · "a" = append at end · add + to any for both read and write · add b (e.g. "rb", "wb") for binary mode.
example 5
5
📋 Copy One File to Another
Open source "r" and destination "w" — pipe bytes with fgetc/fputc
File Copy
File copying is the clearest demonstration of using two FILE* handles at once. Open the source in "r" and the destination in "w". Read one character from source with fgetc, write it to destination with fputc, repeat until EOF. Count the bytes to confirm. Then verify the copy by reading it back and comparing the line count.
ex5_copy_file.c
C
#include <stdio.h>

int countLines(const char *name) {
    FILE *fp = fopen(name, "r");
    if (!fp) return -1;
    int ch, n = 0;
    while ((ch = fgetc(fp)) != EOF) if (ch == '\n') n++;
    fclose(fp); return n;
}

int main() {
    const char *src  = "notes.txt";
    const char *dest = "notes_backup.txt";

    FILE *in  = fopen(src,  "r");
    FILE *out = fopen(dest, "w");

    if (!in || !out) {
        printf("Error opening file(s).\n");
        return 1;
    }

    int ch, bytes = 0;
    while ((ch = fgetc(in)) != EOF) {
        fputc(ch, out);    /* write one char to destination */
        bytes++;
    }

    fclose(in);
    fclose(out);

    printf("Copied %d bytes: %s → %s\n", bytes, src, dest);
    printf("Source lines : %d\n", countLines(src));
    printf("Backup lines : %d\n", countLines(dest));
    printf("Copy verified: %s\n",
           countLines(src) == countLines(dest) ? "OK" : "MISMATCH");
    return 0;
}
output
Copied 128 bytes: notes.txt → notes_backup.txt
Source lines : 8
Backup lines : 8
Copy verified: OK
For large files, copying byte-by-byte is slow. Use fread/fwrite with a buffer (e.g. 4096 bytes) instead — one call reads a big chunk, one call writes it. See Example 8 for the binary fread/fwrite approach.
example 6
6
📊 Count Lines, Words and Characters
Single-pass scan — how the Unix wc command works internally
Stats
Read the file character by character and track three counters in a single pass. Lines are counted by newline characters. Words are counted using an inWord flag — it flips from 0 to 1 each time we enter a new non-whitespace run, and that flip is the word count increment. Characters are counted on every iteration. This is exactly how the Unix wc command works at its core.
ex6_count_stats.c
C
#include <stdio.h>
#include <ctype.h>    /* isspace() */

int main() {
    FILE *fp = fopen("notes.txt", "r");
    if (!fp) { printf("Error\n"); return 1; }

    int ch;
    long lines = 0, words = 0, chars = 0;
    int  inWord = 0;   /* flag: are we currently inside a word? */

    while ((ch = fgetc(fp)) != EOF) {
        chars++;

        if (ch == '\n')
            lines++;

        if (isspace(ch)) {
            inWord = 0;              /* left a word */
        } else if (!inWord) {
            words++;                  /* just entered a new word */
            inWord = 1;
        }
    }

    fclose(fp);

    printf("File    : notes.txt\n");
    printf("Lines   : %ld\n", lines);
    printf("Words   : %ld\n", words);
    printf("Chars   : %ld\n", chars);
    printf("Avg word len: %.1f chars\n",
           words ? (float)(chars - lines) / words : 0);
    return 0;
}
output
File    : notes.txt
Lines   : 8
Words   : 16
Chars   : 128
Avg word len: 7.0 chars
The inWord flag ensures each word is counted exactly once — on the transition from whitespace to non-whitespace. Without it, each character of a word would be counted separately. The same flag logic is inside BSD's wc source code.
example 7
7
🔍 Search for a String in a File
fgets + strstr line by line — how grep works at its core
Search
Read the file line by line with fgets. For each line, call strstr(line, keyword) — it returns a non-NULL pointer if the keyword appears anywhere in the line. Print matching lines with their line numbers. This is the exact algorithm inside the Unix grep command. A second pass demonstrates case-insensitive search using tolower on both strings.
ex7_search_file.c
C
#include <stdio.h>
#include <string.h>
#include <ctype.h>

/* Convert string to lowercase in-place (into dest) */
void toLowerStr(const char *src, char *dest, int max) {
    int i;
    for (i = 0; i < max-1 && src[i]; i++)
        dest[i] = (char)tolower((unsigned char)src[i]);
    dest[i] = '\0';
}

int searchFile(const char *filename, const char *keyword, int ignoreCase) {
    FILE *fp = fopen(filename, "r");
    if (!fp) { printf("Error\n"); return 0; }

    char line[200], lcLine[200], lcKey[50];
    int  lineNo = 0, matches = 0;

    if (ignoreCase) toLowerStr(keyword, lcKey, 50);

    while (fgets(line, sizeof(line), fp)) {
        lineNo++;
        const char *haystack = line;
        const char *needle   = keyword;

        if (ignoreCase) {
            toLowerStr(line, lcLine, sizeof(lcLine));
            haystack = lcLine;
            needle   = lcKey;
        }

        if (strstr(haystack, needle)) {
            line[strcspn(line, "\n")] = '\0';
            printf("  Line %2d: %s\n", lineNo, line);
            matches++;
        }
    }
    fclose(fp);
    return matches;
}

int main() {
    printf("Search \"Ananta\" (exact):\n");
    int m = searchFile("notes.txt", "Ananta", 0);
    printf("  %d match(es)\n\n", m);

    printf("Search \"ananta\" (case-insensitive):\n");
    m = searchFile("notes.txt", "ananta", 1);
    printf("  %d match(es)\n\n", m);

    printf("Search \"2025\" (exact):\n");
    m = searchFile("notes.txt", "2025", 0);
    printf("  %d match(es)\n", m);

    return 0;
}
output
Search "Ananta" (exact):
  Line  1: Name  : Ananta
  Line  8: College: Ananta Institute
  2 match(es)

Search "ananta" (case-insensitive):
  Line  1: Name  : Ananta
  Line  8: College: Ananta Institute
  2 match(es)

Search "2025" (exact):
  Line  7: Year  : 2025
  1 match(es)
strstr(haystack, needle) returns a pointer to the first occurrence of needle inside haystack, or NULL if not found. The if (strstr(...)) idiom works because a non-NULL pointer is truthy. For case-insensitive matching, lowercase both strings before comparing.
example 8
8
⚙️ Binary File I/O — fwrite and fread
Store raw bytes — no text, no commas, no newlines — compact and fast
Binary I/O
Binary mode ("wb" / "rb") stores data as raw bytes — exactly as they sit in memory. No text conversion, no newlines, no delimiters. fwrite(ptr, size, count, fp) writes count objects of size bytes each. fread mirrors it exactly. Binary files are more compact, faster to read/write, and mandatory for non-text data like images, audio, and number arrays. The file is unreadable in a text editor but perfectly precise.
ex8_binary_io.c
C
#include <stdio.h>

int main() {
    /* ── WRITE: store 6 integers as raw bytes ── */
    int scores[] = {85, 92, 78, 96, 88, 71};
    int n = 6;

    FILE *fp = fopen("scores.bin", "wb");   /* b = binary mode */
    if (!fp) { printf("Error\n"); return 1; }

    /* fwrite(data, size_per_item, count, file) */
    size_t written = fwrite(scores, sizeof(int), n, fp);
    printf("Written : %zu integers  (%zu bytes)\n",
           written, written * sizeof(int));
    fclose(fp);

    /* ── READ: load them back ── */
    int    buf[10];   /* buffer large enough */
    fp = fopen("scores.bin", "rb");
    if (!fp) { printf("Error\n"); return 1; }

    /* fread(buffer, size_per_item, max_count, file) */
    size_t got = fread(buf, sizeof(int), 10, fp);
    fclose(fp);

    printf("Read    : %zu integers\n\n", got);
    printf("%-6s %s\n", "Index", "Score");
    printf("-----------\n");

    int sum = 0, max = buf[0];
    for (int i = 0; i < (int)got; i++) {
        printf("  [%d]    %d\n", i, buf[i]);
        sum += buf[i];
        if (buf[i] > max) max = buf[i];
    }
    printf("-----------\n");
    printf("Average : %.1f\n", (float)sum / got);
    printf("Maximum : %d\n", max);
    return 0;
}
output
Written : 6 integers  (24 bytes)
Read    : 6 integers

Index  Score
-----------
  [0]    85
  [1]    92
  [2]    78
  [3]    96
  [4]    88
  [5]    71
-----------
Average : 85.0
Maximum : 96
Text vs Binary: In text mode, the integer 96 is stored as two characters '9' and '6' (2 bytes). In binary mode it is stored as the 4-byte integer 0x00000060. Six integers = 6 × 4 = 24 bytes in binary. In text mode with commas and newlines it would be around 20+ characters but variable-length and not directly loadable.
example 9
9
🗂️ Write and Read Struct Records
fwrite/fread with structs — a simple file database of fixed-size records
Struct Records
Writing an array of structs to a binary file is the foundation of file-based databases in C. One fwrite call saves all records. One fread call loads them all back. Every record is exactly sizeof(Student) bytes — so you can seek to any record instantly with fseek(fp, n * sizeof(Student), SEEK_SET). This is called a fixed-length record file — the simplest form of a database.
ex9_struct_records.c
C
#include <stdio.h>
#include <string.h>

typedef struct {
    char  name[20];
    int   roll;
    float marks;
    char  grade;
} Student;

void printStudent(const Student *s) {
    printf("  %-12s Roll:%-4d Marks:%5.1f Grade:%c\n",
           s->name, s->roll, s->marks, s->grade);
}

int main() {
    Student roster[] = {
        {"Ananta",  101, 88.5, 'B'},
        {"Priya",   102, 95.0, 'A'},
        {"Rahul",   103, 72.0, 'C'},
        {"Sneha",   104, 91.5, 'A'},
        {"Vikram",  105, 65.0, 'C'}
    };
    int n = 5;

    /* ── WRITE all records in one call ── */
    FILE *fp = fopen("students.dat", "wb");
    fwrite(roster, sizeof(Student), n, fp);
    fclose(fp);
    printf("Saved %d records (%zu bytes each, %zu total)\n\n",
           n, sizeof(Student), n * sizeof(Student));

    /* ── READ all records back ── */
    Student loaded[10];
    fp = fopen("students.dat", "rb");
    size_t count = fread(loaded, sizeof(Student), 10, fp);
    fclose(fp);

    printf("Loaded %zu records:\n", count);
    for (int i = 0; i < (int)count; i++)
        printStudent(&loaded[i]);

    /* ── SEEK: read only record at index 2 ── */
    fp = fopen("students.dat", "rb");
    Student one;
    fseek(fp, 2 * sizeof(Student), SEEK_SET);   /* jump to record 2 */
    fread(&one, sizeof(Student), 1, fp);
    fclose(fp);

    printf("\nDirect access — record[2]:\n");
    printStudent(&one);
    return 0;
}
output
Saved 5 records (28 bytes each, 140 total)

Loaded 5 records:
  Ananta       Roll:101  Marks: 88.5 Grade:B
  Priya        Roll:102  Marks: 95.0 Grade:A
  Rahul        Roll:103  Marks: 72.0 Grade:C
  Sneha        Roll:104  Marks: 91.5 Grade:A
  Vikram       Roll:105  Marks: 65.0 Grade:C

Direct access — record[2]:
  Rahul        Roll:103  Marks: 72.0 Grade:C
fseek(fp, offset, origin) moves the file position pointer. SEEK_SET = from start, SEEK_CUR = from current, SEEK_END = from end. With fixed-size records, fseek(fp, n * sizeof(Student), SEEK_SET) jumps directly to the nth record — O(1) random access, just like an array.
example 10
10
🎓 Student File Manager — Complete Mini App
Add, display, search by roll, update marks, find topper — all persisted to disk
Complete App
Everything combined — a fully functional student record manager that persists data to students.dat. Five operations: addRecord appends one struct using "ab", displayAll reads and prints all, searchByRoll finds a student with fseek/fread, updateMarks uses "r+b" to overwrite one field in-place, and findTopper scans all records. This is how simple C database applications are structured.
ex10_student_file_app.c
C
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct {
    char  name[20];
    int   roll;
    float marks;
} Student;

const char *DB = "students.dat";

/* 1. Append one record */
void addRecord(Student s) {
    FILE *fp = fopen(DB, "ab");       /* ab = append binary */
    fwrite(&s, sizeof(Student), 1, fp);
    fclose(fp);
}

/* 2. Display all records */
void displayAll() {
    FILE *fp = fopen(DB, "rb");
    if (!fp) { printf("  (empty)\n"); return; }
    Student s; int i = 0;
    printf("  %-12s %5s %7s\n", "Name", "Roll", "Marks");
    printf("  ---------------------------\n");
    while (fread(&s, sizeof(Student), 1, fp) == 1) {
        printf("  %-12s %5d %7.1f\n", s.name, s.roll, s.marks);
        i++;
    }
    printf("  ---------------------------\n");
    printf("  Total records: %d\n", i);
    fclose(fp);
}

/* 3. Search by roll — returns record index or -1 */
int searchByRoll(int roll, Student *out) {
    FILE *fp = fopen(DB, "rb");
    if (!fp) return -1;
    Student s; int i = 0;
    while (fread(&s, sizeof(Student), 1, fp) == 1) {
        if (s.roll == roll) { *out = s; fclose(fp); return i; }
        i++;
    }
    fclose(fp); return -1;
}

/* 4. Update marks in-place at record index */
void updateMarks(int roll, float newMarks) {
    Student s;
    int idx = searchByRoll(roll, &s);
    if (idx < 0) { printf("  Roll %d not found.\n", roll); return; }

    FILE *fp = fopen(DB, "r+b");           /* r+b = read+write binary */
    fseek(fp, idx * (long)sizeof(Student), SEEK_SET);
    fread(&s, sizeof(Student), 1, fp);   /* load current record */
    s.marks = newMarks;                   /* modify in memory */
    fseek(fp, idx * (long)sizeof(Student), SEEK_SET);
    fwrite(&s, sizeof(Student), 1, fp);  /* write back */
    fclose(fp);
    printf("  Updated %s: marks = %.1f\n", s.name, newMarks);
}

/* 5. Find topper */
void findTopper() {
    FILE *fp = fopen(DB, "rb");
    if (!fp) return;
    Student s, best; best.marks = -1;
    while (fread(&s, sizeof(Student), 1, fp) == 1)
        if (s.marks > best.marks) best = s;
    fclose(fp);
    printf("  Topper: %s (Roll %d) — %.1f marks\n",
           best.name, best.roll, best.marks);
}

int main() {
    /* Start fresh */
    remove(DB);

    printf("=== Adding Records ===\n");
    addRecord((Student){"Ananta",  101, 88.5});
    addRecord((Student){"Priya",   102, 73.0});
    addRecord((Student){"Rahul",   103, 91.5});
    addRecord((Student){"Sneha",   104, 58.0});
    addRecord((Student){"Vikram",  105, 45.0});
    printf("  5 records saved to %s\n\n", DB);

    printf("=== All Records ===\n");
    displayAll();

    printf("\n=== Search Roll 103 ===\n");
    Student found;
    int idx = searchByRoll(103, &found);
    if (idx >= 0)
        printf("  Found at index %d: %s — %.1f\n",
               idx, found.name, found.marks);

    printf("\n=== Update Priya's marks to 97.0 ===\n");
    updateMarks(102, 97.0);

    printf("\n=== All Records After Update ===\n");
    displayAll();

    printf("\n=== Topper ===\n");
    findTopper();

    return 0;
}
output
=== Adding Records ===
  5 records saved to students.dat

=== All Records ===
  Name          Roll   Marks
  ---------------------------
  Ananta         101    88.5
  Priya          102    73.0
  Rahul          103    91.5
  Sneha          104    58.0
  Vikram         105    45.0
  ---------------------------
  Total records: 5

=== Search Roll 103 ===
  Found at index 2: Rahul — 91.5

=== Update Priya's marks to 97.0 ===
  Updated Priya: marks = 97.0

=== All Records After Update ===
  Name          Roll   Marks
  ---------------------------
  Ananta         101    88.5
  Priya          102    97.0
  Rahul          103    91.5
  Sneha          104    58.0
  Vikram         105    45.0
  ---------------------------
  Total records: 5

=== Topper ===
  Topper: Priya (Roll 102) — 97.0 marks
All five file I/O patterns in one program: addRecord appends with "ab" · displayAll reads with a while(fread...==1) loop · searchByRoll does linear scan · updateMarks uses "r+b" + fseek to edit in-place · findTopper scans for the max. Data persists between runs — kill the program, run it again, all five students are still there.
checklist
  • Ex 1 — Open → Use → Close. Mode "w" creates or erases. Always check fp == NULL. fclose flushes to disk.
  • Ex 2 — fgetc returns int, not char. Loop until EOF. Declaring ch as char can cause an infinite loop on unsigned-char platforms.
  • Ex 3 — fgets(buf, size, fp) is safe — limits line length. It keeps \n. Strip with strcspn. fscanf reads tokens.
  • Ex 4 — Mode "a" always writes at end. Original data is untouched. If file missing, it is created.
  • Ex 5 — Two FILE* handles at once: one read, one write. fgetc/fputc pipe bytes. For speed, use fread/fwrite with a buffer.
  • Ex 6 — inWord flag counts word-entry transitions. Count lines via '\n'. This is exactly how wc works.
  • Ex 7 — strstr(line, keyword) returns non-NULL on match. tolower both strings for case-insensitive search.
  • Ex 8 — fwrite(ptr, size, count, fp) writes raw bytes. fread mirrors it. Binary = compact, fast, non-human-readable.
  • Ex 9 — One fwrite saves all struct records. fseek(fp, n*sizeof(S), SEEK_SET) jumps to record n directly — O(1).
  • Ex 10 — "ab" appends structs · "rb" reads · "r+b" + fseek edits in-place · while(fread==1) is the standard read-all loop.