Complete guide to file handling in C using fopen, fclose, fread, fwrite, fgets, and fprintf. Includes robust error handling and file modes.
In C programming, all data stored in variables, arrays, and dynamic heap structures resides in volatile RAM and disappears the instant your application terminates.
To persist information permanently across runs, load configuration files, or process large datasets, you must use File Handling (Input/Output file streams).
In this comprehensive guide, you will master file handling in C from fundamental text read/write operations to binary struct serialization. We will explore stream pointers (FILE *), file access modes ("r", "w", "rb", "wb"), buffer safety with fgets(), random file positioning with fseek(), and bulletproof error handling with perror().
1. Quick Reference: File Access Modes in C
In C, files are accessed through a file stream handle of type FILE * created by the fopen() function:
c (ISO Standard)FILE *fp = fopen("data.txt", "mode");
| Mode | Stream Type | Action if File Exists | Action if File Does Not Exist | File Position |
|---|---|---|---|---|
"r" | Read Text | Opens for reading | Returns NULL (Error) | Beginning |
"w" | Write Text | Truncates (erases) file to 0 bytes | Creates new file | Beginning |
"a" | Append Text | Preserves existing data, writes to end | Creates new file | End of file |
"r+" | Read/Write Text | Opens for reading and writing | Returns NULL (Error) | Beginning |
"w+" | Read/Write Text | Truncates existing file | Creates new file | Beginning |
"a+" | Read/Append Text | Preserves existing data | Creates new file | End of file |
"rb" | Read Binary | Opens binary stream for reading | Returns NULL (Error) | Beginning |
"wb" | Write Binary | Truncates binary file | Creates new binary file | Beginning |
"ab" | Append Binary | Appends binary data to end | Creates new binary file | End of file |
2. The 4 Golden Steps of File Handling
Every robust file operation in C follows a 4-step sequence:
text (ISO Standard)1. Open Stream (fopen) | v 2. Validate Pointer (!fp -> perror) | v 3. Perform I/O (fgets / fprintf / fread / fwrite) | v 4. Close Stream (fclose)
Step 1 & 2: Open and Validate
Never assume fopen() succeeded. Always check for NULL to prevent segmentation faults:
c (ISO Standard)#include <stdio.h> #include <stdlib.h> FILE *fp = fopen("config.txt", "r"); if (fp == NULL) { perror("Error opening file"); // Prints exact OS error (e.g., No such file or directory) return EXIT_FAILURE; }
Step 4: Always Close Streams
Calling fclose(fp) flushes internal I/O buffers to physical disk and releases the operating system file descriptor handle.
3. Text File Operations: Reading & Writing
Writing Formatted Text: fprintf() and fputs()
c (ISO Standard)#include <stdio.h> int main(void) { FILE *fp = fopen("output.txt", "w"); if (!fp) { perror("Failed to open output.txt"); return 1; } // Write formatted strings directly to file stream fprintf(fp, "Report Generated: %s\n", "2026-08-14"); fprintf(fp, "Score: %d | Precision: %.2f\n", 98, 99.45); fclose(fp); printf("Successfully wrote to output.txt\n"); return 0; }
Safe Line-by-Line Reading: fgets()
Avoid dangerous functions like gets() (removed in C11) or unconstrained fscanf(). Always use fgets() with bounded buffer lengths:
c (ISO Standard)#include <stdio.h> #define BUFFER_SIZE 256 int main(void) { FILE *fp = fopen("output.txt", "r"); if (!fp) { perror("Failed to open output.txt"); return 1; } char line[BUFFER_SIZE]; // fgets returns NULL upon reaching EOF or encountering an error while (fgets(line, sizeof(line), fp) != NULL) { printf("Read Line: %s", line); } fclose(fp); return 0; }
4. Binary File Operations: fwrite() and fread()
When working with binary data, images, audio, or complex C structures, text serialization is slow and wastes space. Binary I/O reads and writes raw memory bytes directly.
Function Signatures:
c (ISO Standard)size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream); size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
Saving and Loading Structs to Disk: Complete Example
c (ISO Standard)#include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct { int id; char name[32]; float gpa; } StudentRecord; void saveStudent(const char *filename, const StudentRecord *student) { FILE *fp = fopen(filename, "wb"); // Write Binary if (!fp) { perror("Error opening file for write"); return; } // Write 1 struct directly from RAM to disk fwrite(student, sizeof(StudentRecord), 1, fp); fclose(fp); printf("Student record saved successfully.\n"); } void loadStudent(const char *filename, StudentRecord *student) { FILE *fp = fopen(filename, "rb"); // Read Binary if (!fp) { perror("Error opening file for read"); return; } // Read 1 struct directly from disk into RAM if (fread(student, sizeof(StudentRecord), 1, fp) == 1) { printf("Loaded: ID=%d, Name=%s, GPA=%.2f\n", student->id, student->name, student->gpa); } fclose(fp); } int main(void) { StudentRecord s1 = { .id = 101, .name = "Alice Smith", .gpa = 3.92f }; StudentRecord s2; const char *dbFile = "student.bin"; saveStudent(dbFile, &s1); loadStudent(dbFile, &s2); return 0; }
5. Random File Access: fseek(), ftell(), and rewind()
By default, file reading is sequential. You can jump to arbitrary byte offsets inside a file using fseek():
c (ISO Standard)int fseek(FILE *stream, long offset, int origin);
Origin Constants:
SEEK_SET: Offset relative to the beginning of the file.SEEK_CUR: Offset relative to the current position pointer.SEEK_END: Offset relative to the end of the file.
Professional Trick: Calculating Exact File Size in Bytes
c (ISO Standard)#include <stdio.h> long getFileSizeBytes(const char *filename) { FILE *fp = fopen(filename, "rb"); if (!fp) return -1; // Seek to the end of the file fseek(fp, 0, SEEK_END); // Get current byte position (total size) long size = ftell(fp); fclose(fp); return size; }
6. Common Pitfall: Why while (!feof(fp)) is a Bug
One of the most widespread bugs in C programming is writing:
c (ISO Standard)// ⚠️ WRONG: feof() only returns true AFTER a read has ALREADY failed! while (!feof(fp)) { fgets(buffer, sizeof(buffer), fp); printf("%s", buffer); // Duplicates the last line twice! }
The Correct Pattern: Check the Return Value of the Read Call
c (ISO Standard)// GOOD: Directly inspect the return status of fgets / fread / fscanf while (fgets(buffer, sizeof(buffer), fp) != NULL) { printf("%s", buffer); }
Frequently Asked Questions (FAQ)
1. What is the difference between Text mode and Binary mode on Windows?
On Linux and macOS, text and binary modes are identical. On Windows systems, text mode automatically translates newline characters (\n) into Carriage Return + Line Feed (\r\n) during write operations, and translates \r\n back to \n during reads. In binary mode ("rb", "wb"), bytes are transferred identically with zero translation.
2. What happens if you forget to call fclose()?
If a program crashes or terminates abnormally without calling fclose(), data remaining in the C standard library's memory buffer may never be written to disk, leading to corrupted or incomplete files. Additionally, the process leaks operating system file descriptors until termination.
3. How do you delete or rename a file in C?
Standard C <stdio.h> provides built-in system utility functions:
remove("old_file.txt");— Deletes the specified file. Returns0on success.rename("old_name.txt", "new_name.txt");— Renames or moves a file. Returns0on success.
Conclusion
File handling is an essential skill for turning transient in-memory computations into durable, production-grade applications. By structuring operations around the 4 golden steps, validating pointers with perror(), using safe reading functions like fgets() and fread(), and managing file offsets with fseek(), you can handle complex file storage requirements with total confidence.
Related Technical Resources
- Master memory pointers in Pointers in C Explained — Visual Memory & Code Guide.
- Debug pointer memory crashes in How to Fix Segmentation Fault in C.