0

NB: to avoid misunderstanding, the code is aimed to handle input errors .. i'm asking for integers (no characters) but the user might enter characters by mistake, so how to avoid the following:

I know that scanf sucks but I have to use it for some reasons. So, the problem I'm facing right now is that if I'm scanning a single integer from the user as follow:

  scanf("%d",&c);

if the user enters a digit followed by a character, such as: 1k, it's treated as double input not a single one, and checking the return value for the scanf and using flushinput concept doesn't work here. To make my question clearer:

The user is prompted to enter a choice, and in case of invalid choice he'd be asked again (through a loop), so, if he enters for ex:

k, gives the message (it's an invalid input) once, and rescans

k2, gives the message (it's an invalid input) once, and rescans

2k, gives the message (it's an invalid input) TWICE then rescans.

Any hints to solve that issue? Thanks in advance !!

NB: I check the returned value of scanf as follow:

 if (scanf("%d",&confirm)!=1)
  {
       flushInput(); confirm=0;
  }

where:

   void flushInput()///prevents infinite loops resulted due to character input instead of integer
   {
    int c; //c ->absorbs the infinite characters resulted due to the entry of chars, using getchar()
    while((c = getchar()) != EOF && c != '\n');
  }
  • `%d` is for reading numbers(ints) why are you giving `char` type input – IrAM Jan 25 '21 at 18:34
  • for better answers provide minimal reproducible program – IrAM Jan 25 '21 at 18:36
  • If you want to read lines of input from a user, use `fgets`. scanf is for formatted input. – stark Jan 25 '21 at 18:36
  • 1
    When you say you "have to use scanf for some reason", I wonder what that really means? And is the code you're writing part of a learning exercise for school, or as production code for some company? The fact is that `scanf` is almost useless, and it is in fact *completely* useless for robust error checking, and it is also *completely* useless for user input in any kind of commercial or production code. – Steve Summit Jan 25 '21 at 19:03
  • 1
    If you want to do robust, error-checked input, you either can't do it at all using `scanf`, or it will take you five times as long (and deliver at most 90% of the error checking) as if you used proper, non-`scanf` methods. Sorry to say this, because it's not answering the question, and I know you said you need to use `scanf`, but it's really not a reasonable requirement. If you told me I *had* to do robust input and I *had* to use `scanf`, I would either (a) resign or (b) read whole lines using `%[^\n]`, then parse them sanely (that is, just as I would do if I were allowed to call `fgets`). – Steve Summit Jan 25 '21 at 19:05
  • 1
    If this is part of a class you're taking, and your instructor is insisting that you (a) implement robust error checking using (b) `scanf`, it is a pointless exercise from which you will learn nothing useful, except perhaps forbearance. Yes, in the real world, it's important for programs to do robust, error-checked input, but in the real world, nobody implements this using `scanf`. – Steve Summit Jan 25 '21 at 19:12
  • `scanf()` is shorthand for "**scan f**ormatted" input. You're not reading formatted input, obviously, since you routinely run into data your format string can't handle. ***`scanf()` is the wrong tool for this job***. – Andrew Henle Jan 25 '21 at 19:13
  • Everytthing @SteveSummit has posted is true, but he's actually understating just how bad `scanf()` is when processing unformatted input. When `scanf()` runs into an error, ***it leaves your input stream in an unknown state***. And an input stream in general can not be recovered. Once you've read data from a stream, it's gone forever. `scanf()` is so bad, if you print data with `printf()` you can't even be guaranteed to be able to read back that data with the same format string used to print it. – Andrew Henle Jan 25 '21 at 19:16
  • @dxiv That's great, but it's nearly incomprehensible, and it's harder to think about and to write (by a factor of two or three) than it needs to be. So what's the point? I can dig a ten-foot hole using a teaspoon, too, and I will if you hold a gun to my head, but if you're not holding a gun to my head, I won't. `scanf` is a plague on C programming, which costs beginning programmers uncounted millions of hours, and every single one of those hours is a waste, that could have been spent better. – Steve Summit Jan 25 '21 at 19:17
  • Sorry for the vitriolic ranting. If you'd like to learn how to do input using something other than `scanf`, see [What can I use for input conversion instead of scanf?](https://stackoverflow.com/questions/58403537) – Steve Summit Jan 25 '21 at 19:27
  • @Steve summit .. it's useful for characters and strings .. i'm dealing here with integers .. so what shall i use (or do) instead of scanf? – Mariam Atef Jan 25 '21 at 19:32
  • @MariamAtef See [What can I use for input conversion instead of scanf?](https://stackoverflow.com/questions/58403537) (And I'm guessing you haven't used `%c` much, or you wouldn't be saying "It's useful for characters." :-) ) – Steve Summit Jan 25 '21 at 19:34
  • @steve summit .. well pardon my ignonrance, i'm a first year student and not very expert .. so I was seeking for solutions or simple replies instead of mocking XD .. and yes .. my university is asking us to use scanf for such a task .. as we're begginers you know .. thank you, sir, anyway – Mariam Atef Jan 25 '21 at 20:07
  • @MariamAtef I beg your pardon: I am absolutely not mocking you. We were all beginners once. I am mocking your instructor, for forcing you to go through a useless exercise. Learning how to use `scanf` does not teach you anything you need to use for writing real C programs. It is only useful for beginning C programs, and it is not even very useful for those. It is like square training wheels for learning how to ride a bike. Avoid it if you can. – Steve Summit Jan 25 '21 at 20:11
  • If I could give you a "solution or simple reply", I would, but the fact is, I simply don't know a simple way to use `scanf` to cleanly reject non-numeric input following numeric. There was a comment earlier from someone named dxiv, but it's been deleted. – Steve Summit Jan 25 '21 at 20:20
  • *my university is asking us to use scanf for such a task .. as we're beginners you know* The implication is either that beginners need to (a) learn `scanf`, because it's such an important part of C, or (b) use `scanf`, because despite its faults it's the simplest way to get user input into a program. Now, in my opinion, (a) is completely false, and statement (b) *might* be true, for the first one or at most two weeks of your C programming career. After that, you'll have learned enough that the alternatives to `scanf`, that are slightly harder but infinitely more useful, will make sense. – Steve Summit Jan 25 '21 at 21:20
  • But, again, I'm not blaming you for trying to use `scanf`. I understand: it's the only thing your instructor has told you how to use so far. I am blaming your instructor for having such a shortsighted approach to your education. If you really want to learn C, my advice to you is to abandon `scanf` now, never use it again, and start learning about alternatives. If you only care about passing this class, then I'm afraid you're stuck with the pointless exercise of learning how to do hard things with `scanf`. – Steve Summit Jan 25 '21 at 21:22
  • @SteveSummit You must have a quick eye ;-) I removed that comment because it was incomplete, and I did not have the time to fix it then. I agree that `scanf` is not the first/best choice for the job, though it can still be an educational exercise to try. – dxiv Jan 26 '21 at 01:57

1 Answers1

2

The following addresses the question with OP's given constraint of using scanf. The preferred choice, however, would be to read entire lines, then parse the strings with safer functions like strtol which perform proper range checking. More about that at atoi vs atol vs strtol vs strtoul vs sscanf.

Using scanf, the sample code below reads one integer per line of input, while rejecting lines that contain any non-whitespace other than the integer itself.

#include <stdio.h>
#include <string.h>

int scanf_solo_int(int *p)
{
    // read integer
    int ret = scanf("%d", p);
    if(ret == EOF) return -1;

    // read rest of line
    char s[132 + 1];
    if(scanf("%132[^\n]", s) != 1) s[0] = '\0';
    scanf("%*1[\n]");

    if(ret == 1)
    {
        // check for extra non-whitespace
        if(s[strspn(s, " \t\n")] != '\0') ret = 2;
    }

    return ret;
}

int main()
{
    for(;;)
    {
        int n;
        switch(scanf_solo_int(&n))
        {
        case 1:  printf("ok: %d\n", n); continue;

        case 0:  printf("error: not a number\n"); continue;
        case 2:  printf("error: extra characters past %d\n", n); continue;

        case -1: printf("error: EOF\n"); break;
        default: printf("error: unexpected\n"); break;
        }
        break;
    }
    return 0;
}

Sample run:

input               output
-----------         ------------------------------
12                  ok: 12
 -34                ok: -34
5 6                 error: extra characters past 5
x y z               error: not a number
7x                  error: extra characters past 7
8 x                 error: extra characters past 8
9.0                 error: extra characters past 9
2147483647          ok: 2147483647    
-2147483648         ok: -2147483648
9876543210          ok: 1286608618
                    error: EOF

The last line is an example of uncaught integer overflow where scanf accepts the 9876543210 string as %d input, but truncates it mod 2^32 to 1286608618 without warning.

dxiv
  • 16,984
  • 2
  • 27
  • 49