Each stream has \"an error indicator that records whether a read/write error has occurred\".
It is set, usually rarely, by various functions: fgetc(
My reading of the standard is that it doesn't explicitly say fgetc
is allowed to return a non-EOF
value if the error indicator was already set on the stream on entry, but it doesn't explicitly say that it can't, either. I sympathize with Nominal Animal's observation (which I shall hoist from comments on his answer in case it gets deleted or moved to chat; allow me to grind my personal axe for a moment and observe that the policy of treating comments as "ephemeral" is harmful and should be abolished):
IMHO the standard is then bass-ackwards: there is no practical need for EOF to be sticky, but if error is not sticky, then there is a real risk of accidentally missing errors.
However, if existing implementations are all consistently not treating error as sticky, changing the behavior will be very hard to sell to the committee. Therefore, I am soliciting tests from the community:
Below is a shortened, non-interactive version of Nominal Animal's test program. It only looks at the behavior of fgetc
after a read error, not after EOF. It uses SIGALRM
to interrupt a read, instead of control-C, so you don't have to do anything but run it.
#include
#include
#include
#include
static _Noreturn void
perror_exit (const char *msg)
{
perror (msg);
exit (1);
}
static void
handler (int unused)
{
}
int
main (void)
{
struct sigaction sa;
int pipefd[2];
FILE *fp;
int ch, pa;
setvbuf (stdout, 0, _IOLBF, 0);
sa.sa_handler = handler;
sa.sa_flags = 0; /* DO interrupt blocking system calls */
sigemptyset (&sa.sa_mask);
if (sigaction (SIGALRM, &sa, 0))
perror_exit ("sigaction");
if (pipe (pipefd))
perror_exit ("pipe");
fp = fdopen (pipefd[0], "r");
if (!fp)
perror_exit ("fdopen");
printf ("before fgetc 1, feof = %d ferror = %d\n",
feof (fp), ferror (fp));
alarm (1);
ch = fgetc (fp);
if (ch == EOF)
printf ("after fgetc 1, ch = EOF feof = %d ferror = %d\n",
feof (fp), ferror (fp));
else
printf ("after fgetc 1, ch = '%c' feof = %d ferror = %d\n",
ch, feof (fp), ferror (fp));
write (pipefd[1], "x", 1);
alarm (1);
ch = fgetc (fp);
pa = alarm (0);
printf ("after fgetc 2, alarm %s\n",
pa ? "did not fire" : "fired");
if (ch == EOF)
printf ("after fgetc 2, ch = EOF feof = %d ferror = %d\n",
feof (fp), ferror (fp));
else
printf ("after fgetc 2, ch = '%c' feof = %d ferror = %d\n",
ch, feof (fp), ferror (fp));
return 0;
}
On all of the Unixes I can get at at the moment, this program's output is consistent with John Bollinger's observation that
the case of most interest, is in fact allowed:
1 0 1 Normal reading of valid data with error indicator set!
I would particularly like to know what this program prints when run on alternative Linux-based C libraries (e.g. musl, bionic); Unixes which are not Linux nor are they BSD-phylum; and Windows. If you've got anything even more exotic please try that too. I'm marking this post community wiki; please edit it to add test results.
The test program should be acceptable to any C89-compliant compiler for an environment where unistd.h
exists and signal.h
defines sigaction
, except for one use of the C11 _Noreturn
keyword which is only to squelch warnings. If your compiler complains about _Noreturn
, compile with -D_Noreturn=
; the results will not be affected. If you don't have unistd.h
, the test program will not do anything meaningful in your environment. If you don't have sigaction
you may be able to adapt the program to use alternative interfaces, but you need to persuade SIGALRM
to interrupt a blocking read
somehow.
before fgetc 1, feof = 0 ferror = 0
after fgetc 1, ch = EOF feof = 0 ferror = 1
after fgetc 2, alarm did not fire
after fgetc 2, ch = 'x' feof = 0 ferror = 1
("normal reading of valid data with error indicator set")
.
before fgetc 1, feof = 0 ferror = 0
after fgetc 1, ch = EOF feof = 0 ferror = 1
after fgetc 2, alarm did not fire
after fgetc 2, ch = EOF feof = 0 ferror = 1
("sticky error" behavior: fgetc(fp)
immediately returns EOF without calling read
when ferror(fp)
is true on entry)
.