2

Should I use fgets or formatted scanf like scanf("%10s", foo).

Excepted that scanf does not read blank characters, which can be solved and do more stuffs with scanset, then why I should use fgets instead of scanf?

Any help would be appreciated.


Edit

One more thing I want to ask is: even when we use fgets what happen if user enter characters more than boundary (I mean a lot of characters), does it lead to buffer overflow? Then how to deal with it?

Becker
  • 147
  • 7
  • 4
    `scanf` should pretty much never get used in professional code. `fgets` is both faster and safer. – Lundin Nov 23 '21 at 14:05
  • scanf reads precisely formatted input and does not care about line boundaries. Users are rarely precise and enter lines of text.. – stark Nov 23 '21 at 14:09
  • Overflow conditions, for both string buffers and numeric input is edited into my answer below. – ryyker Nov 23 '21 at 16:41

4 Answers4

5

On most operating sytems, user input is, by default, line-based. One reason for this is to allow the user to press the backspace key to correct the input, before sending the input to the program.

For line-based user input, it is meaningful and intuitive for a program to read one line of input at a time. This is what the function fgets does (provided that the buffer is large enough to store the entire line of input).

The function scanf, on the other hand, normally does not read one line of input at a time. For example, when you use the %s or %d conversion format specifier with scanf, it will not consume an entire line of input. Instead, it will only consume as much input as matches the conversion format specifier. This means that the newline character at the end of the line will normally not be consumed (which can easily lead to programming bugs). Also, scanf called with the %d conversion format specifier will consider input such as 6sldf23dsfh2 as valid input for the number 6, but any further calls to scanf with the same specifier will fail, unless you discard the remainder of the line from the input stream.

This behavior of scanf is counter-intuitive, whereas the behavior of fgets is intuitive, when dealing with line-based user input.

After using fgets, you can use the function sscanf on the string, for parsing the contents of an individual line. This will allow you to continue using scansets. Or you can parse the line by some other means. Either way, as long as you are using fgets instead of scanf for reading the input, you will be handling one line of input at a time, which is the natural and intuitive way to deal with line-based user input.

When we use fgets what happen if user enter characters more than boundary (I mean a lot of characters), does it lead to buffer overflow? Then how to deal with it?

If the user enters more characters than fit in the buffer as specified by the second fgets function argument, then it will not overflow the buffer. Instead, it will only extract as many characters from the input stream as fit in the buffer. You can determine whether the entire line was read by checking whether the string contains a newline character '\n' at the end.

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
2

This is a commonly discussed topic, full of opinion but interesting none the less. I have observed that a large majority of those that have already responded to similar questions on this site fall on the side of fgets(). I am one of them. I find fgets() to be much better to use for user input than scanf() with few exceptions. scanf() is considered by many as as sub-optimal method for handling user input. For example

"...it will tell you whether it succeeded or failed, but can tell you only approximately where it failed, and not at all how or why. You have very little opportunity to do any error recovery."
(jamesdlin). But in the interest of attempting balance, will start off citing this discussion.

For user input that comes from stdin, i.e. keyboard input, fgets() will be a better choice. It is much more forgiving in that the string it reads can be fully validated before conversion is attempted

One of the few times using a form of scanf(): fscanf() would be okay to use might be when converting input from a very controlled source, i.e. from reading a strictly formatted file with repeating predictable fields.

For more discussion, this comparison of the two highlights additional advantages and disadvantages of both.

Edit: to address OP additional question about overflow:

"One more thing I want to ask is: even when we use fgets what happen if user enter characters more than boundary (I mean a lot of characters), does it lead to buffer overflow? Then how to deal with it?"

[fgets()](https://www.tutorialspoint.com/c_standard_library/c_function_fgets.htm is nicely designed to prevent buffer overflow, simply by using its parameters properly, eg:

char buffer[100] = {0};
...
while fgets(buffer, sizeof buffer, stdin);  

This prevents input greater than the buffer size from being processed, thus preventing the overflow.

even using scanf()preventing buffer overflow is pretty straight forward: Use a width specifier in the format string. If you want to read input for example and limit input size from user to 100 characters max, the code would include the following:

char buffer[101] = {0};// includes space for 100 + 1 for NULL termination

scanf("%100s", buffer);
        ^^^  width specifier 

However with numbers, overflow is not so nice using scanf(). To demonstrate, use this simple code, inputting the two values indicated in comment one per run:

int main(void)
{
    int val = 0;
    // test with 2147483647 & 2147483648
    scanf("%d", &val);
    printf("%d\n", val);
    
    return 0;
}

For the second value, my system throws the following:

NON-FATAL RUN-TIME ERROR: "test.c", line 11, col 5, thread id 22832: Function scanf: (errno == 34 [0x22]). Range error `

Here you need to read in a string, then follow with a string to number conversion using one of the strto_() functions: strtol(), strtod(), ...). Both include the ability to test for overflow before causing a run-time warning or error. Note that using atoi(), atod() will not protect from overflow either.

ryyker
  • 22,849
  • 3
  • 43
  • 87
  • Thank you, I really appreciate your answer. – Becker Nov 23 '21 at 14:51
  • A question "full of opinion"? I must respectfully disagree. It's not a matter of opinion that `scanf` is almost completely useless, good at best for reading single, simple inputs in intro-to-C programs, but prohibitively difficult to do anything remotely sophisticated with — these are obvious facts! :-) – Steve Summit Nov 23 '21 at 16:12
1

If you have for example a character array declared like

char s[100];

and want to read a string that contains embedded spaces then you can use either scanf the following way:

scanf( "%99[^\n]", s );

or fgets like:

fgets( s, sizeof( s ), stdin );

The difference between these two calls is that the call of scanf does not read the new line character '\n' from the input buffer. While fgets reads the new line character '\n' if there is enough space in the character array.

To remove the new line character '\n' that is stored in the character array after using fgets you can write for example:

s[ strcspn( s, "\n" ) ] = '\0';

If the input string has more than 99 characters then the both calls read only 99 characters and append the sequence of characters with the terminating zero character '\0'. All remaining characters will be still in the input buffer.

There is a problem with fgets. For example if before fgets there is used scanf as for example:

scanf( "%d", &x );
fgets( s, sizeof( s ), stdin );

and the user input is:

10
Hello World

then the call of fgets will read only the new line character '\n' that is stored in the buffer after pressing the Enter key when the integer value in the call of scanf was read.

In this case you need to write a code that will remove the new line character '\n' before calling fgets.

You can do this for example the following way:

scanf( "%d", &x );
scanf( " " );
fgets( s, sizeof( s ), stdin );

If you are using scanf then in such a situation you can write:

scanf( "%d", &x );
scanf( " %99[^\n]", s );
       ^^ 
Galbatrollix
  • 58
  • 1
  • 4
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
1

So far, all answers here presented intricacies of scanf and fgets, but what I believe is worth mentioning, is that both of those functions are deprecated in current C standard. Scanf is especially dangerous, because it has all sorts of security issues with buffer overflows. fgets is not as problematic, but from my experience, it tends to be a bit clunky and not-so-useful in practice.

The truth is, often you don't really know how long the user input will be. You can get around this by using fgets with I hope this will big enough buffer, but that's not really ellegant. Instead, what you often want to do is to have dynamic buffer that will grow to be big enough to store whatever user input will deliver. And this is when getline function comes into play. It's used to read any number of characters from the user, until \n is encountered. Essentially, it loads the entire line to your memory, as a string.

size_t getline(char **lineptr, size_t *n, FILE *stream);

This function takes a pointer to a dynamically allocated string as first argument, and a pointer to a size of the allocated buffer as a second argument and stream as a third argument. (you will essentially place stdin there for command-line input). And returns number of read characters, including \n at the end, but not the terminating null.

Here, you can see example usage of this function:

int main() {

printf("Input Something:\n");  // asking user for input

size_t length = 10;                   // creating "base" size of our buffer
char *input_string = malloc(length);  // allocating memory based on our initial buffer size
size_t length_read = getline(&input_string, &length, stdin);  // loading line from console to input_string
// now length_read contains how much characters we read
// and length contains new size of our buffer (if it changed during the getline execution)

printf("Characters read (including end of line but not null at the end)"
       ": %lu, current size of allocated buffer: %lu string: %s"
       , length_read, length, input_string);

free(input_string);    // like any other dynamically-allocated pointer, you must free it after usage
return 0;
}

Of course, using this function requires basic knowledge about pointers and dynamic memory in C, however the slightly more complicated nature of getline is definitely worth it, because of the provided security and flexibility.

You can read more about this function, and other input functions available in C, on this website: https://www.studymite.com/blog/strings-in-c I believe it summarizes the intricacies of C input pretty well.

Galbatrollix
  • 58
  • 1
  • 4