1

I saw on Stack Overflow that many similar questions are repeated and they are related to the reading of one input data item from stdin and check its validity.

The data could be integer "%d", double "%f", string "%s", unsigned int "%u"...

And I want to develop a common macro which could be used for the majority of these questions.

Example 1 of questions

The OP could ask:

  • scan the input data
  • the data should be integer so 11a, aaa, aa44,... inputs are not allowed. Only integer followed with a white space (refer to isspace()) is allowed
  • other conditions could be present in the question like: input integer should be >3 and <15

Example 2 of questions

The OP could ask:

  • scan the input data
  • the data should be double so 11.2a, aaa, aa44.3,... inputs are not allowed. Only double followed with a white space (refer to isspace()) is allowed
  • other conditions could be present in the question like: input double should be >3.2 and <15.0

Is it possible to develop a common macro like

#define SCAN_ONEENTRY_WITHCHECK(FORM,X,COND)
// FORM: format of the input data like "%d", "%lf", "%s"
// X: address where to store the input data
// COND: condition to add in the check of the input data

....

// example of calling the macro in the main()

int a;
SCAN_ONEENTRY_WITHCHECK("%d", &a,(a>3 && a<15))

The macro should scan the data and IF one of the following criteria is not true then print a message to the user asking him to enter again. and repeat that until the user enters valid data?

Criteria:

  • input data type should be the same indicated by format
  • input data should be followed by white space indicated in the isspace()
  • input data should be valid the condition COND
MOHAMED
  • 41,599
  • 58
  • 163
  • 268
  • 4
    This is a well-intentioned post, but it's not a great fit for the Q&A format of this site. It seems more like a blog post ("let me share some code I've written") than a legitimate question. – John Kugelman Apr 03 '13 at 16:40
  • I know this question could looks like a blog post. But there is useful informations which could be useful for the SO users especially that similar questions is repeated many times in the SO. and I want to share it with the SO users. so I choose to present it as Q&A in the SO – MOHAMED Apr 03 '13 at 16:45
  • @JohnKugelman `I saw in the stackoverflow that many similars questions are repeated and they are related to the reading of a one input data from stdin (with scanf()) and check its validity.` and I want to provide common solution for theses question. this could be useful for such questions in the future. – MOHAMED Apr 03 '13 at 16:51
  • We can refer to this topic for such questions in the future – MOHAMED Apr 03 '13 at 16:52
  • @JohnKugelman I provided a solution with C and any solution for C++ is welcome. I want to keep the C++ tag – MOHAMED Apr 03 '13 at 16:57
  • @JohnKugelman you are right. when I write this question it was just for C and I shared with C++ tag in order to be seen by c++ people. any way now the idea is to get an answer for the c++ so May be I have to change a little bit my question – MOHAMED Apr 03 '13 at 17:05
  • @JohnKugelman done I removed the scanf from the question. the question now is more general – MOHAMED Apr 03 '13 at 17:07
  • Self-answered questions are legitimate. – Jonathan Leffler Apr 03 '13 at 20:33
  • @JonathanLeffler I know I want to develop a common solution for many questions I saw in the SO and I want to share it with users. And a soluition for c++ is welcome waiting a contribution for c++ – MOHAMED Apr 03 '13 at 20:36
  • @JohnKugelman [It’s OK to Ask and Answer Your Own Questions](http://blog.stackoverflow.com/2011/07/its-ok-to-ask-and-answer-your-own-questions/) – MOHAMED May 07 '13 at 16:45

1 Answers1

1

A quick answer for this question will be

#define SCAN_ONEENTRY_WITHCHECK(FORM,X,COND)\
   while(scanf(" "FORM, X)<1 || !(COND))
      printf("Invalid input, enter again: ");

calling the above macro in the code with the following way

int a; 
SCAN_ONEENTRY_WITHCHECK("%d", &a, (a>3 && a<15))

is equivalent to this

int a;
while(scanf(" %d", &a)<1 || !(a>3 && a<15))
    printf("Invalid input, enter again: ");

but the above answer is wrong because if the user enter for example aaa as input then the above code will result an infinite loop because the stdin is not cleaned because the aaa input is not catched by the scanf(" %d", &a). So we have to add something that clean the stdin if the user enter a such input. adding scanf("%*[^\n]") could be a solution for that. and the above code will be

int a;
while(scanf(" %d", &a)<1 || !(a>3 && a<15)) {
    scanf("%*[^\n]"); // clean stdin
    printf("Invalid input, enter again: ");
}

but the above code still contains a limitation. For example If the user enter a valid integer (20) and the integr does not respect the condition (a>3 && a<15). In this case we do not have to clean the stdin because it's already cleaned otherwise the user will be asked for input data for 2 times . tis problem coud solved with the following solution:

int a;
int c;
while((c=(scanf(" %d", &a)<1)) || !(a>3 && a<15)) {
    if (c) scanf("%*[^\n]"); // clean stdin
    printf("Invalid input, enter again: ");
}

The above code does not respect the criteria input data should be followed by white space for example if the user enter 10abc as input then the above code will catch 10 as input integer and it will clean the rest abc. this problem could be solved if we check that the next charachter is a white space (with isspace() function) nothing else

int a;
char tmp;
int c;
while((c=(scanf(" %d%c", &a, &tmp)!=2 || !isspace(tmp))) || !(a>3 && a<15)) {
    if (c) scanf("%*[^\n]"); // clean stdin
    printf("Invalid input, enter again: ");
}

And now if we want to go back to the macro. the macro code will be:

#define SCAN_ONEENTRY_WITHCHECK(FORM,X,COND) \
do {\
    char tmp;\
    int c;\
    while ((c=(scanf(" "FORM"%c", X, &tmp)!=2 || !isspace(tmp)))\
            || !(COND)) {\
        if (c) scanf("%*[^\n]");\
        printf("Invalid input, please enter again: ");\
    }\
} while(0)

Adding do {...} while(0) in the macro could be explained by this link

The above macro could be writen in another way

#define SCAN_ONEENTRY_WITHCHECK(FORM,X,COND) \
do {\
    char tmp;\
    while(((scanf(" "FORM"%c",X,&tmp)!=2 || !isspace(tmp)) && !scanf("%*[^\n]"))\
            || !(COND)) {\
        printf("Invalid input, please enter again: ");\
    }\
} while(0)

Examples of using the macro:

int main()

{
    int decision;
    double q;
    char buf[32];

    printf("Input data, valid choice 1 or 0: ");
    SCAN_ONEENTRY_WITHCHECK("%d",&decision,(decision==0 || decision==1));
    printf("You have entered good input : %d\n", decision);

    printf("Input unsigned double: ");
    SCAN_ONEENTRY_WITHCHECK("%lf",&q, (q == (unsigned int) q));
    printf("You have entered good input : %lf\n", q);

    printf("Input name: ");
    SCAN_ONEENTRY_WITHCHECK("%s",buf, (strcmp(buf,"kallel")==0));
    printf("You have entered good input : %s\n", buf);

    printf("Input data should be valid integer: ");
    SCAN_ONEENTRY_WITHCHECK("%d",&decision,1);
    // COND is 1 ==> we do not have any check in the input integer
    printf("You have entered good input : %d\n", decision);
}

Execution

$ ./test
Input data, valid choice 1 or 0: 4
Invalid input, please enter again: a4
Invalid input, please enter again: a1
Invalid input, please enter again: 1a
Invalid input, please enter again: 1
You have entered good input : 1
Input unsigned double: 2.3
Invalid input, please enter again: a.0a
Invalid input, please enter again: 2.0a
Invalid input, please enter again: a2.0
Invalid input, please enter again: 2.0
You have entered good input : 2.000000
Input name: an
Invalid input, please enter again: anyad
Invalid input, please enter again: adny
Invalid input, please enter again: any
You have entered good input : any
Input data should be valid integer: 2.5
Invalid input, please enter again: -454f
Invalid input, please enter again: -454
You have entered good input : -454
Community
  • 1
  • 1
MOHAMED
  • 41,599
  • 58
  • 163
  • 268
  • If the code encounters EOF, the loop is going to keep on going, and going, and going ... eat your heart out, Energizer Bunny. You also don't provide feedback on what the correct input is, so the frustrated user can't tell what's wrong with their answer. I'd suggest that actual functions rather than macros would be better. Of course, there's then the problem that you're using `scanf()` at all; personally, I'd not do that. – Jonathan Leffler Apr 03 '13 at 20:32
  • @JonathanLeffler with the above code there is no infinite loop even if you input EOF with `CTRL+D`. You can checke it yourself – MOHAMED May 07 '13 at 16:22
  • I did. It's there - the infinite loop is there. If I type ^D at a Linux terminal, I get prompted with 'Invalid input, please enter again:` repeatedly every other time I hit ^D. If I redirect the input from `/dev/null`, I get screens full of the prompt. The code I tested uses your last macro and `#include #include int main(void) { int i; SCAN_ONEENTRY_WITHCHECK("%d", &i, (i > 0 && i < 10)); printf("Read: i = %d\n", i); return 0; }`. I'm sorry, but as things stand, I cannot approve of or up-vote your suggested macro. It needs to be a function; it needs to handle EOF. – Jonathan Leffler May 07 '13 at 16:54
  • This does not happen for me I even tried your code but I did not get this behavior I m using `gcc version 4.5.2 (Ubuntu/Linaro 4.5.2-8ubuntu4)` – MOHAMED May 07 '13 at 17:02
  • @JonathanLeffler this only happen if I only keep pressing `CTRL+D` – MOHAMED May 07 '13 at 17:03
  • @JonathanLeffler Even if I have to handle the EOF, I do not need to write as a function I could keep it as a macro. Why I have to write it as a function? – MOHAMED May 07 '13 at 17:06
  • That's odd; I was testing on an Ubuntu derivative with GCC 4.6.3-1ubuntu5. _[...additional comment arrives...]_ Well, yes, when I type EOF at a program, I mean EOF and I don't want to keep typing at it. Try running it with the input coming from `/dev/null`; it's even better at generating EOF than ^D is. – Jonathan Leffler May 07 '13 at 17:06
  • Because macros are a menace and functions are your friends...and you need to return a value from it to indicate whether it was successful or not. Yes, you can do it as a macro - but it won't be getting an up-vote from me while it is a macro because it is too big to be a sensible macro. Also, you should probably limit the number of times a user gets to make an error; there should be an upper bound on the number of times the user gets prompted before the code gives up in disgust (meaning that the user was confused by the program, so the program is badly written). – Jonathan Leffler May 07 '13 at 17:07
  • For a moderately reasonable function, see `read_validate_number()` in [](http://stackoverflow.com/questions/16350807/what-is-the-reason-for-error-while-returning-a-structure-in-this-c-program/16371915#16371915). It's not perfect; the prompt tag is appropriately minimal for the case on hand; a better solution would replace more of the prompt with function call information. The hard-wired error count of 3 is not ideal either; it should be a parameter, at which point you start thinking 'maybe I need a struct to describe the parameters to this function'. – Jonathan Leffler May 07 '13 at 17:15
  • @JonathanLeffler when I run my program with input coming from /dev/null `./test – MOHAMED May 07 '13 at 17:15
  • Try running `cat < /dev/null`; it doesn't keep running. Nor should any program that is basically echoing its input. – Jonathan Leffler May 07 '13 at 17:16
  • @JonathanLeffler but pressing only 1 time the `CTRL+D` will not cause an infinite loop in your platform. is that correct? – MOHAMED May 07 '13 at 17:19
  • For input from a terminal; correct (one entry of ^D does not cause an infinite loop). For input from a file, EOF causes an infinite loop; `/dev/null` is the extreme case of a file which has no data. Code that only works interactively is NBG. – Jonathan Leffler May 07 '13 at 17:25
  • @JonathanLeffler I think for this specific case it should be fixed from the main of the program and not by my macro. and some fix could be common for all kind of input reading codes. we can add at the beginning of the main the following code: `if(scanf("%*1[ ]")==EOF) exit(0);` this will solve the problem of `./test – MOHAMED May 07 '13 at 17:45
  • How can the main program do anything when all it does is call your macro? – Jonathan Leffler May 07 '13 at 17:47
  • You are right. If my program contains 3 scan and if I use input file containing only 2 values then I will get an infinite loop. and adding `if(scanf("%*1[ ]")==EOF) exit(0);` will not solve the problem. I have to fix this case. I will update my answer with that. thank you a lot for this remark – MOHAMED May 07 '13 at 17:53