Как ограничить ввод значения пользователем <INT_MAX в C - PullRequest
1 голос
/ 11 июля 2020
#include <stdio.h>
#include <stdlib.h>
#include<limits.h>

int getInt() {
    int n, check;
    char ch;
    do {
        fflush(stdin);
        printf("Enter positive integer n = ");
        check = scanf("%d%c", &n, &ch);
        if ((check == 2)&&(ch == '\n')) {
            if (n > INT_MAX) {
                printf("Invalid number\n");
            } else if (n < 0) {
                printf("n must > 0");
            } else {
                break;
            }
        } else printf("Invalid number\n");
    } while (1);
    return n;
}

int main(int argc, char** argv) {
    int n;
    n = getInt();
}

Мой код принимает вводимый пользователем номер в диапазоне от 0 до INT_MAX.

Когда я ввожу -1, программа отображает "n must > 0".

Но когда я ввожу '777777777777777777777777777777 '(> INT_MAX) программа по-прежнему отображает "n must > 0", а не 'Invalid number'.

Ответы [ 4 ]

2 голосов
/ 11 июля 2020

При вводе вне диапазона в scanf("%d%c", &n, &ch); поведение не определено.

Вместо этого прочтите строку ввода с помощью fgets(), затем качество с помощью strtol()

for (;;) {
  char buf[100];
  if (fgets(buf, sizeof buf, stdin) == NULL) {
    printf("No more input\n");
    return -1;
  }

  errno = 0;
  char *endptr;
  long val = strtol(buf, &endptr, 0); 

  // No numeric conversion done at all?
  // Numeric input outside long range?
  // Junk after the numeric text?
  if (buf == endptr || errno == ERANGE || *endptr != '\n') {
    printf("Invalid number\n");
    continue;
  }
  // Outside int range?
  if (val < INT_MIN || val > INT_MAX) {
    printf("Invalid number\n");
    continue;
  }
  if (val < 0) {
    printf("n must > 0");
    continue;
  }

  n = (int) val;
}

Я бы рекомендовал многократно используемую вспомогательную функцию int get_int(int *val).

0 голосов
/ 12 июля 2020

Простая проверка

int x;
...
if (x > INT_MAX) { ...

- это то, что никогда не сработает. Если значение int не может иметь значений выше INT_MAX, тогда невозможно, чтобы вы когда-либо могли сохранить x значение выше этого. Так что if всегда будет ложным, и компилятор, вероятно, удалит весь код, который вы поместили внутри этого блока if.

Обычные процедуры чтения, такие как scanf(), фактически ограничивают ввод до значения в диапазоне допустимых значений.

Но если вы хотите прочитать и построить число самостоятельно, вам необходимо принять во внимание такую ​​возможность и использовать более совершенные триггеры для остановки.

Например, проверка того, что число выше (INT_MAX - 9) / 10 сообщит вам, что если вы попытаетесь добавить к нему еще один di git, вы рискуете переполнить целое число при добавлении di git. Вы можете просто остановиться на этом и не продолжать читать, но если вы хотите прочитать еще один di git (кто знает, это может быть 0, и это не повредит, вам нужно будет что-то проверить например,

int digit;
int the_integer = 0;
while (  (digit = fgetchar(stdin)) != EOF 
       && isdigit(digit) 
       && the_integer <= (INT_MAX - (digit - '0'))) 
{
    the_integer *= 10;
    the_integer += digit;
} /* while */
if (digit != EOF && isdigit(digit)) {
    /* x > (INT_MAX - (digit - '0')) */
    /* this is necessary, as if you don't do it, you'll lose
     * the next character to read. */
    unput(digit, stdin);
}

Таким образом вы проверите число x, прежде чем умножить его на 10 и прибавить значение digit - '0'.

0 голосов
/ 11 июля 2020

Как указано в разделе комментариев, нет смысла тестировать, если n > INT_MAX, поскольку n имеет тип int и, следовательно, по определению не может представлять какое-либо значение больше INT_MAX.

Простым и гибким способом проверки размера числа, предоставленного при вводе пользователем, было бы прочитать его как строку и подсчитать количество цифр с помощью функции strlen и, возможно, также isdigit для подсчета фактических цифр.

Однако при работе с потенциально большими числовыми значениями, вводимыми пользователем, обычно лучше сначала прочитать этот ввод в тип данных большего размера чем int, и проверьте свой диапазон с этим большим типом данных. Убедившись, что число находится в желаемом диапазоне, вы можете преобразовать его в меньший тип данных int. Хотя стандарт ISO C не гарантирует, что long long больше int, это разумное предположение, и это относится ко всем компиляторам, о которых я знаю. Однако на некоторых платформах (включая 64-битную Windows) тип данных long имеет тот же размер, что и int, поэтому long нельзя надежно использовать для этого на всех платформах.

Вероятно, самым простым решением вашей проблемы было бы использование функции strtol или strtoll. Использование этих функций имеет то преимущество, что вам не нужен больший тип данных, и он явно сообщает вам, если число выходит за пределы допустимого диапазона, путем установки errno на ERANGE. Однако эти функции поддерживают только типы данных long и long long, но не int. Следовательно, если вы хотите использовать int, вам придется вручную проверять диапазон перед его преобразованием.

0 голосов
/ 11 июля 2020

Сначала вам нужно понять, как переменные хранят данные.

В 64-битной архитектуре тип int имеет 4 байта (либо тип C long, независимо от архитектуры), поэтому может хранить следующие значения:

00000000 00000000 00000000 00000000 = 0 (десятичное значение)

01111111 11111111 11111111 11111111 = 2 147 483 647 (десятичное значение)

11111111 11111111 11111111 11111111 = 4 294 967 294 (без знака) 1009 *

11111111 11111111 11111111 11111111 = -1 (десятичное значение со знаком)

Обратите внимание, что целочисленные типы могут использовать старший значащий бит (MSB) для представления сигнала (от 0 до положительного, от 1 до отрицательного), используя a modular aritmeti c.

Подробнее о целочисленном сигнале: https://en.wikipedia.org/wiki/Two%27s_complement

Итак, чтобы хранить десятичные данные выше INT_MAX, вам нужно больше байтов, чем у вас есть тип int. Хороший способ, совместимый с 64-битной архитектурой, использует тип long long.

Тип long long использует 8 байтов, поэтому может хранить значение выше INT_MAX.

Вам нужно будет объявить: long long n;

И используйте scanf () следующим образом: scanf("%lld%c", &n, &ch);

Ваш fflu sh (stdin) должен быть после scanf (), потому что если ваше приложение нарушит l oop после того, как scanf () и до того, как достигли инструкции fflu sh (), могут возникнуть проблемы при дальнейшей обработке ввода. Примерно так:

check = scanf("%lld%c", &n, &ch);
fflush(stdin);

Однако некоторые разработчики не одобряют использование fflu sh () в стандартном вводе, поэтому это альтернатива (немного более сложная) с использованием getch (), принимающего только числа и конвертирующего char * в long long с использованием strtoll ():

        char c = 0;
        char* input_number = malloc(32);
        int accepted_chars = 0;
        memset(input_number, 0, 32);
        while(c != '\r'){ //loop until user press ENTER
            c = getch();
            //receive numbers keys pressed
            if(c >= '0' && c <= '9'){
                *(input_number + accepted_chars) = c;
                accepted_chars ++;
                
                printf("%c", c);
            }
            
            //receive backspace key pressed
            if(c == 8){
                if(accepted_chars > 0){ //don't do nothing if there is nothing to clear
                    accepted_chars --;
                    *(input_number + accepted_chars) = 0;
                    printf("\b");
                    printf(" ");
                    printf("\b");
                }
            }
        }
        printf("\n");
        char* endptr;
        n = strtoll(input_number, &endptr, 10); //convert string in base 10 (decimal) long long type
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...