1

So I tasked myself to write a function, that:

  1. overwrites an int with a safe value (not return gibberish if the user decides to input char-s or anything bigger by absolute value than (2^31-1)
  2. if input exceeds (2^31 - 1) (meaning if the user inputs 8 or more digits) the int must be overwritten with the upper value

Here is the code:

void getSafeIntWithBoundaries(int *dest, int lo, int hi, const char *message);
bool anyChars(const char *input, int len);

int main() {
    int x;
    getSafeIntWithBoundaries(&x, 1, 10, "Enter an integer between 0 and 10.");
    printf("x = %d\n", x);
    return 0;
}

void getSafeIntWithBoundaries(int * dest, int lo, int hi, const char * message) {
    char input[33];
    while (1) {
        puts(message);
        fgets(input, 33, stdin);
        int len = strlen(input);
        if (input[len - 1] == '\n') { input[len - 1] = '\0'; }
        --len;
        if (bool reset = anyChars(input, len)) {
            puts("Try again.");
            continue;
        }
        else {
            int ret;
            if (strcmp("2147483648", input) < 0) {
                *dest = hi;
                return;
            }
            sscanf(input, "%d", &ret);
            ret = ret > hi ? hi : ret;
            ret = ret < lo ? lo : ret;
            *dest = ret;
            break;
        }
    }
}

bool anyChars(const char * input, int len) {
    for(int i = 0; i < len; i++) {
        if (!isdigit(input[i])) {
            return true;
        }
    }
    return false;
}

A few more notes:

  • in getSafeIntWithBoundaries(...) I'm getting rid of the '\n', I'm changing it for a '\0', respectively decreasing int len; which holds the length of the input.
  • anyChars() checks whether the input contains any non digit char. If it does, then the user has to re-enter. One of the problems is however that in case of failure, message needs to be printed out only once. If I input something ridiculously long, message will be printed multiple times. I don't know how to fix this.
  • the strcmp() bit checks if the user entered a number bigger than (2^31 - 1). If the user has, then the int must be overwritten with the high value and the function needs to end. Problem is however, if the user enters a very long number, the target int will be overwritten with the low boundary. I don't know how to fix that either.
  • 2 ?s making sure the target int won't exceed its boundaries. I marked the parts that I can't figure out with bold, essentially that's the whole question.

    Suggestions on improving the code are welcomed as well.

Sergey Teryan
  • 61
  • 1
  • 7
  • What is your question? – Yunnosch Nov 22 '18 at 20:51
  • The stuff marked in bold. – Sergey Teryan Nov 22 '18 at 20:51
  • If each of that is a question then please focus on a single question. But in fact none of that seems a question. The whole post is more of a list of requirements which you want to have matched. Again, what single specific programming related question do you have? What is the one thing you want to change about your code? How does the code you show not act as desired? – Yunnosch Nov 22 '18 at 20:57
  • This doesn't seem to be C. – EOF Nov 22 '18 at 21:01
  • Well what does it seem like, @EOF? – Sergey Teryan Nov 22 '18 at 21:09
  • @SergeyTeryan I don't know, maybe C++? It certainly *is not* valid C. – EOF Nov 22 '18 at 21:11
  • @Yunnosch Everything is described clearly, I don't think there's anything left that's unclear. Also, this question doesn't have to be about 1 single issue, at least according to the rules. – Sergey Teryan Nov 22 '18 at 21:11
  • @EOF Which part of my code isn't valid C? – Sergey Teryan Nov 22 '18 at 21:20
  • @xing what is the issue with `strcmp` and `sscanf`? – Sergey Teryan Nov 22 '18 at 21:20
  • @SergeyTeryan `if (bool reset = anyChars(input, len))` is not valid C. – EOF Nov 22 '18 at 21:22
  • @EOF No, it is. I'm assigning `reset` the result of the function and then compare it whether it's `true`. – Sergey Teryan Nov 22 '18 at 21:24
  • @SergeyTeryan \*sigh\*, no, C does not allow declarations in `if()` statements. – EOF Nov 22 '18 at 21:25
  • @EOF *nods* you're correct, thanks for the lesson in pedantism – Sergey Teryan Nov 22 '18 at 21:36
  • @SergeyTeryan Given how sloppy, unorganized and confused your code is, you could *really* do with a lot *more* pedantism. If you can't even follow the basic rules of the language, how are you ever going to fix your sloppy thinking, the likes of which lead you to do a lexical string comparison in order to compare numbers represented by variable-length strings. – EOF Nov 22 '18 at 21:47
  • @EOF In case you didn't notice, the code was unfinished. The idea is to get it to work first, then optimize it second. Rules aren't always worthwhile, especially in a language like C. As for comparing numbers in the form of a string, a few people agree this isn't such a bad idea: https://stackoverflow.com/a/6389959/10692664 – Sergey Teryan Sep 11 '19 at 15:39

2 Answers2

1

Suggestions on improving the code are welcomed

Code fails many cases


Overflow UB

When the range exceed int, sscanf(input, "%d", &ret) is undefined behavior.

Long lines not consumed

When input is more than 32 characters (including the '\n), left over input remains.

Null character input

Input starting with a null character '\0' lead to undefined behavior with input[len - 1]

Non ASCII input

isdigit(input[i]) is undefined behavior when input[i] < 0.

Assumed ranged

Code uses int assuming it covers the range 2^31 - 1. C requires int to have a minimum range of [-32,767 ... 32,767].

Unclear goals

"if input exceeds (2^31 - 1) (meaning if the user inputs 8 or more digits)" --> What if input is `"0000000000000000000000000000000000001\n"? 35 zeros? It is in range yet exceeds 8 digits and exceed 33 character buffer.

End-of-file

puts("Try again."); does not make sense if input is closed. I'd expect int getSafeIntWithBoundaries() to return 1 on success, 0 on failure, EOF on end-of-file/input error.


Below is some untested code - will test later. I'll work on the message details later. It is certainty more than what one might think is needed to simply read an `int, but if you want robust code, it is work.

To read an entire line of input obliges reading until '\n' or EOF.

I'd tolerate leading and trailing spaces.

strtol() is good , but then the entire line needs to be read first. Recall valid input can have many leading spaces or zeros.

Do not overflow intmath- it is UB. Summing the value with negativesint` has greater range than the positive side.

Pre-C99 /,% has implementation defined behavior when the remainder is non-zero - so I avoided that.

#include <errno.h>
#include <limits.h>
#include <stdio.h>

#define INT_MIN_LS_DIGIT ((-(INT_MIN + 10)) % 10)
#define INT_MIN_DIV_10 ((INT_MIN + INT_MIN_LS_DIGIT)/10)

int getSafeIntWithBoundaries(int * dest, int lo, int hi, const char *message) {
  fputs(message, stdout);
  fflush(stdout);  // Insure data to sent out completely

  int ch;
  while (isspace((ch = fgetc(stdin))) && (ch != '\n')) {
    ;
  }
  bool positive = true;
  if (ch == '-' || ch == '+') {
    positive = ch == '+';
    ch = fgetc(stdin);
  }

  bool digit_found = false;
  bool overflow = false;

  int sum = 0;
  while (isdigit(ch)) {
    digit_found = true;
    int digit = ch = '0';
    // Detect possible overflow
    if (sum <= INT_MIN_DIV_10
        && (sum < INT_MIN_DIV_10 || digit > INT_MIN_LS_DIGIT)) {
      sum = INT_MIN;
      overflow = true;
    } else {
      sum = sum * 10 - digit;
    }
  }

  if (positive) {
    if (sum < -INT_MAX) {
      sum = INT_MAX;
      overflow = true;
    } else {
      sum = -sum;
    }
  }

  if (sum > hi) {
    sum = hi;
    overflow = true;
  }
  if (sum < lo) {
    sum = lo;
    overflow = true;
  }

  *dest = sum;

  while (isspace(ch) && ch != '\n') {
    ch = fgetc(stdin);
  }

  if (ch == EOF && iserror(stdin)) {
    return EOF; // Rare input error detected
  }

  if (!digit_found) {
    return 1; // or a "No digit found" error code
  }

  if (overflow) {
    errno = ERANGE;
    return 1; // or a "Overflow" error code
  }

   if (ch != '\n' && ch != EOF) {
    return 1; // or a "Extra trailing junk" error code
  }

  return 0;
}
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
0

strtol could be used to parse an integer from a string. It provides for overflow and the pointer to the last character allows for testing for valid terminating characters. This set the range to 0 and INT_MAX but any range from INT_MIN to INT_MAX could be used. The terminating character is nul but could be comma, semicolon or any appropriate character.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>

//inputs
// char *line : pointer to text to be parsed
// char **next : pointer to pointer to allow modification of caller's pointer
// char *term : pointer to characters to be considered terminators
// int *value : pointer to int to allow modification of caller's int
// int min : minimum value of range
// int max : maximum value of range
// returns : 0 failure or 1 success
int get_int_range ( char *line, char **next, char *delim, int *value, int min, int max)
{
    long int input = 0;
    char *end = NULL;//will point to end of parsed value

    if ( line == NULL) {
        return 0;
    }
    errno = 0;
    input = strtol ( line, &end, 10);//get the integer from the line. end will point to the end of the parsed value
    if ( end == line) {// nothing was parsed. no digits
        printf ( "input [%s] MUST be a number\n", line);
        return 0;// return failure
    }
    // *end is the character that end points to
    if ( *end != '\0' && !( delim && strchr ( delim, *end))) {// is *end '\0' or is *end in the set of term characters
        printf ( "problem with input: [%s] \n", line);
        return 0;
    }
    if ( ( errno == ERANGE && ( input == LONG_MAX || input == LONG_MIN))
    || ( errno != 0 && input == 0)){// parsing error from strtol
        perror ( "input");
        return 0;
    }
    if ( input < min || input > max) {// parsed value is outside of range
        printf ( "input out of range %d to %d\n", min, max);
        return 0;
    }

    if ( next != NULL) {// if next is NULL, caller did not want pointer to end of parsed value
        *next = end;// *next allows modification to caller's pointer
    }
    if ( value == NULL) {
        return 0;
    }
    *value = input;// *value allows modification to callers int
    return 1;// success
}

int main( int argc, char *argv[])
{
    char line[900] = {'\0'};
    int valid = 0;
    int number = 0;

    do {
        printf ( "Enter number or enter quit\n");
        fgets ( line, sizeof ( line), stdin);//read a line
        if ( strcmp ( line, "quit\n") == 0) {
            return 1;// if quit is entered, exit the program
        }
        line[strcspn ( line, "\n")] = '\0';//remove trailing newline
        valid = get_int_range ( line, NULL, "", &number, 0, INT_MAX);// call to parse a value
    } while ( !valid);// on failure, keep looping the above

    printf ( "input is %d\n", number);

    return 0;
}
xing
  • 2,125
  • 2
  • 14
  • 10