Question
In C, why is it incorrect to use feof() as the condition that controls a file-reading loop?
For example:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
char *path = "stdin";
FILE *fp = argc > 1 ? fopen(path = argv[1], "r") : stdin;
if (fp == NULL) {
perror(path);
return EXIT_FAILURE;
}
while (!feof(fp)) { /* THIS IS WRONG */
/* Read and process data from file... */
}
if (fp != stdin && fclose(fp) != 0) {
perror(path);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
What is wrong with this loop, and what is the correct pattern for reading from a file until no more input is available?
Short Answer
By the end of this page, you will understand why feof() does not predict the future in C. You will learn that the end-of-file flag is only set after a read attempt fails, why while (!feof(fp)) often causes one extra loop iteration or bad data handling, and how to correctly write file-reading loops using fgets(), fscanf(), fread(), or fgetc() based on the operation you are performing.
Concept
feof(fp) tells you whether the end-of-file indicator is currently set for a stream. That sounds useful, but the key detail is this:
- The EOF indicator is not set until you try to read past the end.
- That means
feof(fp)is a result check, not a loop control predictor.
So this pattern is flawed:
while (!feof(fp)) {
/* try to read */
}
Why? Because before the final read attempt, feof(fp) is still false. The loop enters one more time, your code attempts a read, and that read fails. Only then does EOF become true.
This usually leads to problems such as:
- processing stale data from the previous iteration
- running the loop one extra time
- confusing end-of-file with read errors
- writing fragile code that behaves differently depending on the input function used
The real rule
In C, you should control the loop using the return value of the read operation itself.
Examples:
fgets()returnsNULLon failure or EOFfscanf()returns the number of successfully read itemsfread()returns the number of elements actually read
Mental Model
Think of reading a file like taking pages from a stack.
feof(fp)is not checking whether there are pages left in advance.- It only tells you that you already tried to take a page and there was none.
So while (!feof(fp)) is like saying:
- "As long as nobody has told me the stack is empty, keep working."
But the only way to find out the stack is empty is to try taking one more page.
That means the loop discovers the file is finished too late.
A better model is:
- "Try to take a page. If you got one, process it. If not, stop."
That is exactly what this pattern does:
while (fgets(buffer, sizeof buffer, fp) != NULL) {
/* process buffer */
}
Syntax and Examples
Correct pattern: use the read operation in the condition
Reading lines with fgets()
#include <stdio.h>
int main(void) {
FILE *fp = fopen("data.txt", "r");
char buffer[256];
if (fp == NULL) {
perror("data.txt");
return 1;
}
while (fgets(buffer, sizeof buffer, fp) != NULL) {
printf("Read: %s", buffer);
}
fclose(fp);
return 0;
}
Here, the loop continues only when fgets() successfully reads a line.
Reading formatted input with fscanf()
#include <stdio.h>
int main(void) {
FILE *fp = fopen(, );
x;
(fp == ) {
perror();
;
}
((fp, , &x) == ) {
(, x);
}
fclose(fp);
;
}
Step by Step Execution
Consider this file content:
apple
banana
Now look at this incorrect code:
char buffer[100];
while (!feof(fp)) {
fgets(buffer, sizeof buffer, fp);
printf("%s", buffer);
}
What happens step by step
Before loop starts
- EOF flag is not set
!feof(fp)is true- loop enters
First iteration
fgets()readsapple\nbuffercontainsapple\nprintf()printsapple- EOF is still not set
Second iteration
!feof(fp)is still true- loop enters again
fgets()readsbanana\n
Real World Use Cases
This concept appears everywhere input is processed.
Reading log files
A program may read one line at a time from a server log:
while (fgets(line, sizeof line, logFile) != NULL) {
/* parse log entry */
}
Importing CSV or text data
When reading rows from a data file, you should stop when the read fails, not when feof() happens to change later.
Parsing configuration files
Applications often read key-value lines until input ends:
- database config files
- environment-style text files
- simple app settings
Copying binary files
For images, archives, or audio files, fread() is the correct basis for loop control:
while ((n = fread(buf, 1, sizeof buf, src)) > 0) {
fwrite(buf, 1, n, dst);
}
Reading from standard input
Interactive tools and shell pipelines often use:
while (fgets(line, sizeof line, stdin) != ) {
}
Real Codebase Usage
In real C codebases, developers usually build input loops around successful reads, then check feof() or ferror() afterward if they need to know why input stopped.
Common pattern: read first, then inspect state
while (fgets(buf, sizeof buf, fp) != NULL) {
/* process line */
}
if (ferror(fp)) {
perror("read failed");
}
This is clearer because:
- the loop condition matches the real action
- processing only happens for valid data
- error handling is separated from normal input flow
Guard clause style
Programs often validate resources early:
FILE *fp = fopen(path, "r");
if (fp == NULL) {
perror(path);
return EXIT_FAILURE;
}
Then they use a tight read loop based on the input function's return value.
Handling partial reads
With binary I/O, partial reads are normal near EOF. Real code handles the returned size carefully:
while ((n = fread(buf, 1, sizeof buf, fp)) > ) {
process_bytes(buf, n);
}
(ferror(fp)) {
}
Common Mistakes
1. Using feof() before reading
Broken code:
while (!feof(fp)) {
fscanf(fp, "%d", &x);
printf("%d\n", x);
}
Why it is wrong:
feof()is still false before the failed read- the final
fscanf()may fail xmay keep its old value and be printed again
Fix:
while (fscanf(fp, "%d", &x) == 1) {
printf("%d\n", x);
}
2. Ignoring the return value of the input function
Broken code:
fgets(buf, sizeof buf, fp);
process(buf);
If fgets() fails, buf should not be processed.
Fix:
(fgets(buf, buf, fp) != ) {
process(buf);
}
Comparisons
| Pattern | Good for | Correct loop condition | Notes |
|---|---|---|---|
feof(fp) | Checking stream state after reading stops | Not recommended as loop control | EOF is set only after a failed read |
fgets() | Reading text line by line | while (fgets(buf, sizeof buf, fp) != NULL) | Best for line-oriented text input |
fscanf() | Structured formatted input | while (fscanf(fp, "%d", &x) == 1) | Must check exact item count |
fgetc() | Reading one character at a time | while ((ch = fgetc(fp)) != EOF) |
Cheat Sheet
Quick rules
- Do not use
while (!feof(fp))for reading loops. - EOF is set after a read attempts to go past the end.
- Use the read function's return value to control the loop.
- Use
feof()andferror()after the loop if needed.
Correct patterns
fgets()
while (fgets(buf, sizeof buf, fp) != NULL) {
/* use buf */
}
fscanf()
while (fscanf(fp, "%d", &x) == 1) {
/* use x */
}
fgetc()
int ch;
while ((ch = fgetc(fp)) != EOF) {
/* use ch */
}
fread()
FAQ
Why does while (!feof(fp)) read one extra time?
Because the EOF flag is only set after a read attempt fails at the end of the file. The loop condition is checked too early.
Is feof() ever useful in C?
Yes. It is useful after a read loop ends, when you want to know whether input stopped because of EOF or because of an error.
What should I use instead of feof() in a loop?
Use the return value of the reading function, such as fgets() != NULL, fscanf(...) == expected_count, or fread(...) > 0.
Can fscanf() stop for reasons other than EOF?
Yes. It can fail because the input does not match the format string. That is why checking its exact return value matters.
Should I check both feof() and ferror()?
Usually only after the loop. If a read loop ends unexpectedly, ferror() helps detect errors, and feof() can confirm normal end-of-file.
Why is processing stale data dangerous?
If a read fails and you still use the buffer or variable, you may reuse old values from the previous successful read, causing duplicate output or incorrect logic.
Mini Project
Description
Build a small C program that reads a text file line by line and prints each line with a line number. This project demonstrates the correct way to loop over file input without using feof() as the loop condition.
Goal
Create a program that opens a file, reads each line safely with fgets(), numbers the lines, and handles file errors correctly.
Requirements
- Accept a filename from the command line.
- Open the file for reading and report an error if it cannot be opened.
- Read the file line by line using a correct loop condition.
- Print each line prefixed with its line number.
- After reading, report a read error if one occurred.
- Close the file before exiting.
Keep learning
Related questions
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.
C printf Format Specifier for bool: How to Print Boolean Values
Learn how to print bool values in C with printf, why no %b/%B specifier exists, and the common patterns to print true/false or 0/1.
Calling C or C++ from Python: Building Python Bindings
Learn the quickest ways to call C or C++ from Python, including ctypes, C extensions, Cython, and binding tools with practical examples.