How a stream error indicator affects following input code?

后端 未结 4 1890
佛祖请我去吃肉
佛祖请我去吃肉 2021-01-06 03:20

Each stream has \"an error indicator that records whether a read/write error has occurred\".

It is set, usually rarely, by various functions: fgetc(

4条回答
  •  温柔的废话
    2021-01-06 04:22

    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.

    Results

    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")

    • Linux with glibc 2.27
    • NetBSD 7.1.2
    • FreeBSD 11.2-RELEASE-p4
    • macOS 10.14, clang-1000.10.44.2
    • macOS 10.14, gcc 8.2.0 (Homebrew)

    .

    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)

    • Plan 9 (compiler does not recognize _Noreturn)

    .

提交回复
热议问题