3
votes

May seem like a silly question for most of you, but I'm still trying to determine the final answer. Some hours ago I decided to replace all the scanf() functions in my project with the fgets() in order to get a more robust code. I learned that the fgets() automatically ends the inserted input string with the '\n' and the NUL characters but.. let's say I have something like this:

char user[16];

An array of 16 char which stores a username (15 characters max, I reserve the last one for the NUL terminator). The question is: if I insert a 15 characters strings, then the '\n' would end up in the last cell of the array, but what about the NUL terminator? does the '\0' get stored in the following block of memory? (no segmentation fault when calling the printf() function implies that the inserted string is actually NUL terminated, right?).

4
The \n will be left in the stream in that case. It'll be there for your next read.Fred Larson
Your buffer should be declared as char user[16];.5gon12eder
uh, sorry guys! my bad! :)Atlas80b
To address your last question: No, passing a character array without 0-bytes to printf is not guaranteed to segfault.mafso

4 Answers

7
votes

As a complement to 5gon12eder answer. I assume you have something like :

char user[16];

fgets(user, 16, stdin);

and your input is abcdefghijklmno\n , that is 15 characters and a newline.

fgets will put in user the 15 (16-1) first characters of the input followed by a null and you will effectively get "abcdefghijklmno", which is what you want

But ... the \n still remains in stream buffer an is actually available for next read (be it a fgets or anything else) on same FILE. More exactly, until you do another fgets you cannot know whether there was other characters following the o.

5
votes

As @5gon12eder suggests, use:

char user[16];
fgets(user, sizeof user, stdin);

// Function prototype for reference
#include <stdio.h>
char *fgets(char * restrict s, int n, FILE * restrict stream);

Now for details:

  1. The '\n' and the '\0' are not automatically appended. Only the '\0' is automatically appended. fgets() will stop reading once it gets a '\n', but will stop for other reasons too including a full buffer. In those cases, there is no '\n' before the '\0'.

  2. fgets() does not read a C string, but reads a line. The input stream is typically in text mode and then end-of-line translations occur. On some systems, '\r', '\n' pair will translate to '\n'. On others, it will not. Usually the files being read match this translation, but exceptions occur. In binary mode, no translations occur.

  3. fgets() reads in '\0'. and continues reading. Thus using strlen(buf) does not always reflect the true number of char read. There may be a full-proof method to determine the true number of char read when '\0' are in the middle, but itis is likely easier to code with fread() or fgetc().

  4. On EOF condition (and no data read) or IO error, fgets() returns NULL. When an I/O error occurs, the contents of the buffer is not defined.

  5. Pedantic issue: The C standard uses a type of int as the size of the buffer but often code passes a variable of type size_t. A size n less than 1 or more than INT_MAX can be a problem. A size of 1 should do nothing more than fill the buf[0] = '\0', but some systems behave differently especially if the EOF condition is near or passed. But as long as 2 <= n <= INT_MAX, a terminating '\0' can be expected. Note: fgets() may return NULL when the size is too small.

  6. Code typically likes to delete the terminating '\n' with something that could cause trouble. Suggest:

    char buf[80];
    if (fgets(buf, sizeof buf, stdin) == NULL) Handle_IOError_or_EOF();
    
    // IMO potential UB and undesired behavior
    // buf[strlen(buf)-1] = '\0';
    
    // Suggested end-of-line deleter
    size_t len = strlen(buf);
    if (len > 0 && buf[len - 1] == '\n') buf[--len] = '\0';
    
  7. Robust code checks the return value from fgets(). The following approach has short-comings. 1) if an IO Error occurred the buffer contents are not defined. Checking the buffer contents will not provide reliable results . 2) A '\0' may have been the first char read and the file is not in the EOF condition.

    // Following is weak code.
    buf[0] = '\0';
    fgets(buf, sizeof buf, stdin);
    if (strlen(buf) == 0) Handle_EOF();
    
    // Robust, but too much for code snippets
    if (fgets(buf, sizeof buf, stdin) == NULL) {
      if (ferror(stdin)) Handle_IOError();
      else if (feof(stdin)) Handle_EOF();
      else if (sizeof buf <= 1) Handle_too_small_buffer();  // pedantic check
      else Hmmmmmmm();
    }
    
5
votes

Documentation of fgets from the C99 Standard (N1256)

7.19.7.2 The fgets function

Synopsis

#include <stdio.h>
char *fgets(char * restrict s, int n,
FILE * restrict stream);

Description

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.

Coming to your post, you said:

An array of 16 char which stores a username (15 characters max, I reserve the last one for the NUL terminator). The question is: if I insert a 15 characters strings, then the '\n' would end up in the last cell of the array, but what about the NUL terminator?

For such a case, the newline character is not read until the next call to fgets or any other call to read from the stream.

does the '\0' get stored in the following block of memory? (no segmentation fault when calling the printf() function implies that the inserted string is actually NUL terminated, right?).

The terminating null character is always set. In your case, the 16-th character will be the terminating null character.

2
votes

From the man page of fgets:

char *fgets(char *s, int size, FILE *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.

I think that is pretty clear, isn't it?