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 readfgetc()returnsEOFwhen it cannot read a character
That means the correct question is not:
- "Have I reached EOF yet?"
Instead, ask:
- "Did this read succeed?"
Why this matters in real programs
Real file-reading code must handle at least two cases correctly:
- normal end of input
- read failure
feof() alone cannot do that before the read happens. If you use the read result directly, your code naturally handles both cases more safely and clearly.
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("numbers.txt", "r");
int x;
if (fp == NULL) {
perror("numbers.txt");
return 1;
}
while (fscanf(fp, "%d", &x) == 1) {
printf("Number: %d\n", x);
}
fclose(fp);
return 0;
}
fscanf() returns how many items it successfully read. For one integer, success means the return value must be 1.
Reading bytes with fread()
#include <stdio.h>
int main(void) {
FILE *fp = fopen("data.bin", "rb");
unsigned char buf[64];
size_t n;
if (fp == NULL) {
perror("data.bin");
return 1;
}
while ((n = fread(buf, 1, sizeof buf, fp)) > 0) {
fwrite(buf, 1, n, stdout);
}
fclose(fp);
return 0;
}
This processes only the bytes that were actually read.
A broken pattern
while (!feof(fp)) {
fgets(buffer, sizeof buffer, fp);
printf("%s", buffer);
}
Problems:
- the loop may execute once more after the last successful read
fgets()may returnNULLbuffermay still contain old data from the previous iteration- the program may print the last line twice or behave unpredictably
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\nbuffercontainsbanana\nprintf()printsbanana- EOF is still not set yet
Third iteration
!feof(fp)is still true- loop enters again
fgets()tries to read, but there is no more datafgets()returnsNULL- EOF indicator is now set
- but your code still does
printf("%s", buffer); bufferstill contains the old valuebanana\nbananamay be printed again
That extra iteration is the core bug.
Correct version
char buffer[100];
while (fgets(buffer, sizeof buffer, fp) != NULL) {
printf("%s", buffer);
}
Now the loop only runs when reading succeeded:
- read
apple - process
apple - read
banana - process
banana - next
fgets()fails - stop immediately
No extra iteration happens.
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) != NULL) {
/* handle user input or piped data */
}
This works whether input comes from a keyboard, a redirected file, or another command.
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)) > 0) {
process_bytes(buf, n);
}
if (ferror(fp)) {
/* handle disk or stream error */
}
Distinguishing EOF from errors
After a loop ends, code may check:
feof(fp)to confirm normal end-of-fileferror(fp)to detect a read error
That is an appropriate use of feof().
Useful rule
- Use
feof()to inspect what happened after reading stops. - Do not use
feof()to decide whether the next read will succeed.
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:
if (fgets(buf, sizeof buf, fp) != NULL) {
process(buf);
}
3. Confusing EOF with error
End-of-file is not the same as a read error.
Fix:
while (fgets(buf, sizeof buf, fp) != NULL) {
/* process */
}
if (ferror(fp)) {
perror("read error");
}
4. Processing stale data after a failed read
Broken code:
char buf[100];
while (!feof(fp)) {
fgets(buf, sizeof buf, fp);
puts(buf);
}
If the last fgets() fails, buf may still contain previous content.
5. Closing stdin
In code like the original example, fp may be stdin. Many programs should not call fclose(stdin) unless they intentionally want to close standard input.
Safer pattern:
if (fp != stdin) {
fclose(fp);
}
6. Assuming one input function behaves like another
Different functions report success differently:
fgets()returns a pointer orNULLfscanf()returns item countfread()returns number of elements readfgetc()returns a character orEOF
Always check the correct return convention for the function you use.
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) | Use int, not char, for ch |
fread() | Binary/block input | while ((n = fread(buf, 1, sizeof buf, fp)) > 0) | Supports partial reads |
feof() vs ferror()
| Function | What it tells you | When to use it |
|---|---|---|
feof(fp) | End-of-file indicator is set | After reading stops, to see if EOF was reached |
ferror(fp) | Error indicator is set | After reading stops or after a failed operation |
Wrong mindset vs right mindset
| Wrong idea | Better idea |
|---|---|
| "Loop until EOF becomes true." | "Loop while each read succeeds." |
| "Check the stream first." | "Attempt the read, then check the result." |
| "EOF means no more data is coming before I read." | "EOF is discovered by a failed read attempt." |
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()
size_t n;
while ((n = fread(buf, 1, sizeof buf, fp)) > 0) {
/* use first n bytes */
}
After the loop
if (ferror(fp)) {
perror("read error");
}
Optional EOF check:
if (feof(fp)) {
/* normal end of file */
}
Edge cases
fscanf()can stop because of invalid format, not just EOF.fread()may return less than requested near EOF.fgetc()result must be stored inint, notchar.- Failed reads do not give valid new data to process.
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.
Is while ((ch = fgetc(fp)) != EOF) safe?
Yes, if ch is an int. fgetc() returns all possible byte values plus the special EOF value, which may not fit safely in a char.
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
2D Array Loop Order and Cache Performance in C
Learn why swapping nested loops changes 2D array performance in C, using row-major memory layout, cache locality, and practical benchmarks.
Array-to-Pointer Conversion in C and C++ Explained
Learn what array-to-pointer conversion means in C and C++, how array decay works, and how it differs from a pointer to an array.
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.