Home > Back-end >  How do you make a safe input of an int with retry logic?
How do you make a safe input of an int with retry logic?

Time:12-19

I'm trying to write to a program that takes in an integer from the terminal and safely checks if it actually is an int and if it is equal to or in between 0 and 5. The code should also have retry logic.

This is the code that I have written:

#include <stdio.h>
#include <stdlib.h>


int main(){
    int input;
    char i[3];
    char *p;

    while(fgets(i, (sizeof(i)),stdin)){
        input=strtol(i,&p,10);
        if(input<0 || input>5 || p==i || (*p)!='\n'){
            printf("please enter a integer larger or equal to 0 or smaller or equal to 5\n");   
        }
        else{
            printf("%d",input);
            break;
        }
    }
}

The problem I have encountered is that if I input more characters than fgets() reads such as "aaaaa", I get the output:

please enter a integer larger or equal to 0 or smaller or equal to 5
please enter a integer larger or equal to 0 or smaller or equal to 5
please enter a integer larger or equal to 0 or smaller or equal to 5 

I'm assuming the issue is that the characters that fgets does not read get left in the buffer and get read on subsequent iterations which trigger the if statement and therefore the repeated output. I'm assuming the solution is the somehow clear the buffer but from what I've read fflush() isn't allowed. Is there a simple way to clear the buffer? And how would you go about doing it, if that actually is the issue to the problem?

CodePudding user response:

A simplistic approach starts with a bigger buffer.

#define BUF_N 100
char buf[BUF_N];

Then a not so simple approach that tests for all sort of troubles. I suspect simplifications exists, but at least this one handles many corners and is illustrative.

For simplicity, if the input line is excessively long, let us just say the input line is bad, regardless of text read.

  #define BUF_N 100
  char buf[BUF_N];

  while (fgets(buf, sizeof buf, stdin)) {
    // If entire buffer filled, the line may be longer, read it.
    if (strlen(buf) == BUF_N - 1 && buf[BUF_N - 2] != '\n') {
      printf("Excessive input\n");
      // Consume rest of the line 
      int ch;
      while ((ch = fgetc(stdin)) != '\n') {
        if (ch == EOF) {
          return EOF;
        }
      }
      continue;
    }

    char *p;
    long input = strtol(buf, &p, 10); // use long

    if (buf == p) {
      printf("Non-numeric input\n");
      continue;
    }

    if (input < 0 || input > 5) {
      printf("Out of range %ld\n", input);
      continue;
    }

    // Look for trailing junk
    while (isspace(*(unsigned char* )p)) {
      p  ;
    }
    if (*p) {
      printf("Trailing junk\n");
      continue;
    }

    return (int) input;
  }
  return EOF;
}

The above is not so tolerant of reading null characters.

CodePudding user response:

Calling the function fgets with a buffer size of 3 will read at most one character, in addition to the newline character and the terminating null character. If the input is supposed to be a number between 0 and 5, then this behavior is acceptable, but you should be aware that your program will be unable to read input such as 05, even though it fulfills the range requirement.

If you want to allow input such as 05, then you should increase the buffer size.

You can easily detect whether the user entered more characters into the line than fit into the buffer. Since the function fgets also reads and stores the newline character into the buffer, you can check for the existance of this newline character, for example using the function strchr. If you determine that fgets did not read a newline character, then this means that the input was too long to fit into the buffer. In that case, you probably want to reject the input and discard the remainder of the input, so that it does not get read by the next call to fgets. This is what was happening in your program, which caused the same line to be processed multiple times.

One way of discarding the remainder of the input is to call getchar, which reads one character at a time. You should repeat this until you encounter the newline character, which means that the end of the line has been reached.

This is how the code could look:

if ( strchr( i, '\n' ) == NULL && !feof(stdin) && !ferror(stdin) )
{
    int c;

    //print error message
    printf( "Line input was too long to fit into buffer!\n" );

    //discard remainder of line
    do
    {
        c = getchar();

        if ( c == EOF )
        {
            fprintf( stderr, "unrecoverable input error!\n" );
            exit( EXIT_FAILURE );
        }

    } while ( c != '\n' );

    continue;
}

Note that you must additionally #include <string.h> in order to use the function strchr.

  •  Tags:  
  • c
  • Related