Недостатки scanf

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

На многих сайтах я читал, что использование scanf может привести к переполнению буфера. Что является причиной этого? Существуют ли другие недостатки с scanf?

+56
источник поделиться
10 ответов

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

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

Я очень предпочитаю использовать fgets для чтения целых строк, чтобы вы могли ограничить количество прочитанных данных. Если у вас есть буфер 1K, и вы читаете строку в нем с помощью 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]
+52
источник

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

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

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

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

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

+53
источник
другие ответы

Связанные вопросы


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

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

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

В более общем плане 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

+12
источник

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

Пример:

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

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

+5
источник

Очень сложно получить 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.

+5
источник

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

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

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

+3
источник

В многочисленных ответах здесь обсуждаются потенциальные проблемы с переполнением при использовании scanf("%s", buf), но последняя спецификация POSIX более или менее разрешает эту проблему, предоставляя символ назначения назначения m, который может использоваться в спецификаторах формата для c, s и [. Это позволит scanf выделять столько памяти, сколько необходимо, с помощью malloc (поэтому он должен быть освобожден позже с помощью free).

Пример использования:

char *buf;
scanf("%ms", &buf); // with 'm', scanf expects a pointer to pointer to char.

// use buf

free(buf);

Смотрите здесь. Недостатки этого подхода заключаются в том, что он является относительно недавним дополнением к спецификации POSIX и вообще не указан в спецификации C, поэтому на данный момент он остается неспортивным.

+3
источник

Существует одна большая проблема с scanf -подобными функциями - отсутствие безопасности любого типа. То есть вы можете закодировать это:

int i;
scanf("%10s", &i);

Черт, даже это "отлично":

scanf("%10s", i);

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

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

+3
источник

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


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

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

Например, непосвященные обычно ожидают, что делегат %s вызовет чтение строки, и хотя это может показаться интуитивным, это не обязательно верно. Более уместно описать поле, прочитанное как слово. Чтение руководства настоятельно рекомендуется для каждой функции.

Каким был бы ответ на этот вопрос без упоминания его отсутствия безопасности и риска переполнения буфера? Как мы уже говорили, C не является безопасным языком и позволит нам сократить углы, возможно, применить оптимизацию за счет правильности или, скорее, потому что мы ленивые программисты. Таким образом, когда мы знаем, что система никогда не получит строку, большую, чем фиксированное количество байтов, нам будет предоставлена возможность объявить массив, размер которого и отменяет проверку границ. Я действительно не вижу в этом нисходящего падения; это вариант. Опять же, чтение руководства настоятельно рекомендуется и будет раскрывать этот вариант для нас.

Отсканированные программисты не единственные, что ужалили scanf. Это не редкость, когда люди пытаются читать float или double values, используя, например, %d. Они обычно ошибаются, полагая, что реализация будет выполнять какое-то преобразование за кулисами, что имеет смысл, потому что подобные преобразования происходят по всему остальному языку, но это не так. Как я уже говорил ранее, scanf и друзья (и даже остальная часть C) обманчивы; они кажутся лаконичными и идиоматическими, но это не так.

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

Как и fgets, когда scanf и друзья не читают то, что им говорят, поток будет оставлен в необычном состоянии; - В случае fgets, если для хранения полной строки недостаточно места, оставшаяся часть строки, оставленной непрочитанной, может быть ошибочно обработана, как если бы она была новой строкой, когда она не является. - В случае scanf и друзей преобразование завершилось неудачно, как описано выше, ошибочные данные остаются непрочитанными в потоке и могут быть ошибочно обработаны, как если бы они были частью другого поля.

Не проще использовать scanf и друзей, чем использовать fgets. Если мы проверим успех, ищем '\n' когда мы используем fgets или проверяем возвращаемое значение при использовании scanf и друзей, и мы обнаруживаем, что мы прочитали неполную строку с использованием fgets или не смогли прочитать поле с помощью scanf, то мы сталкиваемся с такой же реальностью: мы, вероятно, отменим ввод (обычно вплоть до следующей новой строки). Yuuuuuuck!

К сожалению, scanf одновременно делает его сложным (неинтуитивным) и легким (наименьшее количество нажатий клавиш), чтобы отбросить вход таким образом. Столкнувшись с этой реальностью отказа от пользовательского ввода, некоторые попытались scanf("%*[^\n]%*c"); , не понимая, что делегат %*[^\n] потерпит неудачу, когда он встречает только новую строку, и, следовательно, новая строка все равно останется в потоке.

Небольшая адаптация, разделив делегатов двух форматов, и мы видим здесь некоторый успех: scanf("%*[^\n]"); getchar(); scanf("%*[^\n]"); getchar(); , Попробуйте сделать это с таким количеством нажатий клавиш, используя какой-либо другой инструмент;)

+3
источник

Функция fgets() - хорошее решение

0
источник

Посмотрите другие вопросы по меткам или Задайте вопрос