Home > Net >  fgets implementation from the std library of K&R 2nd edition
fgets implementation from the std library of K&R 2nd edition

Time:04-07

I'm aware that code snippets from textbooks are just for demonstration purposes and shouldn't be held to production standards but K&R 2nd edition on page 164-165 says:

fgets and fputs, here they are, copied from the standard library on our system:

char *fgets(char *s, int n, FILE *iop)
{
    register int c;
    register char *cs;
    
    cs = s;
    while (--n > 0 && (c = getc(iop)) != EOF)
        if ((*cs   = c) == '\n')
            break;
    *cs = '\0';
    return (c == EOF && cs == s) ? NULL : s;
}

Why is the return statement not return (ferror(iop) || (c == EOF && cs == s)) ? NULL : s; since:

  1. ANSI C89 standard says:

If a read error occurs during the operation, the array contents are indeterminate and a null pointer is returned.

  1. Even the standard library illustrated in Appendix B of the book says so. From Page 247:

fgets returns s, or NULL if end of file or error occurs.

  1. K&R uses ferror(iop) in fputs implementation given just below this fgets implementation on the same page.

With the above implementation, fgets will return s even if there is a read error after reading some characters. Maybe this is an oversight or am I missing something?

CodePudding user response:

You are correct that the behavior of the posted implementation of the function fgets does not comply with the C89 standard. For the same reasons, it also does not comply with the modern C11/C18 standard.

The posted implementation of fgets handles end-of-file correctly, by only returning NULL if not a single character has been read. However, it does not handle a stream error correctly. According to your quote of the C89 standard (which is identical to the C11/C18 standard in this respect), the function should always return NULL if an error occurred, regardless of the number of characters read. Therefore, the posted implementation of fgets is wrong to handle an end-of-file in exactly the same way as a stream error.

It is worth noting that the second edition of K&R is from 1988, which is from before the ANSI C89 standard was published. Therefore, the exact wording of the standard may not have been finalized yet, when the second edition of the book was written.

CodePudding user response:

Why is the return statement not return (ferror(iop) || (c == EOF && cs == s)) ? NULL : s;

Because this is not the best option either.

return (c == EOF && cs == s) ? NULL : s; well handles the case of EOF due to end-of-file, but not EOF due to input error.

ferror(iop) is true when an input error just occurred or when an input error had occurred before. It is possible that fgetc() can return non-EOF after returning EOF due to input error. This differs from feof(), which is sticky. Once EOF occurs due to end-of-file, feof() continues to return EOF, unless the end-of-file flag is cleared.

A better alternative would be to insure an EOF just occurred rather than use an unqualified ferror().

 if (c == EOF) {
   if (feof(iop)) {
     if (cs == s) return NULL;
   } else {
     return NULL; // Input error just occurred.
   }
 }
 return s;

Pedantic n

Pathological cases: The below suffers when n <= 0 as *cs = '\0' writes into cs[] outside its legal range. --n is a problem when n == INT_MIN.

while (--n > 0 && (c = getc(iop)) != EOF) // Is --n OK?
  if ((*cs   = c) == '\n')
    break;
*cs = '\0';  // Is n big enough

// Alternative
while (n > 1 && (c = getc(iop)) != EOF) {
  n--;
  *cs   = c;
  if (c == '\n') {
    break;
  }
}   
if (n > 0) *cs = '\0';

Pedantic CHAR_MAX >= INT_MAX

To note: On rare machines (some old graphics processors), returning an EOF may be a valid CHAR_MAX. Alternative code not presented.

Uninitialized c

Testing c == EOF is UB as it is not certain c was ever set with a small n.

Better to initialize C: register int c = 0;


More:
What are all the reasons fgetc() might return EOF?.
Is fgets() returning NULL with a short buffer compliant?.

  • Related