Недостатки scanf

Я хочу знать недостатки scanf().

Во многих сайтах я считал то использование scanf мог бы вызвать переполнение буфера. Какова причина этого? Есть ли любые другие недостатки с scanf?

58
задан Spikatrix 20 February 2015 в 15:17
поделиться

6 ответов

Проблемы со scanf (как минимум) следующие:

  • с использованием % s , чтобы получить строку от пользователя, что приводит к возможности того, что строка может быть длиннее, чем ваш буфер, вызывая переполнение.
  • возможность неудачного сканирования, оставляющего указатель файла в неопределенном месте.

Я предпочитаю использовать fgets для чтения целых строк, чтобы можно было ограничить объем считываемых данных. Если у вас есть буфер размером 1 КБ, и вы считываете в него строку с помощью fgets , вы можете определить, была ли строка слишком длинной, по тому факту, что нет завершающего символа новой строки (последняя строка файла без новой строки несмотря на это).

Затем вы можете пожаловаться пользователю или выделить больше места для остальной части строки (при необходимости, непрерывно, пока у вас не будет достаточно места). В любом случае нет риска переполнения буфера.

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

Вот фрагмент кода, который я часто использую, чтобы гарантировать отсутствие переполнения буфера при запросе информации у пользователя.

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

#include <stdio.h>
#include <string.h>

#define OK         0
#define NO_INPUT   1
#define TOO_LONG   2
#define SMALL_BUFF 3
static int getLine (char *prmpt, char *buff, size_t sz) {
    int ch, extra;

    // Size zero or one cannot store enough, so don't even
    // try - we need space for at least newline and terminator.
    if (sz < 2)
        return SMALL_BUFF;

    // Output prompt.
    if (prmpt != NULL) {
        printf ("%s", prmpt);
        fflush (stdout);
    }

    // Get line with buffer overrun protection.
    if (fgets (buff, sz, stdin) == NULL)
        return NO_INPUT;

    // If it was too long, there'll be no newline. In that case, we flush
    // to end of line so that excess doesn't affect the next call.
    size_t lastPos = strlen(buff) - 1;
    if (buff[lastPos] != '\n') {
        extra = 0;
        while (((ch = getchar()) != '\n') && (ch != EOF))
            extra = 1;
        return (extra == 1) ? TOO_LONG : OK;
    }

    // Otherwise remove newline and give string back to caller.
    buff[lastPos] = '\0';
    return OK;
}

И тестовый драйвер для него:

// Test program for getLine().

int main (void) {
    int rc;
    char buff[10];

    rc = getLine ("Enter string> ", buff, sizeof(buff));
    if (rc == NO_INPUT) {
        // Extra NL since my system doesn't output that on EOF.
        printf ("\nNo input\n");
        return 1;
    }

    if (rc == TOO_LONG) {
        printf ("Input too long [%s]\n", buff);
        return 1;
    }

    printf ("OK [%s]\n", buff);

    return 0;
}

Наконец, тестовый запуск, чтобы показать его в действии:

$ ./tstprg
Enter string>[CTRL-D]
No input

$ ./tstprg
Enter string> a
OK [a]

$ ./tstprg
Enter string> hello
OK [hello]

$ ./tstprg
Enter string> hello there
Input too long [hello the]

$ ./tstprg
Enter string> i am pax
OK [i am pax]
53
ответ дан 24 November 2019 в 18:45
поделиться

Похоже, что большинство ответов на данный момент сосредоточено на проблеме переполнения строкового буфера. В действительности спецификаторы формата, которые могут использоваться с функциями scanf , поддерживают явную настройку ширины поля , которая ограничивает максимальный размер ввода и предотвращает переполнение буфера. Это делает популярные обвинения в опасности переполнения строкового буфера, содержащиеся в scanf , практически безосновательными.Утверждать, что scanf чем-то аналогичен получает в этом отношении, совершенно неверно. Существует большое качественное различие между scanf и gets : scanf действительно предоставляет пользователю функции предотвращения переполнения строкового буфера, а получает ] нет.

Можно возразить, что эти функции scanf трудно использовать, поскольку ширина поля должна быть встроена в строку формата (нет способа передать ее через аргумент с переменным числом аргументов, как это можно сделать в printf ). Это действительно так. scanf действительно плохо спроектирован в этом отношении. Но, тем не менее, любые утверждения о том, что scanf каким-то образом безнадежно сломан в отношении безопасности переполнения строкового буфера, являются полностью ложными и обычно делаются ленивыми программистами.

Настоящая проблема с scanf имеет совершенно иную природу, хотя она также связана с переполнением . Когда функция scanf используется для преобразования десятичных представлений чисел в значения арифметических типов, она не обеспечивает защиты от арифметического переполнения. Если происходит переполнение, scanf вызывает неопределенное поведение. По этой причине единственный правильный способ выполнить преобразование в стандартной библиотеке C - это функции из семейства strto ... .

Итак, резюмируя вышесказанное, проблема с scanf заключается в том, что его трудно (хотя и возможно) правильно и безопасно использовать со строковыми буферами.И его нельзя безопасно использовать для арифметического ввода. Последнее и есть настоящая проблема. Первое - это просто неудобство.

P.S. Вышеупомянутое предназначено для всего семейства функций scanf (включая также fscanf и sscanf ). В частности, с scanf очевидной проблемой является то, что сама идея использования строго отформатированной функции для чтения потенциально интерактивного ввода весьма сомнительна.

56
ответ дан 24 November 2019 в 18:45
поделиться

Из часто задаваемых вопросов о comp.lang.c: Почему все говорят не использовать scanf? Что мне использовать вместо этого?

scanf имеет ряд проблем - см. Вопросы 12.17 , 12.18a и 12.19 . Кроме того, его формат % s имеет ту же проблему, что и gets () (см. Вопрос 12.23 ) - трудно гарантировать, что принимающий буфер не будет переполнение. [сноска]

В более общем смысле, scanf разработан для относительно структурированного, отформатированного ввода (его название фактически происходит от «отформатированный отсканированный»). Если вы обратите внимание, он скажет вам, удалось это или нет, но он может сказать вам только приблизительно, где он потерпел неудачу, а вовсе не как и почему. У вас очень мало возможностей для исправления ошибок.

Тем не менее, интерактивный пользовательский ввод - наименее структурированный ввод. Хорошо спроектированный пользовательский интерфейс позволит пользователю вводить что угодно - не только буквы или знаки препинания, когда ожидались цифры, но также большее или меньшее количество символов, чем ожидалось, или полное отсутствие символов ( т.е. , просто клавиша RETURN), или преждевременный EOF, или что-то еще.Практически невозможно корректно справиться со всеми этими потенциальными проблемами при использовании scanf ; гораздо легче читать целые строки (с помощью fgets и т.п.), а затем интерпретировать их, используя sscanf или некоторые другие методы. (Часто полезны такие функции, как strtol , strtok и atoi ; см. Также вопросы 12.16 и 13.6 ). ) Если вы используете какой-либо вариант scanf , обязательно проверьте возвращаемое значение, чтобы убедиться, что было найдено ожидаемое количество элементов. Кроме того, если вы используете % s , обязательно защитите себя от переполнения буфера.

Обратите внимание, кстати, что критика scanf не обязательно является обвинением в отношении fscanf и sscanf . scanf читает из stdin , который обычно представляет собой интерактивную клавиатуру и поэтому имеет меньше всего ограничений, что приводит к большему количеству проблем. С другой стороны, когда файл данных имеет известный формат, его можно прочитать с помощью fscanf . Совершенно уместно анализировать строки с помощью sscanf (пока проверяется возвращаемое значение), потому что очень легко восстановить контроль, перезапустить сканирование, отбросить ввод, если он не соответствует, и т. Д.

​​Дополнительные ссылки:

Ссылки: K & R2 Sec. 7.4 п. 159

13
ответ дан 24 November 2019 в 18:45
поделиться

Очень сложно заставить scanf делать то, что вы хотите. Конечно, можете, но такие вещи, как scanf ("% s", buf); , так же опасны, как gets (buf); , как все уже говорили.

Например, то, что paxdiablo делает в своей функции чтения, может быть выполнено с помощью чего-то вроде:

scanf("%10[^\n]%*[^\n]", buf));
getchar();

Вышеупомянутое будет читать строку, сохранять первые 10 символов не новой строки в buf , а затем отбросьте все до новой строки (включительно). Итак, функция paxdiablo может быть написана с использованием scanf следующим образом:

#include <stdio.h>

enum read_status {
    OK,
    NO_INPUT,
    TOO_LONG
};

static int get_line(const char *prompt, char *buf, size_t sz)
{
    char fmt[40];
    int i;
    int nscanned;

    printf("%s", prompt);
    fflush(stdout);

    sprintf(fmt, "%%%zu[^\n]%%*[^\n]%%n", sz-1);
    /* read at most sz-1 characters on, discarding the rest */
    i = scanf(fmt, buf, &nscanned);
    if (i > 0) {
        getchar();
        if (nscanned >= sz) {
            return TOO_LONG;
        } else {
            return OK;
        }
    } else {
        return NO_INPUT;
    }
}

int main(void)
{
    char buf[10+1];
    int rc;

    while ((rc = get_line("Enter string> ", buf, sizeof buf)) != NO_INPUT) {
        if (rc == TOO_LONG) {
            printf("Input too long: ");
        }
        printf("->%s<-\n", buf);
    }
    return 0;
}

Одна из других проблем с scanf - это его поведение в случае переполнения. Например, при чтении int :

int i;
scanf("%d", &i);

вышеуказанное нельзя безопасно использовать в случае переполнения. Даже в первом случае чтение строки намного проще выполнить с помощью fgets , чем с scanf .

6
ответ дан 24 November 2019 в 18:45
поделиться

Проблемы У меня с семейством * scanf () :

  • Возможность переполнения буфера при использовании спецификаторов преобразования% s и% [. Да, вы можете указать максимальную ширину поля, но в отличие от printf () , вы не можете сделать ее аргументом в вызове scanf () ; он должен быть жестко задан в спецификаторе преобразования.
  • Возможность арифметического переполнения% d,% i и т. Д.
  • Ограниченная способность обнаруживать и отклонять неверно сформированный ввод. Например, «12w4» не является допустимым целым числом, но scanf («% d», & value); успешно преобразует и присвоит 12 значение , оставив «w4» застрявшим. во входном потоке, чтобы испортить будущее чтение. В идеале вся входная строка должна быть отклонена, но scanf () не дает вам простого механизма для этого.

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

3
ответ дан 24 November 2019 в 18:45
поделиться

Да, вы правы. В семействе scanf (scanf, sscanf, fscanf...и т.д.) существует серьезный недостаток безопасности, поскольку они не учитывают длину буфера (в который они читают) при чтении строки.

Пример:

char buf[3];
sscanf("abcdef","%s",buf);

очевидно, что буфер buf может вместить MAX 3 char. Но sscanf попытается поместить в него "abcdef", что приведет к переполнению буфера.

5
ответ дан 24 November 2019 в 18:45
поделиться
Другие вопросы по тегам:

Похожие вопросы: