10

I've been having a lot of problems trying to figure out how to use scanf(). It seems to work fine with integers, being fairly straight forward scanf("%d", &i).

Where I am running into issues is using scanf() in loops trying to read input. For example:

do {
  printf("counter: %d: ", counter);
  scanf("%c %c%d", &command, &prefix, &input);
} while (command != 'q');
  1. When I enter in a validly structured input like c P101, it seems to loop again before prompting me. This seems to happen even with a single:

    scanf("%c", &c) 
    

    in a while loop. It'll do the loop twice before prompting me again. What is making it loop twice, and how do I stop it?

  2. When I enter in less amount of input that programmatically wouldn't have another character or number such as q, pressing enter seems to prompt me to enter more. How do I get scanf() to process both single and double character entries?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
zxcv
  • 7,391
  • 8
  • 34
  • 30

5 Answers5

19

When you enter "c P101" the program actually receives "c P101\n". Most of the conversion specifiers skip leading whitespace including newlines but %c does not. The first time around everything up til the "\n" is read, the second time around the "\n" is read into command, "c" is read into prefix, and "P" is left which is not a number so the conversion fails and "P101\n" is left on the stream. The next time "P" is stored into command, "1" is stored into prefix, and 1 (from the remaining "01") is stored into input with the "\n" still on the stream for next time. You can fix this issue by putting a space at the beginning of the format string which will skip any leading whitespace including newlines.

A similiar thing is happening for the second case, when you enter "q", "q\n" is entered into the stream, the first time around the "q" is read, the second time the "\n" is read, only on the third call is the second "q" read, you can avoid the problem again by adding a space character at the beginning of the format string.

A better way to do this would be to use something like fgets() to process a line at a time and then use sscanf() to do the parsing.

Robert Gamble
  • 106,424
  • 25
  • 145
  • 137
  • 2
    Very good explanation, I suspected it must be reading the \n, but this certainly complicated what I thought was a simple parsing. I'll look into fgets and sscanf(). – zxcv Oct 19 '08 at 23:12
  • Would you consider adding a paragraph about checking the return value from `scanf()` to make sure that you got what you expected. Unless you know of an earlier question, this is likely to become the canonical Q&A for this problem, but the canonical answer should cover all reasonable points and I think the return from `scanf()` is a reasonable point. Make a comment aimed at me when you do it; I'll then remove this comment and flag your comment for removal (or you can flag this as obsolete, but I'd like to know you've made the changes). Thanks. – Jonathan Leffler Feb 13 '14 at 01:24
1

For question 1, I suspect that you've got a problem with your printf(), since there is no terminating "\n".

The default behavior of printf is to buffer output until it has a complete line. That is unless you explicitly change the buffering on stdout.

For question 2, you've just hit one of the biggest problems with scanf(). Unless your input exactly matches the scan string that you've specified, your results are going to be nothing like what you expect.

If you've got an option you'll have better results (and fewer security issues) by ignoring scanf() and doing your own parsing. For example, use fgets() to read an entire line into a string, and then process the individual fields of the string — maybe even using sscanf().

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Andrew Edgecombe
  • 39,594
  • 3
  • 35
  • 61
  • Hmm, I don't think thats it. even with a \n put in, it'll just print it twice before scanf prompts me again. I even tried putting printf before and after the scanf (within the do-while loop) and they loop twice before scanf prompts again. This is with single character input, like s and return. – zxcv Oct 19 '08 at 22:45
1

It's really broken! I didn't know it

#include <stdio.h>

int main(void)
{
    int counter = 1;
    char command, prefix;
    int input;

    do 
    {
        printf("counter: %d: ", counter);
        scanf("%c %c%d", &command, &prefix, &input);
        printf("---%c %c%d---\n", command, prefix, input);
        counter++;
    } while (command != 'q');
}
counter: 1: a b1
---a b1---
counter: 2: c d2
---
 c1---
counter: 3: e f3
---d 21---
counter: 4: ---e f3---
counter: 5: g h4
---
 g3---

The output seems to fit with Robert's answer.

Federico A. Ramponi
  • 46,145
  • 29
  • 109
  • 133
1

Once you have the string that contains the line. i.e. "C P101", you can use the parsing abilities of sscanf.

See: http://www.cplusplus.com/reference/clibrary/cstdio/sscanf.html

jaircazarin-old-account
  • 2,875
  • 3
  • 20
  • 17
0

Perhaps using a while loop, not a do...while loop will help. This way the condition is tested before execution of the code. Try the following code snippet:

 while(command != 'q')
    {
        //statements
    }

Also, if you know the string length ahead of time, 'for' loops can be much easier to work with than 'while' loops. There are also some trickier ways to dynamically determine the length as well.

As a final rant: scanf() does not "suck." It does what it does and that is all.

The gets() function is very dangerous (though convenient for no-risk applications), since it does not natively do any checking of the input. It is VERY commonly known as a point of exploit, specifically buffer overflow attacks, overwriting space in registers not allocated for that variable. Therefore if you choose to use it, spend some time putting some solid error checking/correction in.

However, almost invariably, either fgets() or POSIX getline() should be used to read the line — noting that the functions both include the newline in the input string, unlike gets(). You can remove the trailing newline from string read by either fgets() or getline() using string[strcspn(string, "\n")] = '\0'; — this works reliably.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
jap3r
  • 1