C File I/O — Reading and Writing Files with stdio
In this tutorial, you will learn about C File I/O. We cover key concepts, practical examples, and best practices to help you master this topic.
C file I/O using the standard library's stdio.h provides buffered file access through FILE pointers: fopen opens files with modes like r, w, a, and their binary variants; fgets, fprintf, fread, fwrite read and write data; fseek/ftell navigate the file; and fclose releases resources.
What You Will Learn
- Opening files with fopen and error handling
- Reading text files with fgets and fscanf
- Writing text files with fprintf and fputs
- Binary I/O with fread and fwrite
- File positioning with fseek, ftell, rewind
- Buffering and flushing with fflush
- Temporary files with tmpfile
Why It Matters
File I/O is how programs persist data. Configuration files, log files, data files, user documents, databases, and save files all use file I/O. Understanding buffering, error handling, and binary vs text modes prevents data corruption and performance issues. Durga Antivirus Pro writes scan logs to files, reads virus definition databases, and saves quarantine files using binary file I/O.
Real-World Use
A weather station records temperature every minute. The data logger program opens a CSV file in append mode, writes one line per reading, and closes the file. If the power fails between writes, only the last reading is lost (not the entire file). The CSV file is then read by a reporting tool that parses it for graphs.
Learning Path
flowchart LR A[CMake] --> B[File I/O\nYou are here] B --> C[Standard Library] style B fill:#f90,color:#fff
Reading a Text File Line by Line
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *file = fopen("data.txt", "r");
if (file == NULL) {
perror("Failed to open file");
return 1;
}
char line[256];
int line_num = 0;
while (fgets(line, sizeof(line), file)) {
line_num++;
printf("%3d: %s", line_num, line);
}
if (ferror(file)) {
perror("Error reading file");
}
fclose(file);
return 0;
}
Writing a Text File
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
FILE *file = fopen("log.txt", "a");
if (file == NULL) {
perror("Failed to open file");
return 1;
}
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char timestamp[20];
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info);
fprintf(file, "[%s] %s\n", timestamp, "Application started");
fprintf(file, "[%s] %s\n", timestamp, "Processing data...");
fprintf(file, "[%s] %s\n", timestamp, "Shutdown complete");
fclose(file);
printf("Log entries written to log.txt\n");
return 0;
}
Binary File I/O
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int id;
char name[64];
double balance;
} Record;
int main() {
// Write binary records
FILE *file = fopen("records.bin", "wb");
if (file == NULL) {
perror("Failed to create file");
return 1;
}
Record records[] = {
{1, "Alice", 1000.50},
{2, "Bob", 2500.75},
{3, "Charlie", 320.00}
};
size_t written = fwrite(records, sizeof(Record), 3, file);
if (written != 3) {
perror("Write error");
fclose(file);
return 1;
}
fclose(file);
// Read binary records
file = fopen("records.bin", "rb");
if (file == NULL) {
perror("Failed to open file");
return 1;
}
Record r;
while (fread(&r, sizeof(Record), 1, file) == 1) {
printf("ID=%d, Name=%s, Balance=%.2f\n", r.id, r.name, r.balance);
}
fclose(file);
return 0;
}
File Positioning
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *file = fopen("test.txt", "w+");
if (file == NULL) {
perror("Failed to open file");
return 1;
}
fprintf(file, "Hello World This is a test file");
// Get current position
long pos = ftell(file);
printf("After writing: position = %ld\n", pos);
// Seek to beginning
rewind(file);
printf("After rewind: position = %ld\n", ftell(file));
// Seek to a specific position
fseek(file, 6, SEEK_SET); // 6 bytes from start
char buffer[32] = {0};
fgets(buffer, sizeof(buffer), file);
printf("From offset 6: %s\n", buffer);
// Seek from current position
fseek(file, -5, SEEK_CUR); // 5 bytes back
fgets(buffer, sizeof(buffer), file);
printf("After going back 5: %s\n", buffer);
// Seek from end
fseek(file, -10, SEEK_END);
fgets(buffer, sizeof(buffer), file);
printf("Last 10 chars: %s\n", buffer);
fclose(file);
return 0;
}
Reading Formatted Data with fscanf
#include <stdio.h>
#include <stdlib.h>
int main() {
// Create a CSV file
FILE *file = fopen("cities.csv", "w");
if (!file) { perror("Error"); return 1; }
fprintf(file, "City,Country,Population\n");
fprintf(file, "Tokyo,Japan,13929286\n");
fprintf(file, "Delhi,India,16787941\n");
fprintf(file, "Shanghai,China,24870895\n");
fclose(file);
// Parse CSV
file = fopen("cities.csv", "r");
if (!file) { perror("Error"); return 1; }
char header[256];
fgets(header, sizeof(header), file); // Skip header
char city[64], country[64];
int population;
while (fscanf(file, "%63[^,],%63[^,],%d\n",
city, country, &population) == 3) {
printf("%s, %s: %d\n", city, country, population);
}
fclose(file);
return 0;
}
Temporary Files
#include <stdio.h>
#include <stdlib.h>
int main() {
// Create a temporary file (automatically deleted on close)
FILE *tmp = tmpfile();
if (tmp == NULL) {
perror("tmpfile failed");
return 1;
}
fprintf(tmp, "Temporary data that will not persist\n");
fprintf(tmp, "This file is deleted when closed\n");
// Rewind and read back
rewind(tmp);
char line[256];
while (fgets(line, sizeof(line), tmp)) {
printf("%s", line);
}
fclose(tmp); // File is automatically deleted
printf("Temporary file closed and deleted.\n");
return 0;
}
Error Handling in File I/O
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
void safe_close(FILE **f) {
if (*f) {
fclose(*f);
*f = NULL;
}
}
int main() {
FILE *file = fopen("/nonexistent/file.txt", "r");
if (file == NULL) {
fprintf(stderr, "Error opening file: %s (errno=%d)\n",
strerror(errno), errno);
return 1;
}
// Write to a file opened for reading
if (fprintf(file, "data") < 0) {
fprintf(stderr, "Write failed: %s\n", strerror(errno));
safe_close(&file);
return 1;
}
safe_close(&file);
return 0;
}
Common Mistakes
Not checking fopen return value: fopen returns NULL on failure. Using the FILE pointer without checking causes a segfault. Always check before proceeding.
Forgetting to close files: Failing to fclose leads to resource leaks. The OS limits the number of open files (ulimit -n). Use a wrapper function or cleanup pattern to ensure fclose always runs.
Ignoring ferror after read/write loops: EOF alone does not indicate success. ferror checks for actual I/O errors (disk full, device error). Always check after read/write loops.
Assuming fgets includes the newline: fgets includes the newline in the buffer if there is room. Code that strips it must handle the case where the line is truncated (no newline in buffer).
Binary mode on Windows without "b": Windows treats "\n" as "\r\n" in text mode, corrupting binary data. Always use "rb" or "wb" for binary files, even on Linux (the "b" is ignored on POSIX but makes code portable).
Practice Questions
- What is the difference between "r", "w", and "a" modes in fopen?
- What does ftell return and when might it return -1?
- Why is fread/fwrite preferred over fscanf/fprintf for structured data?
- What happens if you write to a file opened in "r" mode?
- Challenge: Write a program that reads a binary file containing 1000 integers (4 bytes each), sorts them in memory, and writes the sorted result to a new file. Handle files larger than available memory by using an external sorting algorithm (merge chunks).
Mini Project
Build a file-based key-value store:
- A binary file stores records of type: key (32-byte string), value (256-byte string), flags (uint8_t: active/deleted)
- Functions: put(key, value), get(key, value_buffer), delete(key), list_all()
- put: if key exists, overwrite; otherwise append
- get: linear search, return the value or NULL
- delete: mark the record as deleted (do not physically remove)
- Compaction utility: create a new file without deleted records, swap files
- Handle: file corruption detection (magic number header), file size limits, concurrent access prevention (lock file)
- Bonus: add an in-memory hash index for O(1) lookups
FAQ
{{< faq "How do I check if a file exists without opening it?" "Use access() with F_OK: if (access(\"file.txt\", F_OK) == 0) { ... }. But beware of TOCTOU race conditions (check then use)." >}}What is Next
Proceed to Standard Library to explore commonly used libc functions for string handling, memory, math, and time. Then continue with String Handling.