10

From The GNU C Programming Tutorial:

The fgets ("file get string") function is similar to the gets function. This function is deprecated -- that means it is obsolete and it is strongly suggested you do not use it -- because it is dangerous. It is dangerous because if the input data contains a null character, you can't tell. Don't use fgets unless you know the data cannot contain a null. Don't use it to read files edited by the user because, if the user inserts a null character, you should either handle it properly or print a clear error message. Always use getline or getdelim instead of fgets if you can.

I thought the fgets function stops when it encounters a \0 or \n; why does this manual page suggest a null byte is "dangerous" when fgets should handle the input properly? Furthermore, what is the difference between getline and fgets, and is the fgets function truly considered deprecated in the C99 or future C standards?

Vilhelm Gray
  • 11,516
  • 10
  • 61
  • 114
  • 1
    `fgets()` does not stop on encountering a null byte; it only stops when it runs out of space, when it encounters a newline, or when it reaches EOF. – Jonathan Leffler Jul 29 '22 at 18:08

2 Answers2

17

No, fgets is not actually deprecated in C99 or the current standard, C11. But the author of that tutorial is right that fgets will not stop when it encounters a NUL, and has no mechanism for reporting its reading of such a character.

The fgets function reads at most one less than the number of characters specified by n from the stream pointed to by stream into the array pointed to by s. No additional characters are read after a new-line character (which is retained) or after end-of-file.

(§7.21.7.2)

GNU's getdelim and getline have been standardized in POSIX 2008, so if you're targeting a POSIX platform, then it might not be a bad idea to use those instead.

EDIT I thought there was absolutely no safe way to use fgets in the face of NUL characters, but R.. (see comments) pointed out there is:

char buf[256];

memset(buf, '\n', sizeof(buf));  // fgets will never write a newline
fgets(buf, sizeof(buf), fp);

Now look for the last non-\n character in buf. I wouldn't actually recommend this kludge, though.

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
  • So `fgets` keeps reading past null bytes, searching just for the newline character? – Vilhelm Gray May 01 '13 at 17:36
  • 2
    @VilhelmGray: that's right, and it won't tell you it did. There's no way to be sure that the first '\0' you find was added by `fgets` or not. – Fred Foo May 01 '13 at 17:42
  • 8
    null bytes do not belong in **text** files. `fgets()` was designed to work with text files: using `fgets()` with files of binary data is not recommended. – pmg May 01 '13 at 17:45
  • @pmg: they might wind up in text files by accident, and it would be nice if `stdio` were more robust against such errors (it should at least report them, but it doesn't). – Fred Foo May 01 '13 at 17:53
  • I wonder why `fgets` wasn't designed to return the number of bytes read. – Vilhelm Gray May 01 '13 at 17:56
  • 3
    @VilhelmGray: `stdio` tends to trust the user too much -- it just doesn't anticipate long lines, null characters in text files, etc. It's from the 1970s, when defensive programming wasn't deemed as important as it is now (no internet, no script kiddies trying to break into your system, no noob users that will spam you when they screw up a text file). – Fred Foo May 01 '13 at 18:02
  • @larsman: Actually you can know if you pre-fill the buffer right. – R.. GitHub STOP HELPING ICE May 01 '13 at 18:18
  • @R..: how? By filling it with `\n` then searching for the last non-newline? – Fred Foo May 01 '13 at 19:24
  • Yep, if you pre-fill the buffer with `'\n'` then you can search for the first newline; if it's followed by a 0 byte, that newline was the last read byte. Otherwise, the byte before that newline was the last read byte. – R.. GitHub STOP HELPING ICE May 01 '13 at 19:54
  • @R..: I'd rather reimplement `fgets` with a saner interface than do that, but I'll edit my answer. – Fred Foo May 01 '13 at 19:55
  • The only way to reimplement `fgets` is by repeatedly calling `getc`, which will be many times slower than the trick I just described. – R.. GitHub STOP HELPING ICE May 01 '13 at 19:55
  • 1
    @R..: probably, but when I/O is not the bottleneck I prefer my code clean. – Fred Foo May 01 '13 at 20:00
  • Yes I would not do that inline in other code but as a function it's reasonable. – R.. GitHub STOP HELPING ICE May 01 '13 at 21:29
  • 4
    `// fgets will never write a newline` comment is unclear. Certainly a `'\n'` is written to the buffer on many `fgets()` calls. Did you mean `// fgets will never read after a newline`? – chux - Reinstate Monica Jan 04 '17 at 21:46
  • `memset(buf, '\n', sizeof(buf)); fgets(buf, sizeof(buf), fp);` **is** good except for a) The C spec is weak/inadequate on the stability of contents after the appended null character, so searching from the end is on thin ice. Searching from the beginning past a null character is of similar concern. b) When an input error occurs - buffer is explicitly undefined - of course, no need to read the buffer then. – chux - Reinstate Monica Jan 04 '17 at 21:59
8

This is just GNU propaganda. In no official sense is fgets deprecated. gets however is dangerous and deprecated.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711