2

Suppose I want to run the following C snippet:

scanf("%d" , &some_variable);
printf("something something\n\n");

printf("Press [enter] to continue...")
getchar(); //placed to give the user some time to read the something something

This snippet will not pause! The problem is that the scanf will leave the "enter" (\n)character in the input stream1, messing up all that comes after it; in this context the getchar() will eat the \n and not wait for an actual new character.

Since I was told not to use fflush(stdin) (I don't really get why tho) the best solution I have been able to come up with is simply to redefine the scan function at the start of my code:


void nsis(int *pointer){ //nsis arconim of: no shenanigans integer scanf
     scanf("%d" , pointer);
     getchar(); //this will clean the inputstream every time the scan function is called
}

And then we simply use nsis in place of scanf. This should fly. However it seems like a really homebrew, put-together-with-duct-tape, solution. How do professional C developers handle this mess? Do they not use scanf at all? Do they simply accept to work with a dirty input stream? What is the standard here?

I wasn't able to find a definite answer on this anywhere! Every source I could find mentioned a different (and sketchy) solution...


EDIT: In response to all commenting some version of "just don't use scanf": ok, I can do that, but what is the purpose of scanf then? Is it simply an useless broken function that should never be used? Why is it in the libraries to begin with then?

This seems really absurd, especially considering all beginners are taught to use scanf...


[1]: The \n left behind is the one that the user typed when inputting the value of the variable some_variable, and not the one present into the printf.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Noumeno
  • 115
  • 6
  • 2
    `scanf` is function that most people don't really understand what it does and use it in a wrong way, and that's when you get these kind of questions. I suggest that you 1. first the whole line with `fgets` and then parse it with `sscanf`. Then you don't have have to deal with "leftovers". `fflush(stdin)` is not defined in the standard, as `fflush` is only defined for output buffers, `stdin` is an input buffer – Pablo Jan 07 '22 at 11:40
  • @Pablo But if I should not use `scanf` to get user inputs then when should I use it?? Is it simply a useless broken function then??? Can you expand on this in an answer? I feel most people will benefit from an explanation, and based on your "most people don't really understand" I feel you think the same also – Noumeno Jan 07 '22 at 11:46
  • 2
    "I was told not to use `fflush(stdin)`". For one thing the C standard says it is *undefined behaviour*. And in practice, what do you think happens when you redirect a file as the input for `stdin` when the code flushes the input stream? It would make it impossible. – Weather Vane Jan 07 '22 at 11:53
  • 1
    @Noumeno: You may want to read these two links: 1. [A beginners' guide away from scanf()](http://sekrit.de/webdocs/c/beginners-guide-away-from-scanf.html) 2. [Disadvantages of scanf](https://stackoverflow.com/questions/2430303/disadvantages-of-scanf) – Andreas Wenzel Jan 07 '22 at 11:54
  • 1
    Please see [What can I use for input conversion instead of scanf?](https://stackoverflow.com/questions/58403537/what-can-i-use-for-input-conversion-instead-of-scanf) – Weather Vane Jan 07 '22 at 11:56
  • `"How do professional C developers handle this mess?"` -- In the second code snippet of [this answer of mine to another question](https://stackoverflow.com/a/69636446/12149471), I provided a function `get_int_from_user`. That is the function I use for obtaining an integer from the user. That function uses `fgets` and `strtol` instead of `scanf`, and it automatically reprompts the user if the input is invalid. – Andreas Wenzel Jan 07 '22 at 12:04
  • @Noumeno I'm not going to write an answer that has been written already a lot of times. Please take a look at the posted links from other users (like Andreas's or Weather Vane's), they explain the "problem" very well. – Pablo Jan 07 '22 at 12:05
  • 1
    Note that you should be checking the return value from `scanf()`, especially in your `nsis()` function – Jonathan Leffler Jan 07 '22 at 12:57

2 Answers2

5

but what is the purpose of scanf then?

An excellent question.

Is it simply a useless broken function that should never be used?

It is almost useless. It is, arguably, quite broken. It should almost never be used.

Why is it in the libraries to begin with then?

My personal belief is that it was an experiment. It tries to be the opposite of printf. But that turned out not to be such a good idea in practice, and the function never got used very much, and pretty much fell out of favor, except for one particular use case...

This seems really absurd, especially considering all beginners are taught to use scanf...

You're absolutely right. It is really quite absurd.

There's a decent reason why all beginners are taught to use scanf, though. During week 1 of your first C programming class, you might write the little program

#include <stdio.h>

int main()
{
    int size = 5;
    for(int i = 0; i < size; i++) {
        for(int j = 0; j < size; j++)
            putchar('*');
        putchar('\n');
    }
}

to print a square. And during that first week, to make a square of a different size, you just edit the line int size = 5; and recompile.

But pretty soon — say, during week 2 — you want a way for the user to enter the size of the square, without having to recompile. You're probably not ready to muck around with argv. You're probably not ready to read a line of text using fgets and convert it back to an integer using atoi. (You're probably not even ready to seriously contemplate the vast differences between the integer 5 and the string "5" at all.) So — during week 2 of your first C programming class — scanf seems like just the ticket.

That's the "one particular use case" I was talking about. And if you only used scanf to read small integers into simple C programs during the second week of your first C programming class, things wouldn't be so bad. (You'd still have problems forgetting the &, but that would be more or less manageable.)

The problem (though this is again my personal belief) is that it doesn't stop there. Virtually every instructor of beginning C classes teaches students to use scanf. Unfortunately, few or none of those instructors ever explicitly tell students that scanf is a stopgap, to be used temporarily during that second week, and to be emphatically graduated beyond in later weeks. And, even worse, many instructors go on to assign more advanced problems, involving scanf, for which it is absolutely not a good solution, such as trying to do robust or "user friendly" input validation.

scanf's only virtue is that it seems like a nice, simple way to get small integers and other simple input from the user into your early programs. But the problem — actually a big, shuddering pile of 17 separate problems — is that scanf turns out to be vastly complicated and full of exceptions and hard to use, precisely the opposite of what you'd want in order to make things easy for beginners. scanf is only useful for beginners, and it's almost perfectly useless for beginners. It has been described as being like square training wheels on a child's bicycle.

How do professional C developers handle this mess?

Quite simply: by not using scanf at all. For one thing, very few production C programs print prompts to a line-based screen and ask users to type something followed by Return. And for those programs that do work that way, professional C developers unhesitatingly use fgets or the like to read a full line of input as text, then use other techniques to break down the line to extract the necessary information.


In answer to your initial question, there's no good answer. One of the fundamental rules of scanf usage (a set of rules, by the way, that no instructor ever teaches) is that you should never try to mix scanf and getchar (or fgets) in the same program. If there were a good way to make your "Press [enter] to continue..." code work after having called scanf, we wouldn't need that rule.

If you do want to try to flush the extra newline, so that a later call to getchar might work, there are several questions here with a bunch of good answers:


There's one more unrelated point that ends up being pretty significant to your question. When C was invented, there was no such thing as a GUI with multiple windows. Therefore no C programmer ever had the problem of having their output disappear before they could read it. Therefore no C programmer ever felt the need to write printf("Press [enter] to continue..."); followed by getchar(). I believe (another personal belief) that it is egregiously bad behavior for any vendor of a GUI-based C compiler to rig things up so that the output disappears upon program exit. Persistent output windows ought to be the default, for the benefit of beginning C programmers, with some kind of non-default option to turn that behavior off for those who don't want it.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • 1
    It was quite common at the time C was written to read data in fixed column text files (card image). scanf works quite well for those. – stark Jan 07 '22 at 12:40
  • The [CS50](https://en.wikipedia.org/wiki/CS50) course is one notable exception. That course does not use `scanf`, but instead provides [its own functions for obtaining user input](https://cs50.readthedocs.io/libraries/cs50/c/#usage). The course probably does that because the C standard library does not provide any simple input functions of its own. – Andreas Wenzel Jan 07 '22 at 12:43
  • 2
    *no C programmer ever had the problem of having their output disappear before they could read it* As a matter of fact, in the earliest days of C, there weren't even video terminals, and programmers were using teletypes that printed on paper, so you could still read your output even after a power failure! – Steve Summit Jan 07 '22 at 12:54
  • 1
    @AndreasWenzel CS50 is faulted for many things, but I don't blame them for trying to introduce those input functions, because it's a glaring need. (And another exception, perhaps not so notable, is the [introductory C course](https://www.eskimo.com/~scs/cclass/cclass.html) written by yours truly.) – Steve Summit Jan 07 '22 at 12:57
  • @stark Although it's true that card images were still in widespread use at that time, the people who were inventing C and Unix hated them, and were doing everything they could to design a system free of such inflexible and constraining formats. – Steve Summit Jan 07 '22 at 13:03
  • @stark But you're right. That was a good, early use case, that certainly accounted for `scanf` ever getting any traction at all. – Steve Summit Jan 07 '22 at 13:13
  • 1
    That they wanted to avoid *dependence* on such formats was forward-looking, but they still needed to be able to *handle* them. `scanf()` and `printf()` seem to be designed with this in mind. Their differences from, say, Fortran formatted `read` and `write` statements (which predate C and have fewer issues, but are less flexible) may be indicative of an attempt to compromise. If so then that compromise was not altogether successful. – John Bollinger Jan 07 '22 at 13:14
  • I agree with almost everything you wrote but I wouldn't say that `scanf` is useless. The problem is that people tend to use it for a different purpose (as it was intended) and are not aware of the side effects as they are not obvious, hence there is this perception of `scanf` being useless. – Pablo Jan 07 '22 at 13:27
  • Your answer was **really** helpful, but there is one final point that still bugs me: I understood I should use **multiple functions and instructions** to take user inputs properly (`fgets`, `atoi` or `sscanf`, ecc.). Ok, but C has been around for decades! Why noone developed an *all in one* function to do this? Why isn't there something already in the standard libraries to take user inputs cleanly and safely? Why do I have to build it myself? Seems like something people should use all the time, everyone ok with rewriting all the code from scratch just to take user inputs? – Noumeno Jan 07 '22 at 14:26
  • 2
    @Noumeno Another good question. And, of course, plenty of people *have* written various "all in one" functions to do this, so your question really is, why hasn't such a function been added to the official C standards? I think the answer there is in two parts: (1) the C standards have been very conservative, really not adding much that wasn't there from the beginning, and (2) there's not, sorry to say, really a compelling need, because simple input functions are really only needed by beginning programmers who are learning, and C has never been intended as a teaching language. – Steve Summit Jan 07 '22 at 14:50
  • 1
    @Noumeno I have been toying for a while with an idea of proposing an Annex for consideration in the next version of the C standard. It would be a set of additional requirements solely to make C easier to learn, and a proper input function would certainly be a big part of it. I'm not at all sure I'll ever manage to finish this and turn it into a formal proposal, and even if I do I expect it will have approximately zero chance of ever getting approved, but there we are. – Steve Summit Jan 07 '22 at 14:51
3

Is scanf broken? No it is not. It is an excellent input function when you want to parse free form input data where few errors are to be expected. Free form means here that new lines are not relevant exactly as when you read/write very long paragraphs on a normal screen. And few errors expected is common when you read from files.

The scanf family function has another nice point: you have the same syntax when reading from the standard input stream, a file stream or a character string. It can easily parse simple common types and provide a minimal return value to allow cautious programmers to know whether all or part of all the expected data could be decoded.

That being said, it has major drawbacks: first being a C function, it cannot directly control whether the programmer has passed types meeting the format specifications, and second, as beginners are not consistenly hit on their head when they forget to control its return value, it is really too easy to make fully broken programs using it.

But the rule is:

  • if input is expected to be line oriented, first use fgets to get lines and then sscanf testing return values of both
  • only if input is expect to be free form (irrelevant newlines), scanf should be used directly. But never without testing its return value except for trivial tests.

Another drawback is that beginners hope it to be clever. It can indeed parse simple input formats, but is only a poor man's parser: do not use it as a generic parser because that is not what it is intended for.

Provided those rules are observed, it is a nice tool consistent with most of C language and its standard library: a simple tool to do simple things. It is up to programmers or library implementers to build richer tools.

I have only be using C language for more than 30 years, and was never bitten by scanf (well I was when I was a beginner, but I now know that I was to blame). Simply I have just tried for decades to only use it for what it can do...

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252