Function that prompts user for integer value and checks for valid input

前端 未结 5 1755
一个人的身影
一个人的身影 2020-12-20 09:07

I currently am stuck on a small part of an assignment I need to do. One requirement of the assignment is

\"Call a function that prompts the user for

5条回答
  •  既然无缘
    2020-12-20 09:25

    Function that prompts user for integer value and checks for valid input

    If users only entered valid integer text on a line-by-line basis, then code is easy:

    // Overly idealized case
    fputs(prompt, stdout);
    char buf[50];
    fgets(buf, sizeof buf, stdin);
    int i = atoi(buf);
    

    But users are good, bad and ugly and **it happens. If code wants to read a line, parse it for an in-range int, and detect a host of problems, below is code that vets many of the typical issues of bogus and hostile input.

    I especially interested in detecting overly long input as hostile and so invalid as a prudent design against hackers. As below, little reason to allow valid input for a 32-bit int with more than 20 characters. This rational deserve a deeper explanation.

    • End-of-file
    • Input stream error
    • Overflow
    • No leading numeric test
    • Trailing non-numeric text
    • Excessive long line

    First a line of input is read with fgets() and then various int validation tests applied. If fgets() did not read the whole line, the rest is then read.

    #include 
    #include 
    
    // Max number of `char` to print an `int` is about log10(int_bit_width)
    // https://stackoverflow.com/a/44028031/2410359
    #define LOG10_2_N 28
    #define LOG10_2_D 93
    #define INT_DEC_TEXT (1 /*sign*/ + (CHAR_BIT*sizeof(int)-1)*LOG10_2_N/LOG10_2_D + 1)
    
    // Read a line and parse an integer
    // Return:
    //   1: Success
    //   0: Failure
    //   EOF: End-of-file or stream input error
    int my_get_int(int *i) {
      // Make room for twice the expected need. This allows for some
      // leading/trailing spaces, leading zeros, etc.
      //           int        \n  \0
      char buf[(INT_DEC_TEXT + 1 + 1) * 2];
      if (fgets(buf, sizeof buf, stdin) == NULL) {
        *i = 0;
        return EOF; // Input is either at end-of-file or a rare input error.
      }
      int success = 1;
    
      char *endptr;
      errno = 0;
      long value = strtol(buf, &endptr, 10);
    
    
      // When `int` is narrower than `long`, add these tests
    #if LONG_MIN < INT_MIN || LONG_MAX > INT_MAX
      if (value < INT_MIN) {
        value = INT_MIN;
        errno = ERANGE;
      } else if (value > INT_MAX) {
        value = INT_MAX;
        errno = ERANGE;
      }
    #endif
      *i = (int) value;
      if (errno == ERANGE) {
        success = 0;  // Overflow
      }
    
      if (buf == endptr) {
        success = 0; // No conversion
      }
    
      // Tolerate trailing white-space
      // Proper use of `is...()` obliges a `char` get converted to `unsigned char`.
      while (isspace((unsigned char ) *endptr)) {
        endptr++;
      }
    
      // Check for trailing non-white-space
      if (*endptr) {
        success = 0; // Extra junk
        while (*endptr) {  // quietly get rest of buffer
          endptr++;
        }
      }
    
      // Was the entire line read?
      // Was the null character at the buffer end and the prior wasn't \n?
      const size_t last_index = sizeof buf / sizeof buf[0] - 1;
      if (endptr == &buf[last_index] && buf[last_index - 1] != '\n') {
        // Input is hostile as it is excessively long.
        success = 0;  // Line too long
        // Consume text up to end-of-line
        int ch;
        while ((ch = fgetc(stdin)) != '\n' && ch != EOF) {
          ;
        }
      }
    
      return success;
    }
    

    Sample usage

         puts("Enter a value for a: ", stdout);
         fflush(stdout);  // Insure output is seen before input.
         int a;
         if (my_get_int(&a) == 1) {
           printf("a:%d\n", a); 
         }
    

提交回复
热议问题