Like Tree3Likes

FAQ-Draft: Why scanf("%c", &c) doesn't work as expected

This is a discussion on FAQ-Draft: Why scanf("%c", &c) doesn't work as expected within the C Programming forums, part of the General Programming Boards category; In contrast to all the other format specifiers (%d, %f, ...) of scanf(), %c (and the lesser known %[ as ...

  1. #1
    Registered User
    Join Date
    May 2012
    Posts
    1,066

    FAQ-Draft: Why scanf("%c", &c) doesn't work as expected

    In contrast to all the other format specifiers (%d, %f, ...) of scanf(), %c (and the lesser known %[ as well) doesn't skip whitespace when trying to match the user input. It reads the next available character, whatever that is.

    One problem arises when you use scanf("%c", &c) several times consecutively, e.g. inside a loop. To send the input to your program, the user has to press the enter key which is also stored in the input buffer (represented as the newline character - \n). Thus if you continuously read single characters from the input buffer, you sooner or later come across the newline character and that's when your program probably acts strangely.

    To see what is happening, run the following program:
    Code:
    #include <stdio.h>
    
    int main(void)
    {
        char c;
    
        do 
        {
            printf("Enter a letter (q to quit): ");
            scanf("%c", &c);
            printf("You've entered >>%c<<\n", c);
        } while (c != 'q');
    
        return 0;
    }
    Sample session:
    Code:
    Enter a letter (q to quit): a
    You've entered >>a<<
    Enter a letter (q to quit): You've entered >>
    <<
    Enter a letter (q to quit): b
    You've entered >>b<<
    Enter a letter (q to quit): You've entered >>
    <<
    Enter a letter (q to quit): q
    You've entered >>q<<
    As you can see, every second scanf()-call reads the newline character which is still left in the input buffer (there would still be a newline character left after quitting the loop!)

    For a simple solution to this problem you could add a space before the format specifier like:
    Code:
    scanf(" %c", &c);
    Now scanf() skips any whitespace characters (including newline) before reading the next character, resulting in the same behavior as with the other format specifiers.

    Another approach is flushing the input buffer after every scanf()-call, but don't use fflush(stdin)!

    See also the manpage for scanf() for more details.
    thames likes this.

  2. #2
    Registered User
    Join Date
    May 2012
    Posts
    1,066
    Any comments? Improvements? Corrections?

    Bye, Andreas

  3. #3
    qny
    qny is offline
    Registered User
    Join Date
    Sep 2012
    Posts
    355
    Still another approach is to not use scanf() and use fgets() instead to read full lines (or getchar() to read isolated characters).
    thames likes this.

  4. #4
    Registered User whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    7,709
    It does work as expected, because every correct description of scanf's workings says that %c does not ignore whitespace.

    In general scanf scans exactly what you tell it to scan, which means you have to understand that most input streams (including stdin) have newlines or whatever in them. It depends on your use of the input stream.
    Last edited by whiteflags; 10-05-2012 at 02:36 PM.

  5. #5
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,498
    Quote Originally Posted by qny View Post
    Still another approach is to not use scanf() and use fgets() instead to read full lines (or getchar() to read isolated characters).
    That method is already covered in a FAQ article (FAQ > Get a line of text from the user/keyboard (C) - Cprogramming.com), and is outside the scope of this one. Often times, newbies are required to use scanf as part of an assignment, to familiarize them with it and it's variants. This issue probably crops up weekly, maybe more often, during the beginning of the school year when everybody is in an intro C class. This FAQ article addresses those cases where switching to fgets is not an alternative.
    Quote Originally Posted by AndiPersti View Post
    Any comments? Improvements? Corrections?
    You might want to make the %[ issue more clear. It will skip white space if you don't specify it in the char set to accept. Also, you might want to mention that adding the space before the %c still wont fix the issue of the dangling '\n' after the loop quits.

  6. #6
    Registered User
    Join Date
    Jun 2011
    Posts
    2,374
    Just a suggestion regarding the title;

    As anduril462 mentioned, this issue comes up frequently on this board. I've answered such questions myself quite a few times. Sometimes, the posters realize the problem is coming from "scanf()" but don't know why. However, other times, they haven't even narrowed it down to that function, so they might not click on this title in the FAQ when researching for an answer (which itself doesn't seem to happen very much in practice).

    Perhaps the title should speak more to why sometimes character input is ignored.

    Of course, generalizing the title in such a way means the article should including more information - but this is probably a good thing (it wouldn't hurt to add something about using "getchar()" with the same effect occurring, i.e. newline remaining in the buffer and affecting successive character scans).

    Just a thought. Great write-up, by the way.

  7. #7
    Registered User whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    7,709
    "How to get a single character from the user" is a better title. I'd rewrite the article from that perspective.

  8. #8
    TEIAM - problem solved
    Join Date
    Apr 2012
    Location
    Melbourne Australia
    Posts
    1,390
    "scanf" has a "read, but don't do anything with it" option. Providing that the user does not enter a space before hitting enter, this will work -

    Code:
    scanf("%c%*c", &ch);
    I think it's important to suggest an inbuilt feature for scanf.

    Also, I'd changed the name of your variable from 'c' to something else to avoid confusion
    Fact - Beethoven wrote his first symphony in C

  9. #9
    Registered User
    Join Date
    May 2012
    Posts
    1,066
    Thanks for your feedback. I've tried to include your enhancements.

    The aim of this FAQ article is to address the pitfalls of %c when beginners try to get single characters repeatedly, as for example in a loop for a menu.
    (That's why I've also removed the part about %[ because I don't think beginners will know about it).

    Bye, Andreas

    How to get a single character using scanf("%c", &ch)

    scanf() reads input from stdin (the standard input stream, typically your terminal/console) which is line buffered. This means that the OS stores the characters the user types into a buffer and only sends whole lines terminated by the newline character "\n" (which you get by pressing the enter key) to your program. The important thing is that the newline character is just a normal character and hence is included in the input stream.

    Usually scanf() skips any leading whitespace characters (space, horizontal/vertical tab, newline) before scanning the input for a match (that's the behavior you see with %d, %f, ...). So any whitespace left behind from a previous input function call is read and thrown away.

    But %c works differently. It tells scanf() to read the next available character, whatever that is.

    One problem arises when you use scanf("%c", &ch) several times consecutively, e.g. inside a loop. scanf() will continuously read single characters from the input stream, thus you will sooner or later come across a newline character and that's when your program probably acts "strangely".
    The same will happen if you use ch = getchar() instead of scanf("%c", &ch) because both statements are equivalent.

    To see what is happening, run the following program:
    Code:
    #include <stdio.h>
    
    int main(void)
    {
        char ch;
    
        do 
        {
            printf("Enter a letter (q to quit): ");
            scanf("%c", &ch);
            printf("You've entered >>%c<<\n", ch);
        } while (ch != 'q');
    
        return 0;
    }
    Sample session:
    Code:
    Enter a letter (q to quit): a
    You've entered >>a<<
    Enter a letter (q to quit): You've entered >>
    <<
    Enter a letter (q to quit): b
    You've entered >>b<<
    Enter a letter (q to quit): You've entered >>
    <<
    Enter a letter (q to quit): q
    You've entered >>q<<
    As you can see, every second scanf()-call doesn't wait for new input but reads the newline character which is still left in the stream. Only then the stream is empty and the program has to wait for the user to get new input. Note also that after quitting the loop with "q", the newline character is still left behind and could affect a character reading function later in the program!

    There are several workarounds:
    For a simple solution, you could add a space before the format specifier like:
    Code:
    scanf(" %c", &ch);
    The leading space tells scanf() to skip any whitespace characters (including newline) before reading the next character, resulting in the same behavior as with the other format specifiers.

    You could also use getchar() after a scanf() call to remove the newline character. Similarly you could use
    Code:
    scanf("%c%*c", &ch);
    which will read two characters, storing the first in "ch" and throwing away the second. Both solutions only work if you are sure the next character to read will be "\n".

    Another approach is flushing the input buffer after every scanf()-call, but don't use fflush(stdin)!

    Depending on your requirements you could also forget about scanf() at all, use fgets() to get a line of text from the user and parse it yourself.

    See also the manpage for scanf() for more details.

  10. #10
    Stoned Witch Barney McGrew's Avatar
    Join Date
    Oct 2012
    Location
    astaylea
    Posts
    420
    Quote Originally Posted by AndiPersti View Post
    scanf() reads input from stdin (the standard input stream, typically your terminal/console) which is line buffered.
    stdin will be fully buffered if it's not connected to an interactive device. In
    the case that it's connected to a terminal, it may be unbuffered or line
    buffered.

    Quote Originally Posted by AndiPersti View Post
    The same will happen if you use ch = getchar() instead of scanf("%c", &ch) because both statements are equivalent.
    Although they have the same effect, getchar returns an int for the value EOF.
    Assuming that the type of ch is char, it's usually wrong to convert EOF to a
    char.

    If the programmer's objective is to read a character from the stream, then
    discard the rest of the line, something like this would be sufficient:

    Code:
    #include <stdio.h>
    
    int readch(void)
    {
        int c, t;
    
        if ((c = getchar()) == EOF)
            return EOF;
        if (c == '\n')
            return readch();
        while ((t = getchar()) != '\n')
            if (t == EOF)
                return EOF;
        return c;
    }
    Although a better solution would be to write programs that don't assume stdin
    is connected to an interactive device. There are libraries like ncurses which
    are better suited for such programs than standard C.

  11. #11
    Registered User
    Join Date
    May 2012
    Posts
    1,066
    Ok, I've rethought the article and think it's best to just concentrate on the main problem: the dangling \n. So here's my new version:


    How to avoid a "dangling" newline when getting a single character from the user

    Whenever you need to get a single character from the user you will probably think about using one of two functions: scanf() with the format specifier %c or getchar().
    Both functions read a character from the standard input stream (the difference between them is that getchar() returns an int, whereas scanf() expects a pointer to char).

    But both have the same pitfall: when the user enters a character s/he afterwards presses the enter key. Thus there are actually two characters put into the input stream: the character entered and the newline character.
    If you don't remove the newline character from the input stream, it could interfere with input functions later in the program. It seems that the input is ignored.
    For example look at the following program:
    Code:
    #include <stdio.h>
    
    int main(void)
    {
        char ch;
    
        do 
        {
            printf("Enter a letter (q to quit): ");
            scanf("%c", &ch);
            printf("You've entered >>%c<<\n", ch);
        } while (ch != 'q');
    
        return 0;
    }
    And here's a sample session:
    Code:
    Enter a letter (q to quit): a
    You've entered >>a<<
    Enter a letter (q to quit): You've entered >>
    <<
    Enter a letter (q to quit): b
    You've entered >>b<<
    Enter a letter (q to quit): You've entered >>
    <<
    Enter a letter (q to quit): q
    You've entered >>q<<
    As you can see, every second scanf()-call doesn't wait for new input but reads the newline character which is still left in the stream. Only then the stream is empty and the program has to wait for the user to get new input. Note also that after quitting the loop with "q", the newline character is still left behind and could affect a character reading function later in the program!

    There are several workarounds:
    For a simple solution, you could add a space before the format specifier like:
    Code:
    scanf(" %c", &ch);
    The leading space tells scanf() to skip any whitespace characters (including newline) before reading the next character, resulting in the same behavior as with the other format specifiers.

    You could also use getchar() after a scanf() call to remove the newline character. Similarly you could use
    Code:
    scanf("%c%*c", &ch);
    which will read two characters, storing the first in "ch" and throwing away the second. Both solutions only work if you are sure the next character to read will be "\n".

    Another approach is flushing the input buffer after every scanf()-call, but don't use fflush(stdin)!

    Depending on your requirements you could also forget about scanf() at all, use fgets() to get a line of text from the user and parse it yourself.

    See also the manpage for scanf() for more details.

  12. #12
    Registered User
    Join Date
    Sep 2006
    Posts
    8,868
    Good idea to edit it down - make it clear, concise and complete.

    1) use an active and current voice. Avoid expressions like "will probably", etc.

    2) don''t hyperlink to other ways to do this. Include that info in your article.

    3) always use '\n' to indicate a newline, never "\n".

    4) in the sample session display, use color or comments, right in the output, to show the error. The third block of output, should be deleted, since it doesn't show the problem you're describing.

  13. #13
    Registered User
    Join Date
    May 2010
    Posts
    2,760
    You may also want to mention what happens if a user enters a string ("ABCDE") instead of the single character you are looking for.

    Jim

  14. #14
    Registered User
    Join Date
    May 2012
    Posts
    1,066
    Quote Originally Posted by Adak View Post
    1) use an active and current voice. Avoid expressions like "will probably", etc.
    I'm not a native speaker so I'm always open for stylistic improvements :-)
    How about changing the first sentence to:
    "Whenever you need to get a single character from the user you may think about using either scanf() with the format specifier %c or getchar()."

    Quote Originally Posted by Adak View Post
    2) don''t hyperlink to other ways to do this. Include that info in your article.
    -1
    IMHO that's what hyperlinks are made for. If there is already a FAQ article which answers a specific problem you don't have to repeat it.

    Quote Originally Posted by Adak View Post
    3) always use '\n' to indicate a newline, never "\n".
    Good catch :-) I'll change it to "newline character".

    Quote Originally Posted by Adak View Post
    4) in the sample session display, use color or comments, right in the output, to show the error. The third block of output, should be deleted, since it doesn't show the problem you're describing.
    The aim of the article is to get included in the FAQ list (not sure if it will ever be). Thus the sample code/output will look like all the other samples (e.g. here you see that the output is usually shown in comments below the code).
    About deleting the third block, I tend to agree with you. Another option would be to show the newline left behind after the loop. Something like
    Code:
    printf("character left behind after loop: >>%c<<\n", getchar());
    Thanks for the feedback,
    Andreas

  15. #15
    Registered User
    Join Date
    May 2012
    Posts
    1,066
    Quote Originally Posted by jimblumberg View Post
    You may also want to mention what happens if a user enters a string ("ABCDE") instead of the single character you are looking for.
    I think I replace the last three lines with the following:

    "All three workarounds above aren't very stable. Consider the case when the user enters more than one character (by accident or on purpose). They will all fail in that situation.

    A better aproach is flushing the input buffer after every scanf()/getchar()-call, but don't use fflush(stdin)!

    Or, depending on your requirements, you could also forget about scanf()/getchar() at all, use fgets() to get a line of text from the user and parse it yourself."

    Bye, Andreas

Page 1 of 2 12 LastLast
Popular pages Recent additions subscribe to a feed

Similar Threads

  1. "strcpy(string,char[int])" doesn't work?
    By JonathanS in forum C Programming
    Replies: 4
    Last Post: 10-17-2011, 10:32 AM
  2. nbin=fopen("input.txt","a"); doesn't work?
    By Adam Rinkleff in forum C Programming
    Replies: 2
    Last Post: 06-23-2011, 02:57 PM
  3. Replies: 9
    Last Post: 03-31-2009, 04:23 PM
  4. Replies: 10
    Last Post: 12-08-2008, 07:12 AM
  5. "return function" doesn't work
    By Cris987 in forum C++ Programming
    Replies: 10
    Last Post: 03-04-2004, 10:04 PM

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21