1

In C, when we use a scanf("%s") or scanf("%c") before a scanf("%s") or scanf("%[^\n]"), while giving in the input, there will be an extra '\n' at the end of the first input and that will be passed into the second input and mess up the input stream. I've tried these codes in two different systems with the same gcc compiler as far as I know. But it did different things each time. In the first system I had to use a scanf("\n") to discard the newline character. But in the second system there was no such need. It discarded the newline character automatically.

Then I tried three codes,

code 1:

printf("Enter the name of the student: ");
scanf("%s", name);

printf("Enter the email of the student: ");
scanf("%s", email);

Here I didn't have to ignore the newline character, I had no issues with the compiler.

code 2:

printf("Enter the name of the student: ");
scanf("%s", name);
scanf("%*c");

printf("Enter the email of the student: ");
scanf("%s", email);

here I just added the scanf("%*c") to discard the newline character, this had the same result as code 1. But when I replace scanf("%*c") with scanf("\n") the input is messed up again where I have to give 3 inputs now because it doesn't properly discard the first newline character.

code 3:

char chr, s[100], sen[100];

scanf("%c", &chr);
scanf("\n");
scanf("%s", s);
scanf("\n");
scanf("%[^\n]%*c", sen);

Here the code doesn't work without the scanf("\n")s.

I don't really understand what's happening here. Why is it that sometimes I have to escape the newline character and other times it's not required.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
Navaneeth Reddy
  • 315
  • 1
  • 12
  • Most of the format specifiers will read and discard a (possibly empty) initial sequence of whitespace characters. The exceptions are `%c` and `%[`. You can use `scanf(" %c", &chr);` to discard any initial whitespace before a non-whitespace character you are interested in. (Of course, that is no good if you actually want to read and assign the whitespace character to the dereferenced parameter.) – Ian Abbott Mar 08 '23 at 11:41
  • 1
    `scanf("\n");` or `scanf(" ");` or `scanf("``");` will read and discard any amount (including zero) of whitespace characters. – Ian Abbott Mar 08 '23 at 11:46
  • 3
    I would not use scanf to scan the strings or characters – 0___________ Mar 08 '23 at 11:49
  • 1
    Certainly `%s` and `%[` are not safe to use on unknown input. Consider adding a maximum field width. – Ian Abbott Mar 08 '23 at 11:52
  • 1
    You never have to use `scanf()` for anything. In fact, if you're doing real work parsing user input, [you should *never* use `scanf()`](https://stackoverflow.com/questions/2430303/disadvantages-of-scanf) – Andrew Henle Mar 08 '23 at 14:04
  • @AndrewHenle Can you suggest me what to do instead of `scanf` – Navaneeth Reddy Mar 08 '23 at 16:19
  • @IanAbbott I'm not aware of maximum field width. Care to explain? – Navaneeth Reddy Mar 08 '23 at 16:22
  • @NavaneethReddy This link is in my answer, but here it is again: [What can I use for input conversion instead of `scanf`?](https://stackoverflow.com/questions/58403537) – Steve Summit Mar 08 '23 at 16:41
  • @NavaneethReddy Field width is basic: `scanf("%s", s);` will do horrible things if the user types more than 99 characters, while `scanf("%99s", s);` is much safer. – Steve Summit Mar 08 '23 at 16:42
  • Maximum field width example: `scanf("%99s", s);` will skip initial whitespace, then read at most 99 non-whitespace characters into `s` and finally append a null terminator to `s`. Any extra non-whitespace characters will be left behind in the standard input stream. – Ian Abbott Mar 08 '23 at 16:45
  • @IanAbbott thanks for letting me know about this – Navaneeth Reddy Mar 08 '23 at 17:15

3 Answers3

5

In C, when we use a scanf("%s") or scanf("%c") before a scanf("%s") or scanf("%[^\n]"), while giving in the input, there will be an extra '\n' at the end of the first input and that will be passed into the second input and mess up the input stream.

I would not put it that way. I would say:

Whenever we use scanf with any format specifier, the \n indicating the end of the line the user typed remains on the input stream where it may cause later problems. There are three cases:

  1. If the next input is scanf using any format specifier other than %c or %[…], the lingering \n will automatically be skipped along with any other leading whitespace.
  2. If the next input is scanf using %c or %[…], the \n will be read by that format specifier (although that is usually not desired).
  3. If the next input is something other than scanf, such as fgets or getchar, the \n will be read by that function (although that is usually not desired).

(Or perhaps more concisely: "If we use scanf("%c") or scanf("%[…]") after having previously called scanf on something else, the leftover \n tends to mess things up.")

Situation #2 is easily fixed by adding an extra leading space to the format specifier, explicitly instructing scanf to skip leading whitespace (as it implicitly does for all other format specifiers).

Calling scanf("\n") by itself is not usually recommended, as it doesn't do what it appears to do.

These rules aren't the complete story on scanf, but they're a good start and explain the behavior in most basic uses.

I've tried these codes in two different systems with the same gcc compiler as far as I know. But it did different things each time.

That is surprising. If you could describe that difference exactly and reproducibly, it might be interesting to explore.

In your code 1, you're reading two simple strings, so as long as they don't contain spaces, you're fine. There's a \n left over after scanf reads the first string, but it's skipped by the second %s.

In your code 2, the call to scanf("%*c") reads and discards the \n. This is indeed one way to explicitly discard a stray newline, but I don't believe it's the best way. (The problems are that it will read any character — not necessarily just a newline — and if you call it at a spot where there isn't a stray character to consume, it will block, waiting for a character it can consume.)

In your code 3, you say "the code doesn't work without the scanf("\n")s", and that's partly true. After reading a string with %s, there's a \n still on the input stream. So the next %[^\n] is prematurely satisfied by that stray \n. So your call to scanf("\n") is one way to strip it. (But you didn't need to call scanf("\n") between the %c and the %s.)

As an aside, rather than calling scanf("\n"), I believe it would have been cleaner to just add a leading space to the following %[…] specifier: scanf(" %[^\n]%*c", sen);.

(Also you don't necessarily need that %*c out at the end. I know what it's for, but I'd say you don't or shouldn't need it. More on this below.)


The bottom line is that scanf is a pretty highly problematic input function. It's nice and simple to use for really simple inputs, in simple, beginning programs. But it's not much good — it's a frustrating nuisance, generally more trouble than it's worth — for anything fancy.

What do I mean by "really simple inputs"? It's a pretty short list:

  • If you want to read a single int with %d, or a single float or double with %f or %lf, that's fine.
  • If you want to read a simple string with %s, that's fine, but beware that the string will not contain any spaces. You also have to make sure you've properly allocated the memory which scanf will read the string into. It's also a good idea to use a width modifier like %29s so that scanf can avoid overflowing that memory.
  • If you want to read a single character, that might be fine, but remember to use " %c", not plain "%c".

And that's it. If you want to do anything fancier than that, scanf starts turning into a pumpkin. In particular:

  • If you want to read a string that might contain spaces, %s won't work, and there's no good, simple alternative. (There's %[…], of course, but it's not simple.)
  • If you anticipate that the user might type something wrong, like a letter when you're expecting a number, or two characters when you're expecting a single one, and if you want to detect this and print a nice message like "Improper input, try again", scanf is not the right tool for the job.

And, as a more general rule, if you're trying to do something with scanf, and it's not working, and you come to believe that you need to "flush" some unwanted input, stop right there. The very fact that the word "flush" has entered your mind is an almost perfectly reliable indicator that scanf is not the right tool for the job you're trying to do, and you should strongly consider abandoning scanf in favor of something else. (Whatever it is that you're trying to do, although it might be barely possible to do using scanf and some clumsy input-flushing mechanism, it's just about guaranteed to be harder to do, and work less well, than if you used something else.)

See here and here for some similar sets of guidelines on what to try to do, versus not do, using scanf. And when you're ready to try something else, read What can I use for input conversion instead of scanf?.


Finally, a few more words about your call to scanf("%[^\n]%*c", sen);, and specifically that extra %*c specifier at the end. I said I thought you didn't need it. Here's why.

What does that %*c do? Well, %[^\n] reads a line of text up to but not including a newline, so %*c reads and discards the newline, presumably so that it "won't stick around on the input stream and cause problems later". But let's think about this a little more.

When you get right down to it, scanf is always leaving newlines on the input stream. It's the default behavior with scanf. And, if the next call is careful to always skip the stray newline, everything works out fine.

Suppose you call

scanf("%d", &num1);
scanf("%d", &num2);
scanf("%d", &num3);

There's a newline left over after scanning num1, and there's a newline left over after scanning num2, but neither of them cause any problems, because the next %d cleanly skips it.

It's not really a problem that there's always a stray newline after a scanf call, unless the next input call doesn't skip it. That happens if (a) the next input call is scanf using %c or %[…], or (b) the next input call isn't scanf at all, but is rather something like fgets or getchar.

So one way (and IMO the best and cleanest way) of dealing with stray newlines with scanf is just to declare that it's always the next call's job to skip them:

  1. If the next call involves a specifier like %d or %f or %s, it automatically skips leading whitespace, so we're fine.
  2. If the next call involves the specifiers %c or %[…], add an explicit leading space to force a stray newline to be skipped: " %c" or " %[…]".
  3. If the next call isn't scanf... well, that's a bad idea. My recommendation is to never mix scanf with other input functions in the same program.

There's the concept of a loop invariant, which is something useful you can say that's true for each trip through a loop, like "the variable i is always the index of the next cell we'll fill in" or "len is always the length of the string built so far."

Similarly, when reading input a line at a time, it's useful to have a convention for how the newlines are handed. We could have either:

  1. "As each line of input is read, the \n is read also. The input stream is left positioned at the beginning of the next line to be read." Or,
  2. "After reading each line of input, the \n is left on the input stream. It is always the job of the next line-reading function to first skip past that \n."

Both if these conventions are consistent. If you're reading lines of input using fgets, you're obviously using convention #1. But if you're reading input using scanf, well, scanf is always mostly happy with convention #2.

And I believe, for sanity's sake, that if you're using scanf, you should embrace convention #2 wholeheartedly. Don't try to strip the \n after reading a line. Don't use locutions like %*c after %[…] so that "the newline won't cause problems later". Just let the stray newlines linger, and make sure that you always skip them next time. %d and %f and %s do that automatically. %c and %[…] do that, in effect, if you explicitly add a leading space. And don't try to use fgets or getchar in a program where you're also using scanf. (Or, if you must intermix scanf and fgets, then right before calling fgets, do something to skip the newline if it's there. That's one place where a call to scanf("\n") might be warranted.)

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
3

scanf() only consumes the characters that match the conversion format string. Hence it leaves the rest of the input pending in stdin, eg: the newline entered by the user to submit a line of input when stdin is line buffered as is the default when reading from the terminal.

Such white space is skipped by most conversions, but not for %c or %[...]. For these formats, you can explicitly skip the pending newline and other white space by inserting a space before the format.

Also make sure you test the return value of scanf() to detect invalid or missing input and specify the maximum number of characters to store to the destination array for %s and %[...] conversions to avoid buffer overflows.

#include <stdio.h>

int main() {
    char name[100];
    char email[100];
    char grade;
    
    printf("Enter the name of the student: ");
    if (scanf("%99[^\n]", name) != 1)
        return 1;
    
    printf("Enter the grade letter: ");
    if (scanf(" %c", &grade) != 1)
        return 1;
    
    printf("Enter the email of the student: ");
    if (scanf("%99s", email) != 1)
        return 1;

    printf("name: %s\n", name);
    printf("email: %s\n", email);
    printf("grade: %c\n", grade);
    return 0;
}

To avoid the pending newline issue and improve error recovery, it is recommended to read the input one line at a time and use sscanf() to convert the user input.

#include <stdio.h>

int main() {
    char line[200];
    char name[100];
    char email[100];
    char grade;
    
    printf("Enter the name of the student: ");
    if (!fgets(line, sizeof line, stdin)) {
        printf("unexpected end of file\n");
        return 1;
    }
    if (sscanf(line, "%99[^\n]", name) != 1) {
        printf("no name given\n");
        return 1;
    }
    printf("Enter the grade letter: ");
    if (!fgets(line, sizeof line, stdin)) {
        printf("unexpected end of file\n");
        return 1;
    }
    if (sscanf(line, " %c", &grade) != 1) {
        printf("no grade given\n");
        return 1;
    }
    printf("Enter the email of the student: ");
    if (!fgets(line, sizeof line, stdin)) {
        printf("unexpected end of file\n");
        return 1;
    }
    if (sscanf(line, "%99s", email) != 1)
        printf("no email given\n");
        return 1;
    }
    printf("name: %s\n", name);
    printf("email: %s\n", email);
    printf("grade: %c\n", grade);
    return 0;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
3

code 1:

printf("Enter the name of the student: ");
scanf("%s", name);

printf("Enter the email of the student: ");
scanf("%s", email);

Here I didn't have to ignore the newline character, I had no issues with the compiler.

From the C Standard (7.21.6.2 The fscanf function)

8 Input white-space characters (as specified by the isspace function) are skipped, unless the specification includes a [, c, or n specifier.

So there is no problem with the above code. White space characters including the new line character '\n' will be skipped.

code 2:

printf("Enter the name of the student: ");
scanf("%s", name);
scanf("%*c");

printf("Enter the email of the student: ");
scanf("%s", email);

here I just added the scanf("%*c") to discard the newline character, this had the same result as code 1.

Indeed as it follows from the quote above the function itself will skip new line characters. So this added statement

scanf("%*c");

does not change the expected result.

But when I replace scanf("%*c") with scanf("\n") the input is messed up again where I have to give 3 inputs now because it doesn't properly discard the first newline character.

From the C Standard (7.21.6.2 The fscanf function)

5 A directive composed of white-space character(s) is executed by reading input up to the first non-white-space character (which remains unread), or until no more characters can be read.

So for this statement

scanf("\n");

the function skips all white space characters stored in the input buffer and waits when a non-white space character will be added or the call will be interrupted for example by pressing Ctrl+z in MS VC.

That is before executing this statement

printf("Enter the email of the student: ");

the function scanf will wait an input that will be stored in the input buffer and then will be read by the next call of scanf

scanf("%s", email);

That is the program correctly will store data in the variables name and email but the program flow will confuse users because the program will wait for the input before this statement

printf("Enter the email of the student: ");

And after this call of printf the data entered before this call will be automatically read by the next call of scanf. That is the pause of waiting an input will be not after the call of printf but before the call of printf.

This code snippet

char chr, s[100], sen[100];

scanf("%c", &chr);
scanf("\n");
scanf("%s", s);
scanf("\n");
scanf("%[^\n]%*c", sen);

behaves the same way as the code snippet shown above (#2). The difference is that there is no call of printf. So the program flow does not confuse users of the code.

Pay attention to that instead of these two statements

scanf("\n");
scanf("%[^\n]%*c", sen);

you could just write

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

placing a leading space in the format string. This leading space character allows to skip white space characters before a non white space character is encountered.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335