Thread: Frustrated with the instablility of scanf() and gets() functions

Hybrid View

Previous Post Previous Post   Next Post Next Post
  1. #1
    Registered User
    Join Date
    Dec 2008
    Posts
    36

    Angry Frustrated with the instablility of scanf() and gets() functions

    Well, when I run this program it works fine in the first run of it but from the second run it begins to annoy me (or the user):

    Code:
    #include <stdio.h>
    #include <conio.h>
    #include <ctype.h>
    #include <process.h>
    
    #define pause getch()
    #define clear clrscr()
    
    void main(void) {
    
    	char UserName[25];
    	char UserPhone[10];
    	char UserChoice = ' ';
    
    	clear;
    
    	printf("\nName: ");
    	gets(UserName);
    
    	printf("\nPhone: ");
    	gets(UserPhone);
    
    	printf("\nContinue [Y/N] ? : ");
    	scanf("%c", &UserChoice);
    
    	if(toupper(UserChoice) == 'Y')
    		main();
    	
    	printf("\nPress any key to exit...");
    	pause;
    	exit(0);
    }

    First Run:

    Name: Mr. X

    Phone: 0000000000

    Continue [Y/N] ? : y

    Second Run:

    Name:
    Phone: 0000000000

    Continue [Y/N] ? : n

    Press any key to exit...

    Now, you see what is the thing that annoys? In the second run the entry field for the Name is skipped and we are at the field Phone.


    Now before you suggest me anything, kindly, consider this revised version of the above program in which I have used flushall() function to avoid the annoyance:

    Code:
    #include <stdio.h>
    #include <conio.h>
    #include <ctype.h>
    #include <process.h>
    
    #define pause getch()
    #define clear clrscr()
    
    void main(void) {
    
    	char UserName[25];
    	char UserPhone[10];
    	char UserChoice = ' ';
    
    	clear;
    
    	printf("\nName: ");
    	flushall();
    	gets(UserName);
    
    	printf("\nPhone: ");
    	flushall();
    	gets(UserPhone);
    
    	printf("\nContinue [Y/N] ? : ");
    	flushall();
    	scanf("%c", &UserChoice);
    
    	if(toupper(UserChoice) == 'Y')
    		main();
    	
    	printf("\nPress any key to exit...");
    	pause;
    	exit(0);
    }

    Well, I have used the flushall() function just before every gets() and scanf() because otherwise the annoyance would not be guaranted to be gone.

    Now, the question is:

    Do I have to user flushall() everytime I use a scanf() or gets() or any user input function?

  2. #2
    Registered User Sharke's Avatar
    Join Date
    Jun 2008
    Location
    NYC
    Posts
    303
    Doesn't scanf() leave the newline character in the buffer? In which case I would have thought you would have to flush each time.

  3. #3
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    scanf leaves a newline behind, yes; hence you would only need to flush after the scanf.

    (Of course the use of gets is an abomination, and flushall is presumably compiler-specific; you should fgets for the former and fgets for the latter (why use scanf when it causes problems?))

  4. #4
    Registered User
    Join Date
    Sep 2006
    Posts
    8,868
    Instead of flushall, which is undefined for an input stream, use getchar(), right after the scanf() line, and all will be well with your newline problem.

  5. #5
    Registered User
    Join Date
    Dec 2008
    Posts
    36
    Well, I have tried getchar() also and it does the same thing as flushall() does but with the requirement of pressing one more key after pressing Enter when a value has been entered. Besides, if the key pressed is other Enter then you have got to pull your head's hair again as you do in the original program given above.

    Quote Originally Posted by Adak View Post
    Instead of flushall, which is undefined for an input stream, use getchar(), right after the scanf() line, and all will be well with your newline problem.

  6. #6
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by Adak View Post
    Instead of flushall, which is undefined for an input stream, use getchar(), right after the scanf() line, and all will be well with your newline problem.
    Unless the user typed a stray space at the end of the line, in which case this space gets consumed, not the newline, and we are back at the same problem again.

    You need to consume characters all the way to the end of the line.

    Code:
    void flushRestOfLine(FILE *in)
    {
        int ch;
    
        while((ch = fgetc(in)) != EOF && ch != '\n')
            ;
    }
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  7. #7
    Registered User
    Join Date
    Apr 2009
    Posts
    1
    Hi all,

    The easiest way is to add the line

    scanf("%*c");

    This will take a way the end of line in the buffer.

  8. #8
    Registered User
    Join Date
    Apr 2009
    Location
    Malaysia
    Posts
    2
    I'm not a Pro Programmer, but then I could suggest you to use a single line and get rid off your probrlem!!!

    Code:
    #include <stdio.h>
    #include <conio.h>
    #include <ctype.h>
    #include <process.h>
    
    #define pause getch()
    #define clear clrscr()
    
    
    void main(void) {
    
    	char UserName[25];
    	char UserPhone[10];
    	char UserChoice = ' ';
    
    	clear;
    
    	printf("\nName: ");
    	gets(UserName);
    
    	printf("\nPhone: ");
    	gets(UserPhone);
    
    	printf("\nContinue [Y/N] ? : ");
    	scanf("%c", &UserChoice);
    	fflush(stdin); // flush the standard input stream after scanf
    
    	if(toupper(UserChoice) == 'Y')
    		main();
    	
    	printf("\nPress any key to exit...");
    	pause;
    	exit(0);
    }
    fflush(stdin) flushes the standard input stream... fflush() function is defined in <conio.h> and is supplied with all the compilers I've used!!

  9. #9
    DESTINY BEN10's Avatar
    Join Date
    Jul 2008
    Location
    in front of my computer
    Posts
    804
    Quote Originally Posted by vohkiem View Post
    The easiest way is to add the line

    scanf("%*c");

    This will take a way the end of line in the buffer.
    what this line actually did.i put it and yes the problem was solved.can you explian it a bit.

    also when i put this line instead of scanf
    Code:
    printf("\nContinue [Y/N] ? : ");
    UserChoice=getchar();
    then also the same problem persisted.why?does getchar also leaves the any character in the buffer?
    Thank You
    Last edited by BEN10; 04-04-2009 at 08:49 AM.

  10. #10
    Registered User
    Join Date
    Dec 2008
    Posts
    36
    Hi,

    I just tried fgets instead of gets and as it is used for file handling it doesn't serve the purpose I have set in the given program.

    Or could you please give an example?

    Thanks!

    Quote Originally Posted by tabstop View Post
    scanf leaves a newline behind, yes; hence you would only need to flush after the scanf.

    (Of course the use of gets is an abomination, and flushall is presumably compiler-specific; you should fgets for the former and fgets for the latter (why use scanf when it causes problems?))

  11. #11
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by devarishi
    I just tried fgets instead of gets and as it is used for file handling it doesn't serve the purpose I have set in the given program.
    fgets() definitely can be used with files, but it can also be used with the standard input stream, stdin.

    Quote Originally Posted by devarishi
    Or could you please give an example?
    If you need a little searching, or even just looking around, you might have spotted this recent thread where I posted a relevant link: prompting to enter a name in a loop skips the name enter part.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  12. #12
    Registered User
    Join Date
    Dec 2008
    Posts
    36
    Well, I have used flushall() after gets() and scanf() this time and it serves the purpose efficiently. However, I would like to eliminate the need of using flushall() at all.

  13. #13
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Well, I have used flushall() after gets() and scanf() this time and it serves the purpose efficiently. However, I would like to eliminate the need of using flushall() at all.
    As tabstop mentioned, you only need to "flush" the input buffer once per iteration (and yes, iteration, not recursion: you do not need to call main recursively). I still am not clear where does flushall() come from (<conio.h>?), but I would do is write the function myself using the standard library.

    Thus, I would propose this instead:
    Code:
    #include <stdio.h>
    #include <string.h>
    #include <ctype.h>
    #include <conio.h>
    
    char *getLine(char *str, size_t size, FILE *stream);
    void clearScreen();
    void ignoreUnreadInput();
    
    int main(void) {
        int UserChoice;
    
        do {
            char UserName[25];
            char UserPhone[10];
    
            printf("\nName: ");
            getLine(UserName, sizeof(UserName), stdin);
    
            printf("\nPhone: ");
            getLine(UserPhone, sizeof(UserPhone), stdin);
    
            printf("\nContinue [Y/N] ? : ");
            UserChoice = getchar();
    
            ignoreUnreadInput();
            clearScreen();
        } while (toupper(UserChoice) == 'Y');
    
        printf("\nPress enter to exit...");
        getchar();
        return 0;
    }
    
    char *getLine(char *str, size_t size, FILE *stream)
    {
        if ((str = fgets(str, size, stream)))
        {
            char *new_line = strchr(str, '\n');
            if (new_line)
            {
                *new_line = '\0';
            }
        }
        return str;
    }
    
    void clearScreen()
    {
        clrscr();
    }
    
    void ignoreUnreadInput()
    {
        int ch;
        while ((ch = getchar()) != EOF && ch != '\n');
    }
    I implemented a getLine() function to replace gets(). It uses fgets(), but additionally removes the newline that might be read into the string by fgets().

    Instead of scanf() I used getchar() as suggested by tabstop.

    Instead of just using one getchar() call as suggested by Adak, I implemented ignoreUnreadInput() similiar to how it was done in the tutorial I linked to, since a user could enter multiple characters instead of just 'y' or 'n'.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  14. #14
    Registered User
    Join Date
    Dec 2008
    Posts
    36
    Hi LaserLight,

    Why didn't you choose to make your name Light of Knowledge? Haha!! Well, thanks for your support. Now I am leaving for the day as my shift has ended. So, I will try it later on if I can't do it right away.

    Thanks a lot to all of you guys!


    Quote Originally Posted by laserlight View Post
    As tabstop mentioned, you only need to "flush" the input buffer once per iteration (and yes, iteration, not recursion: you do not need to call main recursively). I still am not clear where does flushall() come from (<conio.h>?), but I would do is write the function myself using the standard library.

    Thus, I would propose this instead:
    Code:
    #include <stdio.h>
    #include <string.h>
    #include <ctype.h>
    #include <conio.h>
    
    char *getLine(char *str, size_t size, FILE *stream);
    void clearScreen();
    void ignoreUnreadInput();
    
    int main(void) {
        int UserChoice;
    
        do {
            char UserName[25];
            char UserPhone[10];
    
            printf("\nName: ");
            getLine(UserName, sizeof(UserName), stdin);
    
            printf("\nPhone: ");
            getLine(UserPhone, sizeof(UserPhone), stdin);
    
            printf("\nContinue [Y/N] ? : ");
            UserChoice = getchar();
    
            ignoreUnreadInput();
            clearScreen();
        } while (toupper(UserChoice) == 'Y');
    
        printf("\nPress enter to exit...");
        getchar();
        return 0;
    }
    
    char *getLine(char *str, size_t size, FILE *stream)
    {
        if ((str = fgets(str, size, stream)))
        {
            char *new_line = strchr(str, '\n');
            if (new_line)
            {
                *new_line = '\0';
            }
        }
        return str;
    }
    
    void clearScreen()
    {
        clrscr();
    }
    
    void ignoreUnreadInput()
    {
        int ch;
        while ((ch = getchar()) != EOF && ch != '\n');
    }
    I implemented a getLine() function to replace gets(). It uses fgets(), but additionally removes the newline that might be read into the string by fgets().

    Instead of scanf() I used getchar() as suggested by tabstop.

    Instead of just using one getchar() call as suggested by Adak, I implemented ignoreUnreadInput() similiar to how it was done in the tutorial I linked to, since a user could enter multiple characters instead of just 'y' or 'n'.

  15. #15
    Registered User
    Join Date
    Dec 2008
    Posts
    36

    Thumbs up

    Okay, I compiled the program and the following warning message flagged:
    Code:
    if ((str = fgets(str, size, stream)))
    
    Warning: TRYIT.CPP 37: Possibly incorrect assignment

    However, I ran the program and here is the output:

    First Run:

    Name: Mr. X Man

    Phone: 123456789

    Continue [Y/N] ? : y


    Screen gets cleared, as it should, and the following message is displayed:

    Press enter to exit...


    Second Run:

    Name: Mr. X

    Phone: 12345678

    Continue [Y/N] ? : y


    (Screen gets cleared):

    [B]Name: Mr. X

    ....

    then runs fine.

    But... I changed the input a little- Phone number! The program went in an unexpected way when I supplied a nine digit number to it. But it worked fine when there were only eight digits.

    But, thanks! There is something in your codes for me to learn from.

    Thumps up to you! Have a nice time, friend!


    Dev.

    Quote Originally Posted by laserlight View Post

    Thus, I would propose this instead:
    Code:
    #include <stdio.h>
    #include <string.h>
    #include <ctype.h>
    #include <conio.h>
    
    char *getLine(char *str, size_t size, FILE *stream);
    void clearScreen();
    void ignoreUnreadInput();
    
    int main(void) {
        int UserChoice;
    
        do {
            char UserName[25];
            char UserPhone[10];
    
            printf("\nName: ");
            getLine(UserName, sizeof(UserName), stdin);
    
            printf("\nPhone: ");
            getLine(UserPhone, sizeof(UserPhone), stdin);
    
            printf("\nContinue [Y/N] ? : ");
            UserChoice = getchar();
    
            ignoreUnreadInput();
            clearScreen();
        } while (toupper(UserChoice) == 'Y');
    
        printf("\nPress enter to exit...");
        getchar();
        return 0;
    }
    
    char *getLine(char *str, size_t size, FILE *stream)
    {
        if ((str = fgets(str, size, stream)))
        {
            char *new_line = strchr(str, '\n');
            if (new_line)
            {
                *new_line = '\0';
            }
        }
        return str;
    }
    
    void clearScreen()
    {
        clrscr();
    }
    
    void ignoreUnreadInput()
    {
        int ch;
        while ((ch = getchar()) != EOF && ch != '\n');
    }

Popular pages Recent additions subscribe to a feed

Tags for this Thread