Docs

README

Reading Files in C

Table of Contents

  1. •Introduction
  2. •Reading Characters - fgetc() and getc()
  3. •Reading Strings - fgets()
  4. •Formatted Reading - fscanf()
  5. •Reading Binary Data - fread()
  6. •Reading Lines with getline()
  7. •End of File (EOF) Detection
  8. •Reading Entire File
  9. •Common Reading Patterns
  10. •Error Handling While Reading
  11. •Performance Considerations
  12. •Best Practices
  13. •Summary

Introduction

Reading data from files is a fundamental operation in C programming. C provides multiple functions for reading, each suited for different scenarios:

FunctionPurposeBest For
fgetc()Read single characterCharacter-by-character processing
fgets()Read line/stringLine-oriented text files
fscanf()Formatted readingStructured data with known format
fread()Binary readingBinary files, bulk data
getline()Read line (dynamic)Lines of unknown length

Reading Characters - fgetc() and getc()

fgetc() Function

Reads a single character from a file.

int fgetc(FILE *stream);

Returns:

  • •Character read as unsigned char cast to int
  • •EOF on end-of-file or error

Example:

#include <stdio.h>

int main() {
    FILE *fp = fopen("input.txt", "r");
    if (fp == NULL) {
        perror("Error");
        return 1;
    }

    int ch;  // Use int, not char!
    while ((ch = fgetc(fp)) != EOF) {
        putchar(ch);  // Print the character
    }

    fclose(fp);
    return 0;
}

Why Use int Instead of char?

ASCII characters: 0 to 127 (or 255 for extended)
EOF constant:     -1 (typically)

If you use char:
  - Some systems treat char as unsigned (0-255)
  - EOF (-1) becomes 255, which is a valid character!
  - Loop never terminates

Always use int for fgetc() return value.

getc() Function

int getc(FILE *stream);
  • •Functionally identical to fgetc()
  • •May be implemented as a macro (faster but less safe)
  • •fgetc() is always a function (safer)
/* Both work the same way */
int c1 = fgetc(fp);
int c2 = getc(fp);

Character Reading Workflow

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                  Character Reading Process                  │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                                                             │
│   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”    ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”    ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”       │
│   │   File   │───►│  fgetc()   │───►│  int ch      │       │
│   │ [A][B][\n]│    │            │    │ (or EOF)     │       │
│   ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜    ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜    ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜       │
│        ^                                    │               │
│        │                                    ā–¼               │
│   ā”Œā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”      │
│   │  Position moves forward after each fgetc()      │      │
│   │  First call: returns 'A', position → B          │      │
│   │  Second call: returns 'B', position → \n        │      │
│   │  Third call: returns '\n', position → EOF       │      │
│   │  Fourth call: returns EOF                       │      │
│   ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜      │
│                                                             │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

ungetc() - Push Character Back

int ungetc(int c, FILE *stream);

Pushes a character back onto the input stream.

int ch = fgetc(fp);
if (ch == '#') {
    ungetc(ch, fp);  // Put it back
    // ... handle comment line differently
}

Important: Only one character of pushback is guaranteed.


Reading Strings - fgets()

Syntax

char *fgets(char *str, int n, FILE *stream);

Parameters

ParameterDescription
strBuffer to store the string
nMaximum characters to read (including null terminator)
streamFile pointer

Returns

  • •Pointer to str on success
  • •NULL on EOF or error

How fgets() Works

File content: "Hello World!\nSecond line\n"
Buffer size: 10

Call 1: fgets(buf, 10, fp)
        buf = "Hello Wor" + '\0'  (9 chars + null)

Call 2: fgets(buf, 10, fp)
        buf = "ld!\n" + '\0'      (includes newline!)

Call 3: fgets(buf, 10, fp)
        buf = "Second li" + '\0'

Call 4: fgets(buf, 10, fp)
        buf = "ne\n" + '\0'

Call 5: fgets(buf, 10, fp)
        Returns NULL (EOF)

Complete Example

#include <stdio.h>
#include <string.h>

int main() {
    FILE *fp = fopen("data.txt", "r");
    if (fp == NULL) {
        perror("Error opening file");
        return 1;
    }

    char buffer[256];
    int line_number = 0;

    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        line_number++;

        /* Remove trailing newline if present */
        size_t len = strlen(buffer);
        if (len > 0 && buffer[len-1] == '\n') {
            buffer[len-1] = '\0';
        }

        printf("Line %d: %s\n", line_number, buffer);
    }

    fclose(fp);
    return 0;
}

fgets() vs gets()

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                    fgets() vs gets()                           │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                                                                │
│  gets() - NEVER USE THIS FUNCTION!                            │
│  ─────────────────────────────────                             │
│  - Removed from C11 standard                                   │
│  - No buffer size limit                                        │
│  - Classic buffer overflow vulnerability                       │
│  - DANGEROUS and DEPRECATED                                    │
│                                                                │
│  fgets() - Safe alternative                                    │
│  ─────────────────────────────                                 │
│  - Requires buffer size parameter                              │
│  - Never writes past buffer boundary                           │
│  - Keeps the newline character (needs removal)                 │
│  - Always use this instead of gets()                           │
│                                                                │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Removing the Newline

Several methods to remove trailing newline from fgets() input:

/* Method 1: Using strlen */
char *line = fgets(buffer, sizeof(buffer), fp);
if (line != NULL) {
    size_t len = strlen(line);
    if (len > 0 && line[len-1] == '\n') {
        line[len-1] = '\0';
    }
}

/* Method 2: Using strcspn */
buffer[strcspn(buffer, "\n")] = '\0';

/* Method 3: Using strchr */
char *newline = strchr(buffer, '\n');
if (newline) *newline = '\0';

Formatted Reading - fscanf()

Syntax

int fscanf(FILE *stream, const char *format, ...);

Returns

  • •Number of successfully matched and assigned items
  • •EOF on end-of-file before any conversion

Common Format Specifiers

SpecifierTypeDescription
%dintDecimal integer
%ldlongLong integer
%ffloatFloating point
%lfdoubleDouble precision
%ccharSingle character
%schar[]String (word)
%[...]char[]Scanset (custom character set)

Basic Usage

#include <stdio.h>

int main() {
    FILE *fp = fopen("numbers.txt", "r");
    if (fp == NULL) return 1;

    int num;

    /* Read integers until EOF */
    while (fscanf(fp, "%d", &num) == 1) {
        printf("Read: %d\n", num);
    }

    fclose(fp);
    return 0;
}

Reading Structured Data

/* File format: name age salary */
/* Example: John 25 50000.00 */

#include <stdio.h>

int main() {
    FILE *fp = fopen("employees.txt", "r");
    if (fp == NULL) return 1;

    char name[50];
    int age;
    float salary;

    while (fscanf(fp, "%49s %d %f", name, &age, &salary) == 3) {
        printf("Name: %s, Age: %d, Salary: %.2f\n",
               name, age, salary);
    }

    fclose(fp);
    return 0;
}

Reading CSV-like Data

/* File format: name,age,city */

#include <stdio.h>

int main() {
    FILE *fp = fopen("data.csv", "r");
    if (fp == NULL) return 1;

    char name[50], city[50];
    int age;

    /* Note: %[^,] reads until comma */
    while (fscanf(fp, "%49[^,],%d,%49[^\n]\n", name, &age, city) == 3) {
        printf("%s is %d years old from %s\n", name, age, city);
    }

    fclose(fp);
    return 0;
}

fscanf() Pitfalls

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                    fscanf() Common Issues                       │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                                                                 │
│ 1. %s stops at whitespace                                       │
│    Input: "John Smith"                                          │
│    fscanf(fp, "%s", name)  → name = "John" only!               │
│                                                                 │
│ 2. Leftover newlines                                            │
│    After reading numbers, '\n' stays in buffer                  │
│    Next fgets() may read empty line                             │
│    Solution: add space before %c or use fgets() instead         │
│                                                                 │
│ 3. No buffer overflow protection by default                     │
│    fscanf(fp, "%s", buf)  → DANGEROUS!                         │
│    fscanf(fp, "%49s", buf)  → Safe (49 chars + null)           │
│                                                                 │
│ 4. Mixing fscanf() and fgets()                                  │
│    fscanf() leaves newline; fgets() reads it                    │
│    Consume newline: fscanf(fp, "%d\n", &x) or fgetc(fp)        │
│                                                                 │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Always Check Return Value

int count = fscanf(fp, "%d %d %d", &a, &b, &c);

if (count == EOF) {
    printf("End of file or error before reading\n");
} else if (count < 3) {
    printf("Only read %d values (expected 3)\n", count);
} else {
    printf("Successfully read all 3 values\n");
}

Reading Binary Data - fread()

Syntax

size_t fread(void *ptr, size_t size, size_t count, FILE *stream);

Parameters

ParameterDescription
ptrPointer to buffer for storing data
sizeSize of each element in bytes
countNumber of elements to read
streamFile pointer

Returns

  • •Number of elements successfully read
  • •May be less than count on EOF or error

Basic Example

#include <stdio.h>

int main() {
    FILE *fp = fopen("data.bin", "rb");  // Note: "rb" for binary
    if (fp == NULL) return 1;

    int numbers[10];
    size_t read = fread(numbers, sizeof(int), 10, fp);

    printf("Read %zu integers:\n", read);
    for (size_t i = 0; i < read; i++) {
        printf("  %d\n", numbers[i]);
    }

    fclose(fp);
    return 0;
}

Reading Structures

#include <stdio.h>

struct Student {
    int id;
    char name[50];
    float gpa;
};

int main() {
    FILE *fp = fopen("students.dat", "rb");
    if (fp == NULL) return 1;

    struct Student student;

    while (fread(&student, sizeof(struct Student), 1, fp) == 1) {
        printf("ID: %d, Name: %s, GPA: %.2f\n",
               student.id, student.name, student.gpa);
    }

    fclose(fp);
    return 0;
}

Bulk Reading for Performance

#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *fp = fopen("large_data.bin", "rb");
    if (fp == NULL) return 1;

    /* Get file size */
    fseek(fp, 0, SEEK_END);
    long file_size = ftell(fp);
    rewind(fp);

    /* Allocate buffer */
    char *buffer = malloc(file_size);
    if (buffer == NULL) {
        fclose(fp);
        return 1;
    }

    /* Read entire file at once */
    size_t read = fread(buffer, 1, file_size, fp);
    printf("Read %zu bytes\n", read);

    free(buffer);
    fclose(fp);
    return 0;
}

fread() Return Value Details

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                fread() Return Value Analysis                    │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                                                                 │
│   fread(buffer, 4, 10, fp);   // Read 10 items of 4 bytes      │
│                                                                 │
│   Return = 10:  All 10 items read successfully                  │
│   Return = 5:   Only 5 items available (partial read)           │
│   Return = 0:   Either EOF or error occurred                    │
│                                                                 │
│   To distinguish EOF from error:                                │
│   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”                  │
│   │ if (fread(...) < count) {              │                  │
│   │     if (feof(fp)) {                    │                  │
│   │         // End of file reached          │                  │
│   │     } else if (ferror(fp)) {           │                  │
│   │         // Error occurred               │                  │
│   │     }                                   │                  │
│   │ }                                       │                  │
│   ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜                  │
│                                                                 │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Reading Lines with getline()

POSIX getline() Function

ssize_t getline(char **lineptr, size_t *n, FILE *stream);

Note: This is a POSIX extension, not standard C. Available on Linux/Unix.

Parameters

ParameterDescription
lineptrPointer to buffer pointer (can be NULL)
nPointer to buffer size
streamFile pointer

Returns

  • •Number of characters read (including newline, excluding null)
  • •-1 on failure or EOF

Advantages Over fgets()

  • •Automatically allocates/reallocates buffer
  • •Handles any line length
  • •Returns actual length read

Example

#define _GNU_SOURCE  /* Required for getline on some systems */
#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *fp = fopen("text.txt", "r");
    if (fp == NULL) return 1;

    char *line = NULL;
    size_t len = 0;
    ssize_t read;

    while ((read = getline(&line, &len, fp)) != -1) {
        printf("Line length: %zd, Buffer size: %zu\n", read, len);
        printf("Content: %s", line);
    }

    free(line);  // Don't forget to free!
    fclose(fp);
    return 0;
}

Memory Management

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                   getline() Memory Behavior                     │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                                                                 │
│   First call (line = NULL, len = 0):                           │
│   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”                    │
│   │ getline allocates buffer internally   │                    │
│   │ Updates line pointer and len          │                    │
│   ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜                    │
│                                                                 │
│   Subsequent calls:                                             │
│   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”                    │
│   │ If line fits: reuses existing buffer  │                    │
│   │ If too small: realloc to larger size  │                    │
│   ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜                    │
│                                                                 │
│   After loop:                                                   │
│   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”                    │
│   │ MUST free(line) to avoid memory leak  │                    │
│   ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜                    │
│                                                                 │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

End of File (EOF) Detection

Two Ways to Detect EOF

1. Check Return Values:

/* For fgetc */
int ch;
while ((ch = fgetc(fp)) != EOF) {
    // process ch
}

/* For fgets */
char buffer[100];
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
    // process buffer
}

/* For fscanf */
int num;
while (fscanf(fp, "%d", &num) == 1) {
    // process num
}

2. Use feof() After Reading:

while (!feof(fp)) {  // This is often WRONG!
    int ch = fgetc(fp);
    // ch might be EOF here!
}

// CORRECT approach:
int ch;
while ((ch = fgetc(fp)) != EOF) {
    // process ch
}

feof() Explained

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                      Understanding feof()                       │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                                                                 │
│   feof() returns true AFTER you've tried to read past EOF.     │
│   It does NOT predict if the next read will reach EOF.          │
│                                                                 │
│   Common WRONG pattern:                                         │
│   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”                  │
│   │ while (!feof(fp)) {                    │                  │
│   │     fscanf(fp, "%d", &n);  // May fail │                  │
│   │     printf("%d\n", n);     // Prints garbage!             │
│   │ }                                       │                  │
│   ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜                  │
│                                                                 │
│   CORRECT pattern:                                              │
│   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”                  │
│   │ while (fscanf(fp, "%d", &n) == 1) {    │                  │
│   │     printf("%d\n", n);                 │                  │
│   │ }                                       │                  │
│   │ if (!feof(fp)) {                       │                  │
│   │     printf("Error reading file\n");    │                  │
│   │ }                                       │                  │
│   ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜                  │
│                                                                 │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Distinguishing EOF from Error

#include <stdio.h>

void read_file(const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) return;

    int ch;
    while ((ch = fgetc(fp)) != EOF) {
        putchar(ch);
    }

    /* Now check WHY loop ended */
    if (feof(fp)) {
        printf("\n[End of file reached normally]\n");
    } else if (ferror(fp)) {
        printf("\n[Error reading file]\n");
        clearerr(fp);  // Clear error indicator
    }

    fclose(fp);
}

Reading Entire File

Method 1: Read into Dynamically Allocated Buffer

#include <stdio.h>
#include <stdlib.h>

char *read_entire_file(const char *filename, long *out_size) {
    FILE *fp = fopen(filename, "rb");
    if (fp == NULL) return NULL;

    /* Get file size */
    fseek(fp, 0, SEEK_END);
    long size = ftell(fp);
    rewind(fp);

    /* Allocate buffer (+1 for null terminator if text) */
    char *buffer = malloc(size + 1);
    if (buffer == NULL) {
        fclose(fp);
        return NULL;
    }

    /* Read entire file */
    long read = fread(buffer, 1, size, fp);
    if (read != size) {
        free(buffer);
        fclose(fp);
        return NULL;
    }

    buffer[size] = '\0';  // Null terminate for text

    if (out_size) *out_size = size;

    fclose(fp);
    return buffer;
}

int main() {
    long size;
    char *content = read_entire_file("example.txt", &size);

    if (content) {
        printf("File size: %ld bytes\n", size);
        printf("Content:\n%s\n", content);
        free(content);
    }

    return 0;
}

Method 2: Read Line by Line into Array

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_LINES 1000
#define MAX_LINE_LENGTH 256

int main() {
    FILE *fp = fopen("text.txt", "r");
    if (fp == NULL) return 1;

    char *lines[MAX_LINES];
    int line_count = 0;
    char buffer[MAX_LINE_LENGTH];

    while (fgets(buffer, sizeof(buffer), fp) && line_count < MAX_LINES) {
        lines[line_count] = strdup(buffer);  // Duplicate the string
        if (lines[line_count] == NULL) break;
        line_count++;
    }

    fclose(fp);

    printf("Read %d lines:\n", line_count);
    for (int i = 0; i < line_count; i++) {
        printf("%d: %s", i + 1, lines[i]);
        free(lines[i]);  // Free each line
    }

    return 0;
}

Common Reading Patterns

Pattern 1: Process File Line by Line

void process_lines(const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) return;

    char line[1024];
    int line_num = 0;

    while (fgets(line, sizeof(line), fp)) {
        line_num++;
        // Remove newline
        line[strcspn(line, "\n")] = '\0';

        // Skip empty lines
        if (line[0] == '\0') continue;

        // Skip comments
        if (line[0] == '#') continue;

        printf("Processing line %d: %s\n", line_num, line);
    }

    fclose(fp);
}

Pattern 2: Read Key-Value Configuration

void read_config(const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) return;

    char line[256];
    char key[128], value[128];

    while (fgets(line, sizeof(line), fp)) {
        // Skip comments and empty lines
        if (line[0] == '#' || line[0] == '\n') continue;

        // Parse key=value
        if (sscanf(line, "%127[^=]=%127[^\n]", key, value) == 2) {
            printf("Key: '%s', Value: '%s'\n", key, value);
        }
    }

    fclose(fp);
}

Pattern 3: Read CSV Data

void read_csv(const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) return;

    char line[1024];

    // Skip header
    fgets(line, sizeof(line), fp);

    char name[50], city[50];
    int age;
    float salary;

    while (fgets(line, sizeof(line), fp)) {
        if (sscanf(line, "%49[^,],%d,%49[^,],%f",
                   name, &age, city, &salary) == 4) {
            printf("%s, %d, %s, %.2f\n", name, age, city, salary);
        }
    }

    fclose(fp);
}

Pattern 4: Read Records of Fixed Size

struct Record {
    int id;
    char data[100];
};

void read_records(const char *filename) {
    FILE *fp = fopen(filename, "rb");
    if (fp == NULL) return;

    struct Record rec;
    int count = 0;

    while (fread(&rec, sizeof(struct Record), 1, fp) == 1) {
        count++;
        printf("Record %d: ID=%d, Data=%s\n", count, rec.id, rec.data);
    }

    printf("Total records: %d\n", count);
    fclose(fp);
}

Error Handling While Reading

Comprehensive Error Handling

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int read_file_safely(const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) {
        fprintf(stderr, "Error opening %s: %s\n",
                filename, strerror(errno));
        return -1;
    }

    char buffer[256];
    int line_count = 0;

    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        line_count++;

        /* Check for read error mid-file */
        if (ferror(fp)) {
            fprintf(stderr, "Error reading file at line %d\n", line_count);
            clearerr(fp);
            break;
        }

        printf("%s", buffer);
    }

    /* Final status check */
    if (ferror(fp)) {
        fprintf(stderr, "Error occurred while reading\n");
        fclose(fp);
        return -1;
    }

    if (feof(fp)) {
        printf("\n[Read %d lines successfully]\n", line_count);
    }

    fclose(fp);
    return line_count;
}

Error Checking Functions

FunctionPurpose
feof(fp)Returns non-zero if EOF indicator is set
ferror(fp)Returns non-zero if error indicator is set
clearerr(fp)Clears both EOF and error indicators
errnoSystem error number (set by failed operations)
strerror(errno)Human-readable error message

Performance Considerations

Buffering Strategies

#include <stdio.h>

void optimize_reading(FILE *fp) {
    /* Set larger buffer for better performance */
    char buffer[65536];  // 64KB buffer
    setvbuf(fp, buffer, _IOFBF, sizeof(buffer));

    /* Now read operations are more efficient */
}

Reading Efficiency Comparison

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                    Reading Performance                         │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                                                                │
│   SLOWEST                                                      │
│   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”                    │
│   │ while ((ch = fgetc(fp)) != EOF)     │  Character by char  │
│   ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜                    │
│                   ā–¼                                            │
│   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”                    │
│   │ while (fgets(buffer, 256, fp))       │  Line by line      │
│   ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜                    │
│                   ā–¼                                            │
│   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”                    │
│   │ fread(buffer, 1, 4096, fp)           │  Block reading     │
│   ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜                    │
│                   ā–¼                                            │
│   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”                    │
│   │ fread(buffer, 1, file_size, fp)      │  Whole file        │
│   ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜                    │
│   FASTEST                                                      │
│                                                                │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Tips for Better Performance

  1. •Use larger buffers when reading large files
  2. •Read in blocks with fread() for binary data
  3. •Minimize function calls - read more data at once
  4. •Avoid repeated opens - cache file contents if needed
  5. •Use appropriate mode - "rb" for binary is faster

Best Practices

1. Always Validate Input

/* Check filename */
if (filename == NULL || filename[0] == '\0') {
    return -1;
}

/* Check file opened successfully */
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
    perror("Error");
    return -1;
}

2. Use Appropriate Function

/* For character-by-character processing */
int ch;
while ((ch = fgetc(fp)) != EOF) { ... }

/* For line-by-line text */
char buffer[256];
while (fgets(buffer, sizeof(buffer), fp)) { ... }

/* For structured data */
while (fscanf(fp, "%d %s", &num, str) == 2) { ... }

/* For binary data */
while (fread(&record, sizeof(record), 1, fp) == 1) { ... }

3. Handle Errors Properly

if (ferror(fp)) {
    fprintf(stderr, "Read error: %s\n", strerror(errno));
    clearerr(fp);
}

4. Limit Buffer Sizes in fscanf

/* DANGEROUS */
fscanf(fp, "%s", buffer);

/* SAFE */
fscanf(fp, "%255s", buffer);  // for buffer[256]

5. Always Close Files

FILE *fp = fopen("file.txt", "r");
if (fp != NULL) {
    // ... use file ...
    fclose(fp);
    fp = NULL;
}

Summary

Quick Reference

TaskFunctionExample
Read characterfgetc()ch = fgetc(fp)
Read linefgets()fgets(buf, size, fp)
Read formattedfscanf()fscanf(fp, "%d", &n)
Read binaryfread()fread(buf, sz, n, fp)
Check EOFfeof()if (feof(fp))
Check errorferror()if (ferror(fp))

Reading Loop Patterns

/* fgetc pattern */
int ch;
while ((ch = fgetc(fp)) != EOF) { ... }

/* fgets pattern */
while (fgets(buffer, sizeof(buffer), fp) != NULL) { ... }

/* fscanf pattern */
while (fscanf(fp, format, ...) == expected_count) { ... }

/* fread pattern */
while (fread(&item, sizeof(item), 1, fp) == 1) { ... }

Common Mistakes to Avoid

  1. •Using char instead of int for fgetc() return
  2. •Using while (!feof(fp)) as loop condition
  3. •Not checking return values
  4. •Not limiting buffer size in fscanf()
  5. •Forgetting to remove newline from fgets() input
  6. •Not closing files
  7. •Not handling partial reads in fread()

Practice Exercises Preview

  1. •Read and display file contents using fgetc()
  2. •Count words, lines, and characters in a file
  3. •Read CSV data into an array of structures
  4. •Implement a simple file viewer with line numbers
  5. •Parse a configuration file with key-value pairs
README - C Programming Tutorial | DeepML