End-of-file indicates that no more input is available from a stream. In C, fgetc returns int so it can represent every character and the special value EOF. A returned EOF does not by itself prove that the stream ended: after a failed read, call feof to distinguish end-of-file from an input error. section 8.4.5
Checking a character read
A loop must treat EOF as a status, not as a character: read with fgetc, test for EOF, then use feof(stream) to confirm end-of-file or handle the error otherwise. The same distinction appears when implementing fgets: EOF plus feof ends the input; EOF without feof reports failure. section 8.4.5
EOF on output
For putchar, fputc, puts, and related output functions, EOF signals failure rather than the end of an input stream. Check the return value and use perror when a diagnostic is needed. section 8.4.1 section 8.1.3
Listing 8.2 Implementing fgets in terms of fgetc
char* fgets_manually(char s[restrict], int n,
FILE*restrict stream) {
if (!stream) return nullptr;
if (!n) return s;
/* Reads at most n-1 characters */
for (size_t i = 0; i < n-1; ++i) {
int val = fgetc(stream);
switch (val) {
/* EOF signals end-of-file or error */
case EOF: if (feof(stream)) {
s[i] = 0;
/* Has been a valid call */
return s;
} else {
/* Error */
return nullptr;
}
/* Stop at end-of-line. */
case '\n': s[i] = val; s[i+1] = 0; return s;
/* Otherwise just assign and continue. */
default: s[i] = val;
}
}
s[n-1] = 0;
return s;
}
Trace how a bounded line reader separates end-of-file from input errors after fgetc returns EOF. section 8.4.5

