📁 16. File Handling in C

Until now, most of our C programs have worked with data stored temporarily while the program is running. File handling allows a program to store data in files so that the data can remain available after the program ends.

C provides file-handling facilities through the standard I/O header <stdio.h>.

Core idea:

A regular file can keep data for later use. In C, an opened file is accessed through a FILE * stream and functions such as fopen(), fprintf(), fscanf(), fgets(), fputs(), fread(), fwrite(), and fclose().

16.1 What Is File Handling?

File handling means using a program to create, open, read, write, update, or close files.

For example, a student-management application may store:

  • Student ID
  • Student name
  • Marks
  • Attendance
  • Course information

Ordinary variables hold data during execution. Writing the required values to a file lets a later run read them back, provided writing and closing succeed.

16.2 Why Do We Need Files?

Temporary Data

Variables normally hold data while the program is executing.

Persistent Data

Files allow information to remain available after the program exits.

Large Data

Files can store much more information than can conveniently be kept in a small set of variables.

Data Exchange

Files can be used to exchange information between programs and processes.

16.3 File Streams in C

C's standard I/O model represents an open file through a stream.

The programmer normally uses a pointer of type:

FILE *file;

The type FILE is defined by <stdio.h>.

 Your C Program | v FILE * | v C Standard I/O | v Operating System | v File / Device 

The internal representation of FILE is managed by the library. Use its functions to read, write, position, and close the stream instead of depending on the internal fields of the FILE object.

16.4 The fopen() Function

The first major operation is opening a file.

FILE *fopen(const char *filename, const char *mode);

Example:

FILE *file; file = fopen("data.txt", "r");

If the operation succeeds, fopen() returns a stream pointer. If it fails, it returns NULL.

Always check the result of fopen().
FILE *file = fopen("data.txt", "r"); if (file == NULL) { printf("Could not open file\n"); }

16.5 File Opening Modes

Mode Purpose
"r" Open an existing file for reading.
"w" Open for writing; creates a new file or truncates an existing file.
"a" Open for writing at the end; creates the file if needed.
"r+" Open an existing file for reading and writing.
"w+" Open for reading and writing; creates or truncates the file.
"a+" Open for reading and writing with writes directed to the end.
Important difference:

Opening a file with "w" erases its old contents when opening succeeds. Use append mode to add data after existing contents, or choose an update mode when both reading and writing are needed.

16.6 Text and Binary Modes

The standard library distinguishes text and binary stream modes.

Binary mode is selected by adding b:

"rb" "wb" "ab" "rb+" "wb+" "ab+"
Text Binary
"r" "rb"
"w" "wb"
"a" "ab"

Binary mode avoids text-mode translations, such as newline conversion on some systems. It does not encrypt data or make an in-memory structure representation portable.

16.7 Reading a File

One simple character-at-a-time method uses fgetc().

#include <stdio.h>

int main(void) {
    FILE *file;
    int ch;
    file = fopen("data.txt", "r");
    if (file == NULL) {
        printf("Unable to open file\n");
        return 1;
    }
    while ((ch = fgetc(file)) != EOF) {
        putchar(ch);
    }
    fclose(file);
    return 0;
}
Why is ch an int?

Because fgetc() must be able to return every possible unsigned char value as well as the special value EOF.

16.8 EOF — End of File

EOF is a negative int constant returned by functions such as fgetc when no character is obtained because of end-of-file or a read error. It is not a special character stored at the end of every file.

int ch; while ((ch = fgetc(file)) != EOF) { putchar(ch); }
Do not use char ch for this pattern.

A char may not be able to represent the distinct EOF value. Using int allows the program to distinguish character values from EOF.

16.9 Writing to a File with fputc()

fputc() writes one character to a stream.

#include <stdio.h>

int main(void) {
    FILE *file;
    file = fopen("data.txt", "w");
    if (file == NULL) {
        return 1;
    }
    fputc('H', file);
    fputc('i', file);
    fputc('\n', file);
    fclose(file);
    return 0;
}

The file receives:

Hi

16.10 Writing Text with fprintf()

fprintf() works similarly to printf(), except that the destination stream is specified.

#include <stdio.h>

int main(void) {
    FILE *file = fopen("student.txt", "w");
    if (file == NULL) {
        return 1;
    }
    fprintf(file, "ID = %d\n", 101);
    fprintf(file, "Marks = %.2f\n", 87.5);
    fclose(file);
    return 0;
}

The first argument identifies the destination stream.

16.11 Reading Formatted Data with fscanf()

fscanf() reads formatted input from a specified stream.

#include <stdio.h>

int main(void) {
    FILE *file;
    int id;
    float marks;
    file = fopen("student.txt", "r");
    if (file == NULL) {
        return 1;
    }
    if (fscanf(file, "%d %f", &id, &marks) == 2) {
        printf("ID = %d\n", id);
        printf("Marks = %.2f\n", marks);
    }
    fclose(file);
    return 0;
}
If you ask for two fields, check that the return value is 2 before using both. A smaller value can mean incomplete or malformed input.

fscanf() returns the number of input items successfully assigned, or EOF if an input failure occurs before the first conversion.

16.12 Reading Lines with fgets()

For text files, fgets() is often convenient for reading one line at a time.

char line[100]; while (fgets(line, sizeof line, file) != NULL) { printf("%s", line); }

On a successful read, fgets adds a null terminator and keeps a newline if one was read. A line longer than the buffer arrives in pieces, so check whether a complete line was obtained.

16.13 Writing Lines with fputs()

fputs() writes a string to a stream.

fputs("CodeBhavya\n", file);
Important:

Unlike puts(), fputs() does not automatically add a newline. Add \n yourself when needed.

16.14 Closing a File with fclose()

Close a stream when the program has finished using it. Output may be buffered, so check the close result as well as the write results when saving data matters.

fclose(file);

A successful fclose() returns 0. On failure, it returns EOF. The stream must not be used again after fclose, even if closing reported an error.

Why close the file?
  • Releases resources associated with the stream.
  • Flushes any pending buffered output; this can report an error that was not seen during an earlier write.
  • The FILE pointer no longer identifies a usable open stream after closing.

16.15 Complete Write and Read Program

#include <stdio.h>

int main(void) {
    FILE *file;
    int number;
    file = fopen("number.txt", "w");
    if (file == NULL) {
        printf("Unable to create file\n");
        return 1;
    }
    fprintf(file, "%d\n", 250);
    fclose(file);
    file = fopen("number.txt", "r");
    if (file == NULL) {
        printf("Unable to open file\n");
        return 1;
    }
    if (fscanf(file, "%d", &number) == 1) {
        printf("Number = %d\n", number);
    }
    fclose(file);
    return 0;
}

The program demonstrates the complete basic cycle:

 Open for writing | v Write 250 | v Close | v Open for reading | v Read 250 | v Close 

16.16 Append Mode

The "a" mode is used when new data should be written at the end of the existing file.

FILE *file = fopen("log.txt", "a");

If the file does not exist, it is created.

fprintf(file, "New log entry\n");

Existing contents are preserved.

16.17 Read and Write Modes

The + versions allow both reading and writing.

Mode Read? Write? Existing contents
r+ Yes Yes Must already exist
w+ Yes Yes Truncated if it exists
a+ Yes Yes Preserved; writes go to end

16.18 File Position

A stream maintains a current file position that determines where the next operation occurs.

 File: A B C D E F G H I J ^ | Current position 

Reading or writing generally advances the stream position according to the operation.

16.19 ftell()

ftell() reports the current file position indicator for a stream.

long position; position = ftell(file);

On success, save this long value as a position marker for a later fseek. For text streams, do not assume it is a byte count or subtract markers to measure file size. On failure it returns -1L.

16.20 fseek()

fseek() requests a new position. Check its result: 0 means success and a nonzero value means failure.

fseek(file, 0, SEEK_SET);

Common origins include:

Constant Meaning
SEEK_SET Beginning of the file.
SEEK_CUR Current position.
SEEK_END End of the file.

16.21 rewind()

rewind() moves the file position back to the beginning of the stream.

rewind(file);

It also clears the end-of-file and error indicators. For an operation where the program must check a positioning result directly, use fseek, which returns a status.

16.22 Binary Files

Binary file operations work with blocks of bytes rather than formatted text.

The major functions are:

  • fread()
  • fwrite()

Example:

int numbers[3] = {10, 20, 30}; fwrite(numbers, sizeof numbers[0], 3, file);

And reading:

int numbers[3]; fread(numbers, sizeof numbers[0], 3, file);

16.23 fread()

The general form is:

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

It attempts to read up to count objects, each of size bytes, into the memory beginning at ptr.

int numbers[5]; size_t n = fread( numbers, sizeof numbers[0], 5, file );

The return value counts complete objects, not bytes unless the object size is 1. A short read can mean end-of-file or an error; inspect feof and ferror after the read when that distinction matters.

16.24 fwrite()

The general form is:

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

Example:

int numbers[3] = {10, 20, 30}; size_t written = fwrite( numbers, sizeof numbers[0], 3, file );

The return value counts complete objects written. Compare it with the requested count; fewer objects means a write error. Check fclose too, because buffered output can fail later.

16.25 Structure Records and Binary Files

A structure can be written as a block of its object representation:

struct Student { int id; float marks; }; struct Student student = {101, 88.5f}; fwrite(&student, sizeof student, 1, file);

It can later be read into a structure object:

fread(&student, sizeof student, 1, file);
Important portability warning:

The bytes can depend on member sizes, padding, byte order, and compiler choices. Use an agreed text format or an explicitly defined byte format when files must be shared across systems. A raw pointer member is not saved object data: its address is not useful as a record link in another run.

16.26 feof()

feof() tells whether the end-of-file indicator is set for a stream.

if (feof(file)) { printf("End-of-file indicator is set\n"); }
Very common interview trap:

Do not use while (!feof(file)) as the main reading condition. The EOF indicator records a previous read outcome; it does not predict whether the next read will succeed. Put the read operation itself in the loop condition.

Prefer the result of the actual input function:

while (fgets(line, sizeof line, file) != NULL) { ... }

16.27 ferror()

ferror() checks whether the stream's error indicator is set.

if (ferror(file)) { printf("A file error occurred\n"); }

When a read loop stops, ferror tells whether a read error occurred. A loop ending is not automatically proof that it reached a normal end-of-file.

16.28 clearerr()

clearerr() clears the end-of-file and error indicators for a stream.

clearerr(file);

Clearing indicators does not repair the underlying problem. Handle the cause before retrying, and use only a stream that is still open.

16.29 perror() and File Errors

For an operation that sets errno on failure, perror() can print a message based on the current errno value.

#include <stdio.h>

int main(void) {
    FILE *file = fopen("missing.txt", "r");
    if (file == NULL) {
        perror("fopen");
        return 1;
    }
    fclose(file);
    return 0;
}

Print the message promptly so another operation does not replace the relevant errno value. Do not assume every standard I/O failure must set errno on every C implementation.

16.30 Temporary Files

The standard library also provides facilities for temporary files.

For example:

FILE *file = tmpfile();

tmpfile() creates a temporary binary update stream and returns a stream pointer, or NULL on failure.

The temporary file is removed automatically when its stream is closed or the program terminates normally. As with other opens, check the result before using it.

16.31 Standard Streams

C starts with three standard text streams available through <stdio.h>:

Stream Typical purpose
stdin Standard input
stdout Standard output
stderr Standard error output

For example:

fprintf(stderr, "Something went wrong\n");

16.32 File Handling Program Trace

FILE *file; file = fopen("marks.txt", "w"); fprintf(file, "85\n"); fclose(file);
 Step 1 file | v No stream yet Step 2 fopen("marks.txt", "w") | v file | v Open stream | v marks.txt Step 3 fprintf(file, "85\n") | v File receives: 85 Step 4 fclose(file) | v Stream closed 

16.33 File Reading Trace

FILE *file = fopen("data.txt", "r"); char line[50]; while (fgets(line, sizeof line, file) != NULL) { printf("%s", line); } fclose(file);
 data.txt Line 1 Line 2 Line 3 | v fgets() | v line[50] | v printf() | v next fgets() | v NULL | v Loop ends | v fclose() 

16.34 Text File vs Binary File

Text File Binary File
Designed for character/text representation Works with bytes/object representations
Human-readable when contents are textual May not be directly human-readable
fprintf(), fscanf(), fgets(), fputs() fread(), fwrite()
Useful for logs and simple text data Useful for structured binary data where appropriate

16.35 Updating a File Safely

When a program needs to update stored information, it must carefully choose the opening mode and manage the file position.

For example:

FILE *file = fopen("data.txt", "r+");

The stream permits both reading and writing, but the programmer must follow the standard's rules for switching between reading and writing operations.

Important:

When using an update stream such as r+, w+, or a+, use fflush or a positioning function before switching from output to input. Before switching from input to output, use a positioning function unless the input reached end-of-file. Positioning functions include fseek(), fsetpos(), or rewind(). Flushing output alone is not the general solution for switching from input to output.

16.36 Common File Handling Mistakes

Mistake 1

Using a file pointer without checking whether fopen() returned NULL.

Mistake 2

Using "w" when the intention was to preserve existing data.

Mistake 3

Forgetting to close a stream.

Mistake 4

Using while (!feof(file)) as the main reading condition.

Mistake 5

Using char instead of int for fgetc() results when EOF must be distinguished.

Mistake 6

Assuming binary structure files are portable between all systems.

16.37 Common Confusions

Confusion Correct Understanding
fopen() creates every file Creation depends on the selected mode and whether the operation succeeds.
"w" appends data No. It writes from the beginning and truncates an existing file.
"a" overwrites the file No. It preserves existing contents and writes at the end.
EOF is a character stored in every file No. It is a special return value used by input functions.
feof() tells us the next read will fail Not exactly. It reports whether the stream's EOF indicator is already set.
fclose() deletes a file No. It closes the stream.
Binary file means encrypted file No. Binary describes representation, not security.

16.38 Complete File Handling Example

#include <stdio.h>

int main(void) {
    FILE *file;
    int i;
    file = fopen("numbers.txt", "w");
    if (file == NULL) {
        perror("numbers.txt");
        return 1;
    }
    for (i = 1; i <= 5; i++) {
        fprintf(file, "%d\n", i * 10);
    }
    if (fclose(file) != 0) {
        perror("fclose");
        return 1;
    }
    file = fopen("numbers.txt", "r");
    if (file == NULL) {
        perror("numbers.txt");
        return 1;
    }
    printf("Stored numbers:\n");
    while (fscanf(file, "%d", &i) == 1) {
        printf("%d\n", i);
    }
    if (ferror(file)) {
        perror("numbers.txt");
        fclose(file);
        return 1;
    }
    fclose(file);
    return 0;
}
 PROGRAM | v fopen("numbers.txt", "w") | v Write 10, 20, 30, 40, 50 | v fclose() | v fopen("numbers.txt", "r") | v Read values | v Display values | v fclose() 

16.39 Text Records with fgets and sscanf

Read a line, then use sscanf to extract the expected fields. Check how many fields were converted and limit %s to the destination size minus one. This example demonstrates field conversion; complete validation also needs checks for extra data and overlong lines.

Complete C program:

#include <stdio.h>

int main(void)
{
    FILE *fp = tmpfile();
    if (fp == NULL) { perror("tmpfile"); return 1; }
    fputs("Ravi 85\nPriya 90\n", fp);
    rewind(fp);
    char line[128], name[32];
    int mark;
    while (fgets(line, sizeof line, fp)) {
        if (sscanf(line, "%31s %d", name, &mark) == 2)
            printf("%s: %d\n", name, mark);
        else
            fputs("Invalid record\n", stderr);
    }
    if (ferror(fp)) { perror("read"); fclose(fp); return 1; }
    fclose(fp);
    return 0;
}

Expected output:

Ravi: 85
Priya: 90

Step-by-step explanation:

  1. tmpfile creates a temporary update stream and is not guaranteed to succeed on every system.
  2. rewind positions the stream at the beginning for reading.
  3. The code displays a record when two fields are converted. A strict record reader must also reject unwanted trailing text; a conversion count alone does not prove the whole line is valid.
Remember: For strict validation, reject unexpected trailing text and detect lines longer than the buffer. The record format must be defined explicitly.

16.40 Quick Revision

📌 FILE * → File pointer

📌 fopen() → Opens a file

📌 fclose() → Closes a file

📌 fprintf() → Writes formatted data

📌 fscanf() → Reads formatted data

📌 fputc() → Writes one character

📌 fgetc() → Reads one character

📌 fputs() → Writes a string

📌 fgets() → Reads a line/string

📌 fread() → Reads binary data

📌 fwrite() → Writes binary data

📌 fseek() → Moves file position

📌 ftell() → Gets current position

📌 rewind() → Returns to beginning

📌 EOF → End-of-file condition
INTERACTIVE LEARNING

🎬 File Handling — File Lifecycle

Follow the normal sequence for safely opening, using, and closing a file.

PROGRAM TRACING

🔎 Program Tracing — File Handling

Trace writing to a file stream, moving back to the beginning, reading the value, and closing the stream.

16.41 Quick MCQs

Select an answer first, then click Check Answer. A correct choice becomes green. If the answer is wrong, your choice becomes red and the correct option becomes green. The explanation appears below.

1. Which type is used for a file pointer?
2. Which function opens a file?
3. Which function closes a file?
4. Which mode opens a file for reading?
5. Which mode can erase existing contents when opening a file?
6. Which mode is used to append data?
7. Which function writes formatted data to a file?
8. Which function reads one character from a file?
9. Which function writes binary blocks?
10. Which function changes the file position?
PRACTICE

16.42 🎯 Practice Problems

Practice text files, binary files, append mode, copying, searching, position functions, and record management. Use 💻 Solve It Yourself first, open Hint only when needed, and use Show Program after attempting the problem.

📈 File Handling Practice Progress
Solved 0 / 20
Completed with Solution 0
Total Score 0 / 400
Completion 0%
A problem counts as Solved when all test cases pass without opening the full solution. Problems completed after viewing the official solution are tracked separately.
1. Create and Open a Text File for Writing

Problem 1: Create a text file named data.txt using write mode and report whether opening succeeded.

Input: No input.

Output: Print "File Created" if the file opens successfully.

2. Write Student Name and Marks

Problem 2: Write a student's one-word name and marks to a text file, then reopen the file and display the stored record.

Input: One-word name and integer marks.

Output: Print the record read back from the file.

3. Read and Display a Text File

Problem 3: Write an input line to a text file and then read and display the complete file contents.

Input: One line of text.

Output: Print the same line after reading it from the file.

4. Write Characters Using fputc()

Problem 4: Write a word to a file one character at a time using fputc(), then display the stored word.

Input: One word.

Output: Print the stored word.

5. Read Characters Using fgetc()

Problem 5: Store a line in a file and read it character by character using fgetc().

Input: One line of text.

Output: Print the text read by fgetc().

6. Write a String Using fputs()

Problem 6: Write a complete input line to a file using fputs(), then print the stored line.

Input: One line of text.

Output: Print the stored line.

7. Read Lines Using fgets()

Problem 7: Create a file containing three lines and read them back using fgets().

Input: Three lines of text.

Output: Print the three lines in the same order.

8. Count Characters in a File

Problem 8: Store a line in a text file and count the number of characters using file reading.

Input: One line of text.

Output: Print the number of stored characters.

9. Count Lines in a File

Problem 9: Store N input lines in a file and count how many lines are present.

Input: N followed by N lines.

Output: Print N as counted from the file.

10. Count Words in a Text File

Problem 10: Store a sentence in a file and count its whitespace-separated words.

Input: One line of text.

Output: Print the word count.

11. Append a New Record

Problem 11: Create a file with one record, append a second record using append mode, then display both records.

Input: Two lines, one record per line.

Output: Print the two stored records in order.

12. Copy One File to Another

Problem 12: Store input text in source.txt, copy it to destination.txt character by character, then display the destination file.

Input: One line of text.

Output: Print the copied text.

13. Store Student Details in a Binary File

Problem 13: Read one student record, write the structure to a binary file using fwrite(), then report success.

Input: Roll number, one-word name, and marks.

Output: Print "Binary Record Stored".

14. Read Student Details from a Binary File

Problem 14: Write one student structure to a binary file, then read it back with fread() and display it.

Input: Roll number, one-word name, and marks.

Output: Print the record read from the binary file.

15. Demonstrate fseek()

Problem 15: Write ABCDE to a file, use fseek() to move to offset 2 from the beginning, and print the character at that position.

Input: No input.

Output: Print "C".

16. Find Current File Position with ftell()

Problem 16: Write ABCDE, reopen the file, read two characters, and print the current file position using ftell().

Input: No input.

Output: Print the reported position after reading two characters.

17. Move to the Beginning with rewind()

Problem 17: Read two characters from a file, call rewind(), and print the first character again.

Input: No input.

Output: Print "A".

18. Search for a Word in a Text File

Problem 18: Store one sentence in a file and search for a target word.

Input: First line: sentence. Second line: one target word.

Output: Print "Found" or "Not Found".

19. Store and Display 10 Student Records

Problem 19: Read exactly ten student records, store them in a text file, then display all records.

Input: Ten lines: roll number, one-word name, integer marks.

Output: Print the ten records, one per line.

20. Student Record Management System

Problem 20: Implement add, display, search, and update operations on student records stored in a text file within one program run.

Input: Four initial records count N and records, then search roll, then update roll and new marks.

Output: Print the search result, then all updated records.

16.43 Key Takeaway

🎯 Remember:

FILE * → File pointer

fopen() → Open

fclose() → Close

fprintf() → Formatted write

fscanf() → Formatted read

fputc() → Write character

fgetc() → Read character

fputs() → Write string

fgets() → Read line

fwrite() → Binary write

fread() → Binary read

fseek() → Move position

ftell() → Current position

rewind() → Beginning
INTERVIEW PREPARATION

🎤 File Handling — Interview Questions

1. Why must fopen() normally be checked against NULL?
2. What is the difference between "w" and "a" mode?
3. Why is int commonly used to store the result of fgetc()?
4. What is the difference between text and binary file operations?
5. What does fseek() do?
6. What does ftell() return?
7. What is a common safe pattern for binary structure files?
8. Why should every successfully opened stream eventually be closed?
PLACEMENT TIPS

💡 File Handling — Placement Tips

  • Always check the return value of fopen() before reading from or writing to the stream.
  • Choose file mode carefully: "w" can truncate existing content, while "a" preserves it and writes at the end.
  • Store fgetc() results in an int so that EOF can be distinguished from valid character values.
  • Check return values from fread(), fwrite(), fscanf(), and other I/O functions when correctness matters.
  • For random-access questions, clearly track the file-position indicator before and after fseek(), ftell(), and rewind().
  • Raw binary structure files are convenient for exercises, but they are not automatically portable across different machines or structure layouts.
EXTRA PRACTICE

✍️ File Handling — Extra Practice Questions

  1. Merge the contents of two text files into a third file.
  2. Count uppercase letters, lowercase letters, digits, and spaces in a file.
  3. Replace every occurrence of a target word while copying one text file to another.
  4. Store employee records in a binary file and search by employee ID.
  5. Update one fixed-size binary record using fseek().
  6. Create a simple file-backed login system that stores usernames and hashed-password placeholders.
← Previous Topic: Preprocessor & Header Files Next Topic: Dynamic Memory Allocation →