File handling in C is done through the standard I/O library (<stdio.h>). The library gives you a small set of functions — fopen, fclose, fread, fwrite, fprintf, fscanf, fgets, fseek — that work the same way on every platform. This guide walks through each operation with a complete, compilable example.
Opening and Closing a File
Every file operation starts with fopen, which returns a FILE *. Always check for NULL and always close with fclose:
#include <stdio.h>
int main(void)
{
FILE *fp = fopen("hello.txt", "w");
if (fp == NULL) {
perror("fopen");
return 1;
}
fprintf(fp, "Hello, file!\n");
fclose(fp);
return 0;
}
Mode strings control how the file is opened:
| Mode | Meaning | File exists? | File missing? |
|---|---|---|---|
"r" |
Read only | Opens | Returns NULL |
"w" |
Write only | Truncates to zero | Creates |
"a" |
Append | Writes at end | Creates |
"r+" |
Read + write | Opens | Returns NULL |
"w+" |
Read + write | Truncates to zero | Creates |
"rb" |
Binary read | Opens | Returns NULL |
"wb" |
Binary write | Truncates to zero | Creates |
Writing Text to a File
fprintf works exactly like printf but sends output to a file:
#include <stdio.h>
int main(void)
{
FILE *fp;
int i;
fp = fopen("numbers.txt", "w");
if (fp == NULL) {
perror("fopen");
return 1;
}
for (i = 1; i <= 5; i++)
fprintf(fp, "Line %d: square = %d\n", i, i * i);
fclose(fp);
printf("Written to numbers.txt\n");
return 0;
}
Written to numbers.txt
File contents of numbers.txt:
Line 1: square = 1
Line 2: square = 4
Line 3: square = 9
Line 4: square = 16
Line 5: square = 25
Reading Text from a File
fgets reads one line at a time and is safe — it never overflows the buffer:
#include <stdio.h>
int main(void)
{
FILE *fp;
char buf[128];
fp = fopen("numbers.txt", "r");
if (fp == NULL) {
perror("fopen");
return 1;
}
while (fgets(buf, sizeof(buf), fp) != NULL)
printf("%s", buf);
fclose(fp);
return 0;
}
Line 1: square = 1
Line 2: square = 4
Line 3: square = 9
Line 4: square = 16
Line 5: square = 25
fgets keeps the newline at the end of buf. That is why the printf above does not add \n. Use fscanf when you need to parse structured data field by field.
Reading and Writing Structs — Binary Files
fwrite and fread transfer raw bytes. This is the standard way to persist struct records:
#include <stdio.h>
#include <string.h>
struct Student {
int id;
char name[32];
float gpa;
};
int main(void)
{
struct Student students[3] = {
{1, "Alice", 3.8f},
{2, "Bob", 3.2f},
{3, "Carol", 3.9f}
};
struct Student s;
FILE *fp;
int i;
/* Write */
fp = fopen("students.dat", "wb");
if (fp == NULL) { perror("fopen"); return 1; }
fwrite(students, sizeof(struct Student), 3, fp);
fclose(fp);
/* Read back */
fp = fopen("students.dat", "rb");
if (fp == NULL) { perror("fopen"); return 1; }
printf("%-4s %-20s %s\n", "ID", "Name", "GPA");
while (fread(&s, sizeof(struct Student), 1, fp) == 1)
printf("%-4d %-20s %.2f\n", s.id, s.name, s.gpa);
fclose(fp);
return 0;
}
ID Name GPA
1 Alice 3.80
2 Bob 3.20
3 Carol 3.90
Random Access — fseek and ftell
fseek positions the file pointer anywhere in the file; ftell returns the current position. Together they enable reading or updating a specific record without reading the whole file:
#include <stdio.h>
#include <string.h>
struct Student { int id; char name[32]; float gpa; };
int main(void)
{
struct Student s;
FILE *fp;
long file_size;
/* Read the second record (index 1) */
fp = fopen("students.dat", "rb");
if (fp == NULL) { perror("fopen"); return 1; }
fseek(fp, 1 * (long)sizeof(struct Student), SEEK_SET);
fread(&s, sizeof(struct Student), 1, fp);
printf("Record 2: %s, GPA %.2f\n", s.name, s.gpa);
/* Find file size */
fseek(fp, 0, SEEK_END);
file_size = ftell(fp);
printf("File size: %ld bytes (%ld records)\n",
file_size, file_size / (long)sizeof(struct Student));
fclose(fp);
return 0;
}
Record 2: Bob, GPA 3.20
File size: 144 bytes (3 records)
fseek takes three arguments: the file pointer, a byte offset, and a reference point — SEEK_SET (start), SEEK_CUR (current), or SEEK_END (end).
Appending to a File
#include <stdio.h>
int main(void)
{
FILE *fp = fopen("log.txt", "a");
if (fp == NULL) { perror("fopen"); return 1; }
fprintf(fp, "Session started\n");
fclose(fp);
printf("Appended to log.txt\n");
return 0;
}
Mode "a" always writes at the end, even if another process is writing to the file concurrently (on most systems). It never truncates an existing file.
Copying a File
#include <stdio.h>
int main(void)
{
FILE *src, *dst;
int ch;
src = fopen("numbers.txt", "r");
if (src == NULL) { perror("source"); return 1; }
dst = fopen("numbers_copy.txt", "w");
if (dst == NULL) { perror("dest"); fclose(src); return 1; }
while ((ch = fgetc(src)) != EOF)
fputc(ch, dst);
fclose(src);
fclose(dst);
printf("File copied.\n");
return 0;
}
File copied.
For large files, copy in chunks using fread/fwrite with a buffer (e.g., 4096 bytes at a time) rather than character by character.
Error Handling
| Function | Returns on error | How to check |
|---|---|---|
fopen |
NULL |
if (fp == NULL) perror(...); |
fread |
count < requested | check return value; then feof() vs ferror() |
fwrite |
count < requested | check return value; use ferror() |
fseek |
non-zero | if (fseek(...) != 0) perror(...); |
fclose |
EOF |
always check — a buffered write may fail here |
perror prints the system error message for the last failed call. ferror(fp) returns non-zero if the file has an error flag set. feof(fp) returns non-zero if the end-of-file flag is set.
How to Compile
gcc -ansi -Wall -Wextra -o fileio fileio.c
Related C Programs
- C Program for File Operations
- Copy One File to Another
- Find the Size of a File
- Count Characters in a File
- Sequential File with Records
- Delete a File Using remove()
- Structures in C – A Complete Guide — binary file I/O with struct records
- All C Programs
📖 Chapter 7 of The C Programming Language by K&R (Amazon.in) · Amazon.com covers standard I/O in full, including formatting, error handling, and the file access model that underlies everything in <stdio.h>.
Preparing for a C interview? Practice with the C Programming Quiz — 150+ questions, free on Android.