1

I'm new to C programming and i find prompting for user input quite a challenge for beginners like me.

I'm just writing a simple C code to continuously prompt for user input until the user enters a negative number which stops the program. In a way, i want the format of prompting user input similar to the way we used to do in python where:

Enter a number: (userinput a number here and press enter)

and the output will be:

The number you entered is 10 (for example)

which doesn't yet stop the loop and prompts for another number since no negative number is entered.

Enter a number: (userinput another number which is negative)

Output:

The number you entered is -10 (for example)

and the program stops.

I tried writing in C:

#include <stdio.h>

int value = 0;
while (value >= 0) {
    do {
       printf("Enter a number: ");
       scanf("%d",&value);
       printf("The number you entered is %d", &value);

    }

but i can't seem to get the "Enter a number:" statement to display first when i run the program as it immediately request for user input at the start without the prompting message displayed until when i enter an integer, the prompting message displays which is the opposite of what i wanted. Would appreciate some help on this.

1
  • 3
    You must always check the return of scanf for EOF a matching or input failure. Further, you don't need do inside your while simply while (value >= 0) { printf .... } And... you printf with value, not &value... Listen to what your compiler is saying. Enable warnings (-Wall -Wextra with gcc/clang or /W3 for VS) and do not accept code until it compiles without a single warning. Commented Sep 24, 2018 at 2:04

1 Answer 1

15

One of the biggest problems faced by new C programmers is handling user input correctly, especially when using the scanf family of functions. Why? When using scanf you must account for all characters left in the input buffer (stdin here) in the event of a matching failure. Why? When a matching failure occurs, scanf stops processing characters at the point of failure, and the character(s) that caused the failure remain in your input buffer unread, just waiting to bite you on your next attempted input.

Further complicating the issue is how the difference scanf conversion specifiers treat whitespace. Your numeric input specifiers and %s will consume leading whitespace, while the remainder of the conversion specifiers don't. That means if you are taking input with other than a numeric or %s conversion specifier -- you must account for and remove the trailing '\n' before attempting the next character or character class conversion.

That said, whenever you use scanf, it is up to you to check the return so you can determine whether the user canceled input with a manually generated EOF of whether a matching or input failure occurred.

At the bare-minimum, you must check the return and handle any return indicating less than the number of expected conversions took place. In your case, with one conversion to int, you must check that the return is 1 before making use of value. Otherwise you can easily invoke Undefined Behavior. Now just checking whether all conversions occurred does not allow you to discriminate between EOF or matching failure - leaving you without the information needed to proceed making the best you can do is to exit on invalid input, e.g.

#include <stdio.h>

int main (void) {

    int value = 0;

    while (value >= 0) {
        printf("Enter a number: ");
        if (scanf("%d",&value) != 1) {
            fputs ("error: invalid input\n", stderr);
            return 1;
        }
        printf("The number you entered is %d\n", value);
    }

    return 0;
}

Example Use/Output

$ ./bin/scanfpos2
Enter a number: 1
The number you entered is 1
Enter a number: 10
The number you entered is 10
Enter a number: foo
error: invalid input

(note: on entry of an invalid integer, all the program can do is end, you do not know whether the input was canceled or whether a matching failure occurred which would allow you to take appropriate action to continue by emptying the input buffer if the failure was a matching failure.)

The proper way to handle input with scanf is to cover all potential error conditions and to gracefully respond to a matching failure by clearing the input buffer of the offending characters allowing you to continue with input. It helps to have a small helper-function to clear stdin rather than having to repeatedly include a clearing loop every where scanf is used in your code. A short example would be:

/** remove all characters that remain in stdin */
void empty_stdin (void)
{
    int c = getchar();

    while (c != '\n' && c != EOF)
        c = getchar();
}

Which simply reads all characters from stdin until the '\n' or EOF is encountered. Combining that with an expanded check of the return of scanf will allow you to handle a matching failure gracefully, e.g.

#include <stdio.h>

/** remove all characters that remain in stdin */
void empty_stdin (void)
{
    int c = getchar();

    while (c != '\n' && c != EOF)
        c = getchar();
}

int main (void) {

    int value = 0;

    while (value >= 0) {
        int rtn;    /* variable to store return on scanf */

        printf ("Enter a number: ");
        rtn = scanf ("%d",&value);
        if (rtn == EOF) {   /* handle EOF */
            fputs ("user canceled input.\n", stderr);
            break;
        }
        else if (rtn == 0) {    /* handle matching/input failure */
            fputs ("error: invalid input\n", stderr);
            empty_stdin();
        }
        else    /* good input - output value */
            printf("The number you entered is %d\n", value);
    }

    return 0;
}

Example Use/Output

$ ./bin/scanfpos2
Enter a number: 1
The number you entered is 1
Enter a number: 10
The number you entered is 10
Enter a number: foo
error: invalid input
Enter a number: -1
The number you entered is -1

Here if a non-integer like foo is entered, it is caught, the characters removed from stdin and the loop prompts again for input.

Look things over. C is not python. Python hides much of the implementation details from you to essentially protect you from instances just as this, but it has its drawbacks as well. With C, nothing is hidden from you. You are given free reign to write to memory you don't own, repeatedly attempt to read form an input buffer after a conversion failure, etc.. You are responsible for the details.

Lastly, all of this is the primary reason taking input with fgets or POSIX getline is recommended for new users. With a sufficiently sized buffer (don't skimp on size), fgets will read a line at a time from the input buffer, preventing offending characters remaining just waiting to bite you again. getline will allocate a buffer of sufficient size no matter how long the line is -- but you are responsible for freeing the memory when you are done with it. Investigate both as alternatives to using scanf. You can always call sscanf on the buffer holding the line after it is read to parse numeric values from it.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.