Mysterious C error

Networking/Security Forums -> Programming and More

Author: capiLocation: Portugal PostPosted: Wed Jan 14, 2009 4:21 am    Post subject:
I didn't quite understand your description of the problem, so I just compiled your program and ran it.

For the benefit of those who may read this thread: when the player answers 'n' the program continues through the loop as normal, then it prints the question again and doesn't stop to get the user's choice. It continues on through the loop and only stops the third time around.

The problem you're having is actually quite common among people who are learning C for the first time. It's in your use of scanf.

The first time your question is asked, scanf will try to read from stdin and, since there's nothing to read, it will pause there waiting for the user to enter something. Remember, stdin is line buffered by default (so that the user has to press Enter to commit his reply). In other words, scanf will only see whatever the user types after he presses Enter.

You're telling scanf to read 1 char from standard input and store it in choice. The user presses n and then he presses Enter. That's 2 characters: the user in effect entered "n\n", and that's what your program will see.

If the user types "nnnnnyyyyy" the first time you ask the question, that's the string that will be in stdin. Plus of course the final Enter the user pressed, so that's "nnnnnyyyyy\n". Your program will read 5 'n' answers, 5 'y' answers, and 1 '\n' answer (which is invalid in your program, just as if the user had typed 'z').

Let's look at what's happening in your program. Say the user typed 'n' and pressed enter, so stdin is left with "n\n" available to read. scanf will read 1 character from stdin (the 'n'), and place it in choice. This still leaves the other character in stdin (the Enter, '\n'). The second time the question is asked, scanf will try to read from stdin again. This time there's still something there (the Enter, '\n'). scanf reads that '\n' and stores it in choice. Execution continues as normal, only this time you don't enter any of the ifs because the choice is neither 'y' nor 'n' (it's '\n').

You can see for yourself what is happening if you add a default case to that if (that is, an else clause). Just add an else that does something like:
else {
  printf("invalid option '%c'\n', choice);

You will notice an output such as:
invalid option '

(where the line break indicates a '\n')

The solution is simple: make scanf read (and discard) any previously existing whitespace. You can achieve this by doing:
scanf("\n%c", &choice);

Note the difference is the added \n before the %c. You could also use scanf(" %c", &choice); (a space instead of '\n'). As you will find in the scanf documentation, whenever any kind of whitespace appears in the format string, that means "read any amount of whitespace" (including none).

scanf takes input by matching it against the format string. Think of it as an input parser, since that's what it is. It matches the input against the format string, and quits when it's matched everything it was asked for (in your case, 1 character), or when the input differs from what it's expecting. If you tell it to match "%c54%c" it's going to expect to read one character, followed by 54, followed by another character. If it finds anything else, it bails out.

Note that it won't work if you do scanf("%c\n", &choice); (which would seem like the obvious choice). This would be telling scanf to read 1 character, followed by any amount of whitespace. Since scanf keeps reading from the input until it's matched everything it was asked to match, it will not stop when the user presses Enter (since Enter falls under "any amount of whitespace"). It will only stop when it finds something other than whitespace, after that first Enter.

scanf can be a complicated beast for the novice, as it's not entirely intuitive. You may find it simpler to use a function such as fgets (which reads a whole line of input) and do the processing yourself. You could process the string with sscanf, or just compare it to your allowed choices with strcmp(line, "y\n").

Do NOT, however, under any circumstance, use gets: that function is a broken design which you should never use in a real program. You cannot use gets in a secure way (search the web for "buffer overflows" and gets if you're curious), so don't get used to it in the first place.

You can also use getchar, although you must still remember that when the user types 'n' and presses Enter that's actually 2 characters, not one.

Incidentally, you will find some people use the hack of doing fflush(stdin); to clear the input buffer before executing scanf. This is broken and non-portable behavior, so don't do it. The C standard specifies that fflush causes any unwritten data in an output stream to be written. It explicitly says that the behavior of the function is undefined if the stream is not an output stream. Flushing an input stream (such as stdin) works in some platforms, but it is bad programming and relying on undefined behavior.

Author: capiLocation: Portugal PostPosted: Fri Jan 16, 2009 11:52 pm    Post subject:
You're welcome, glad I could help.

As for learning resources, I'm afraid I can't really recommend anything from personal experience. My knowledge of C comes from many years of experience; there is no one resource I used to learn, and I wouldn't know about what's currently out there anyway. Perhaps others here may suggest something, though.

What I do of course heartily recommend is familiarizing yourself with the official C99 standard, available from the WG14 standards page. This is definitely not light reading, though. If you're like me and like to learn from formal specifications then you'll enjoy reading the standard. Otherwise, at least keep it around for reference.

Apart from that: practice, practice, practice. Nothing beats practice. Write code, lots of it. Read code, lots of it. Take advantage of the free software philosophy and learn as much as you can by reading code written by others. When you don't understand something, experiment with it. When you do understand something, try to understand why it was done that way. When you know why it was done that way, try to think of better ways to do it. Improve on what you read, and while you're at it share what you've improved with the rest of the world Wink

Networking/Security Forums -> Programming and More

output generated using printer-friendly topic mod, All times are GMT + 2 Hours

Page 1 of 1

Powered by phpBB 2.0.x © 2001 phpBB Group