sgrontflix sgrontflix - 1 month ago 19
C Question

fgets() does not return NULL on empty string

I recently tried to use

fgets()
instead of
scanf()
to read a string for code security reasons. I used a simple function that I found here to check for errors (no input and too long input). The problem is that whenever i press "ENTER" without actually writing anything,
fgets()
doesn't return
NULL
and my program is not able to show the
NO_INPUT
error.

Here's
main.cpp
:

#include <stdlib.h>
#include <stdio.h>

#include "utilities.h"

int main() {
int rc;
char str[20];

rc = getLine("Enter string: ", str, sizeof(str));
if(rc == NO_INPUT) {
printf("[ERROR] No input\n\n");
} else if(rc == TOO_LONG) {
printf("[ERROR] Input is too long\n\n");
} else {
printf("This is your input: \"%s\"\n\n", str);
}

system("pause");
}


Here's
utilities.h
:

#include <stdio.h>
#include <string.h>

#define OK 0
#define NO_INPUT 1
#define TOO_LONG 2

int getLine(const char *msg, char *buff, size_t len) {
if(msg != NULL) {
printf("%s", msg);
fflush(stdout);
}

if(fgets(buff, len, stdin) == NULL /*[+]*/ || strcmp(buff, "\n") == 0) {
//[-] fflush(stdin);
return NO_INPUT;
} else if(buff[strlen(buff)-1] != '\n') {
//[-] fflush(stdin);
return TOO_LONG;
} else {
buff[strlen(buff)-1] = '\0';
//[-] fflush(stdin);
return OK;
}
}


And here's the output:

Enter string:
This is your input: ""

Press any key to continue . . .





I solved my problem by replacing the first
if
statement in
utilities.h
with
if(fgets(buff, len, stdin) == NULL || strcmp(buff, "\n") == 0)
. Doing this, my program will check for input errors OR for empty strings and return the
NO_INPUT
flag.




Thanks to everybody who commented and @Peter for answering. I added the aforementioned
if
statement in
utilities.h
and removed every
fflush(stdin)
occurrence. The code now looks as above.

Answer

Your problem that you "fixed" is believing that a end of line should be treated as end of input.

NULL is an indication from fgets() that it encountered an error or the end of input when reading from the file (or stream). A blank line is neither an error nor a marker of end of input. A human (typing on a keyboard) might choose to interpret a newline as end of input, but a computer program does not - after all, there is nothing stopping a user entering more than one line of input.

Practically, fgets() reads a line and indicates the end of that line with a '\n' character. Let's say, we have a file containing

ABC

DE

FGHIJ

(blank lines interspersed in three lines of text, followed by end of the file).

Let's also that buffer is an array of five char, and that we read that file using consecutive statements of the form fgets(buffer, 5, file).

So what will fgets(buffer, 5, file) do on each call. Well, with 1 representing the first call, 2 representing the second call, etc we will see results of

  1. "ABC\n" stored into buffer;
  2. "\n" stored into buffer; (first blank line)
  3. "DE\n" stored into buffer;
  4. "\n" stored into buffer; (second blank line)
  5. "FGHI" stored into buffer;
  6. "J\n" stored into buffer; and
  7. fgets() returns NULL, and nothing is stored into buffer.

The first six calls will all return &buffer[0] - not NULL - since no error is encountered reading from the file. Even though there are two blank lines in the input. The last line, which is longer than the buffer (with the '\n' counted) is read in two parts.

Incidentally, your code is using fflush(stdin). Unfortunately, fflush() only has defined behaviour on OUTPUT streams or files. Using it on stdin (or any input stream) gives undefined behaviour. If it is actually discarding input (which it does with some implementations of the C standard library), you are getting lucky - there are real-world compilers where the resultant behaviour does not discard input.

Comments