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

  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
    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?))

  6. #6
    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

  7. #7
    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.

  8. #8
    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.

  9. #9
    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

  10. #10
    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'.

  11. #11
    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');
    }

  12. #12
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    You are certain that the number you entered did not exceed 9 characters (including separators and stuff)? Because that you indeed explain why.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  13. #13
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by devarishi
    Okay, I compiled the program and the following warning message flagged:
    That warning is a false positive, since the assignment is correct. If you really want to avoid it, then change it to:
    Code:
    str = fgets(str, size, stream);
    if (str)
    Quote Originally Posted by devarishi
    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.
    Ah yes, I did not consider that input can be left in the buffer if the input provided is too long. You could just use ignoreUnreadInput() after each call to getLine().
    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

    Resolved!

    Hi,


    I think I fixed it. Thanks a lot to you all!

    Cheers!

    Dev.

    Quote Originally Posted by laserlight View Post
    That warning is a false positive, since the assignment is correct. If you really want to avoid it, then change it to:
    Code:
    str = fgets(str, size, stream);
    if (str)

    Ah yes, I did not consider that input can be left in the buffer if the input provided is too long. You could just use ignoreUnreadInput() after each call to getLine().

  15. #15
    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);
    //}

Popular pages Recent additions subscribe to a feed

Tags for this Thread