Search code examples
cwhile-loopcharnewlinefgets

fgets doesn't read in at most one less than size characters from stream


I am learning fgets from the manpage. I did some tests on fgets to make sure I understand it. One of the tests I did results in behaviour contrary to what is specified in the man page. The man page says:

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

fgets() reads in at most one less than size characters from stream and stores them into the buffer pointed to by s. Reading stops after an EOF or a newline. If a newline is read, it is stored into the buffer. A terminating null byte ('\0') is stored after the last character in the buffer.

But it doesn't "read in at most one less than size characters from stream". As demonstrated by the following program:

#include<stdio.h>
#include<stdlib.h>
int main(){
    FILE *fp;
    fp=fopen("sample", "r");
    char *s=calloc(50, sizeof(char));
    while(fgets(s,2,fp)!=NULL)  printf("%s",s);
}

The sample file:

thiis is line no. 1
joke joke 2 joke joke
arch linux btw 3
4th line
5th line

The output of the compiled binary:

thiis is line no. 1
joke joke 2 joke joke
arch linux btw 3
4th line
5th line

The expected output according to the man page:

t
j
a
4
5

Is the man page wrong, or am I missing something?


Solution

  • Is the man page wrong or am i missing something?

    I won't say that the man page is wrong but it could be more clear.

    There are 3 things that may stop fgets from reading from the stream.

    1. The buffer is full (i.e. only room left for the termination character)

    2. A newline character was read from the stream

    3. End-Of-File occured

    The quoted man page only mentions two of those conditions clearly.

    Reading stops after an EOF or a newline.

    That is #2 and #3 are mentioned very explicit while #1 is (kind of) derived from

    reads in at most one less than size characters from stream

    Here is another description from https://man7.org/linux/man-pages/man3/fgets.3p.html

    ... read bytes from stream into the array pointed to by s until n-1 bytes are read, or a newline is read and transferred to s, or an end-of-file condition is encountered.

    where the 3 cases are clearly mentioned.

    But yes... you are missing something. Once the buffer gets full, the rest of the current line is not read and discarded. The rest will stay in the stream and be available for the next read. So nothing is lost. You just need more fgets calls to read all data.

    As suggested in a number of comments (e.g. Fe2O3 and Lundin) you can see this if you change the print statement so that it includes a delimiter of some kind. For instance (from Lundin):

    printf("|%s|",s);
    

    This will make clear exactly what you got from the individual fgets calls.