10

This is the description of fgets() from the man page:

char *fgets(char *s, int size, FILE *stream);
...

RETURN VALUE
  fgets() returns s on success, and NULL on error or  when  end  of  file
  occurs while no characters have been read.

It doesn't follow the pattern of read, which returns -1 on failure and the number of bytes read on success. Instead, it returns a char* which is NULL on failure and s on success. This doesn't give me any info about how long the input is. So if I have something like this:

char input_buffer[256];
fgets(input_buffer, sizeof(input_buffer), stdin);

After the fgets call, is there any way to tell how long the input is WITHOUT zero-initializing the buffer first?

Thanks.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
James Ko
  • 32,215
  • 30
  • 128
  • 239
  • 2
    Possible duplicate of [Return value of fgets()](https://stackoverflow.com/questions/21679063/return-value-of-fgets) – Dumbo Dec 14 '17 at 15:53
  • 2
    `int len= strlen(input_buffer);` will give you that. – Paul Ogilvie Dec 14 '17 at 15:53
  • That why I don't like the design of `fgets()`, I prefer `fread()` but it doesn't handle locale. – Stargateur Dec 14 '17 at 18:48
  • 2
    Good to know `strlen` will do the trick, but is there any solution that doesn't involve O(n) iteration? Seems wasteful that you have to figure it out again when `fgets` already knows what the length is. – James Ko Dec 15 '17 at 01:34
  • @Stargateur, `fread()` does not NUL terminate the input, but `fgets()` does. So `strlen()` works for `fgets()` but not for `fread()`. Each function has its' purpose and capabilities. the function `fgets()` is written so it can be 'chained' The function: `fread()` is not. the function: `fgets()` is for text input. the function: `fread()` is for binary input. – user3629249 Dec 15 '17 at 22:08
  • @user3629249 Thanks captain obvious. Still, I don't like `int` type of size argument of `fgets()`, I don't like to need to use `strlen()` to know the size of my string. `getline()` is so mush better but posix, `fread()` don't add nul byte but you can do it easilly. Like I say `fread()` isn't equal to `fgets()` locale, nul byte etc... but it has a better design. – Stargateur Dec 15 '17 at 23:57

2 Answers2

10

How to determine number of characters that were read with fgets()?

char *fgets(char *s, int size, FILE *stream);

Use strlen(s) after checking the fgets() return value.

if (fgets(s, size, stream)) {
  printf("number of characters that were read: %zu\n", strlen(s));
} else if (feof(stream)) {
  printf("number of characters that were read:0 End-of-file\n");
} else  {
  printf("number of characters that were read unknown due to input error\n");
}

This works unless a null character '\0' is read as strlen() will encounter that '\0' before the appended one by the function. In that case, strlen(s) after fgets() will report a smaller value.

There are various tricks to pre-fill s and then call fgets(), yet it is undefined what happens to the rest of the unread buffer. Other short comings exist.

If null characters as part of a valid input stream are a concern, use fgetc() or something like getline().


A common scenario where null characters are text is when text is encoded as UTF-16. Of course code should not use fgets() to read that text, yet that requires prior knowledge. Much code that reads text has failed in mysterious ways due to the incorrect assumption that a text file is a non-null character text file.

Further, even with a text file supposedly lacking a null characters, what happens with the following code?

if (fgets(s, size, stream)) {
  size_t len = strlen(s);
  s[--len] = '\0';  // poor way to lop off the trailing \n, this could be UB
}

Such code invokes undefined behavior with a hacker exploit: slipping a null character in the file at the beginning of the line. (See this and this for better solutions to lop off the potential \n)

Robust code does not assume the text is well formed and takes measures to detect abnormalities.


Pedantic note: there are pathological problems with fgets(char *s, int size, FILE *stream); with a size < 2.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
4

Yes there is. It is always null terminated in case of success. So it will be strlen(buf).

From standard 7.21.7.2

char *fgets(char * restrict s, int n,FILE * restrict stream); 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. A null character is written immediately after the last character read into the array.

user2736738
  • 30,591
  • 5
  • 42
  • 56
  • 2
    Provided, of course, that the input does not itself include a null character, which would break this scheme. But that is a common limitation. – John Bollinger Dec 14 '17 at 16:00
  • @JohnBollinger.: Well yes that's a case but to avoid that we can also do another check (which is again not fully solid) that is to check for `\n`? – user2736738 Dec 14 '17 at 16:00
  • 1
    Yes, checking for a newline does the job, provided that the buffer supplied to `fgets()` is large enough to accommodate the whole line, including the newline. In many cases it is safer to assume or require that the input not contain nulls than to assume or require that it not contain long lines. It is relevant to note here that `fgets()` is most appropriate for *text*, so that the proviso that the input must not contain nulls is usually a pretty low bar. – John Bollinger Dec 14 '17 at 16:06
  • 2
    @JohnBollinger if your text is expected to contain null terminators, than `fgets` probably should not be your first choice of reading function. – SergeyA Dec 14 '17 at 16:09
  • @JohnBollinger.: So suppose, if the input contains nulls and the line is not big enough to hold `\n` then I guess none of these would work. (which is very unlikely). – user2736738 Dec 14 '17 at 16:18
  • Good to know `strlen` will do the trick, but is there any solution that doesn't involve O(n) iteration? Seems wasteful that you have to figure it out again when `fgets` already knows what the length is. – James Ko Dec 15 '17 at 01:35
  • @JamesKo.: With `fgets` this is the best you can get. If you need anything more than this I guess you will have to use some other function etc – user2736738 Dec 15 '17 at 03:08