C Целочисленный безопасный ввод - PullRequest
0 голосов
/ 30 апреля 2018

Как я могу получить безопасный ввод целого числа (особенно положительного числа), используя scanf или gets? Я пробовал несколько решений, и у каждого решения были свои проблемы.

1. Использование getchar() для удаления строковых входов

int safeInput() {
    int input;
    scanf("%d", &input);
    while(getchar() != '\n');
    return input;
}

Этот метод эффективно обрабатывает строковые входы, однако, если вводятся такие строки, как 3a, значение input становится 3, что не является истинным дескриптором исключения.

2. Получение ввода в виде строки с последующим преобразованием в целочисленное значение.

int safeInput() {
    char[200] input, safe_input;
    gets(input);
    // I know about the security issue about gets - but it's not the point.

    int i = 0;
    while (1) {
        if (input[i] >= 48 && input[i] <= 57) safe_input[i] = input[i];
        else break;
        i++;
    }

    return atoi(safe_input);
}

У этого метода есть проблема, что он не может обработать, если была введена строка, которая имеет большую длину, чем выделено для input.

3. Что если определить строку с помощью указателя?

Я обеспокоен определением input указателем, как char *input;. Однако, как только я выполнил gets(input) (или scanf("%s", input)), это вызвало ошибку времени выполнения.


Итак, как правильно извлечь целочисленное значение из окна консоли, используя scanf или gets?

Ответы [ 2 ]

0 голосов
/ 30 апреля 2018

Ответ зависит от того, что именно вы подразумеваете под safe . Если вы хотите уловить любую возможную ошибку ввода, ваша единственная возможность - использовать функцию семейства strtol(), которая даже позволяет выполнять проверку диапазона. В моем руководстве для начинающих от scanf() я описываю его использование.

Вот код, адаптированный к вашим попыткам, с комментариями:

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

// return success as boolean (0, 1), on success write result through *number:
int safeInput(int *number)
{
    long a;
    char buf[1024]; // use 1KiB just to be sure

    if (!fgets(buf, 1024, stdin))
    {
        // reading input failed:
        return 0;
    }

    // have some input, convert it to integer:
    char *endptr;

    errno = 0; // reset error number
    a = strtol(buf, &endptr, 10);
    if (errno == ERANGE)
    {
        // out of range for a long
        return 0;
    }
    if (endptr == buf)
    {
        // no character was read
        return 0;
    }
    if (*endptr && *endptr != '\n')
    {
        // *endptr is neither end of string nor newline,
        // so we didn't convert the *whole* input
        return 0;
    }
    if (a > INT_MAX || a < INT_MIN)
    {
        // result will not fit in an int
        return 0;
    }

    // write result through the pointer passed
    *number = (int) a;
    return 1;
}
0 голосов
/ 30 апреля 2018

Сначала, если вы хотите безопасный ввод, не используйте gets. Сказать, что вы знаете о проблемах, не является истинным оправданием, когда вы можете использовать fgets. Затем, хитрость заключается в том, чтобы попытаться прочитать непустой символ после int: если вы никого не найдете, то после int в строке ничего нет.

int safeInput(int *input) {   // the return value is the indicator of failed read
    int c;
    char dummy[2];  // never forget the terminating null!
    if (scanf("%d%1s", input, dummy) == 1) return 1;
    // in case of error, skip anything up to end of line or end of file
    while (((c = fgetc(stdin)) != '\n') && (c != EOF));
    return 0;
}

Приятным моментом здесь является то, что когда scanf возвращает 1, %1s съел что-нибудь до конца строки, , включая завершающий 'n'. Но у этого есть главный недостаток: scanf заканчивается только в конце потока или после чтения одного дополнительного (непустого) символа. По этой причине ответ Феликса Пальмена проще и безопаснее.

...