Я хочу знать недостатки scanf()
.
Во многих сайтах я считал то использование scanf
мог бы вызвать переполнение буфера. Какова причина этого? Есть ли любые другие недостатки с scanf
?
Проблемы со 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]
Похоже, что большинство ответов на данный момент сосредоточено на проблеме переполнения строкового буфера. В действительности спецификаторы формата, которые могут использоваться с функциями scanf
, поддерживают явную настройку ширины поля , которая ограничивает максимальный размер ввода и предотвращает переполнение буфера. Это делает популярные обвинения в опасности переполнения строкового буфера, содержащиеся в scanf
, практически безосновательными.Утверждать, что scanf
чем-то аналогичен получает
в этом отношении, совершенно неверно. Существует большое качественное различие между scanf
и gets
: scanf
действительно предоставляет пользователю функции предотвращения переполнения строкового буфера, а получает
] нет.
Можно возразить, что эти функции scanf
трудно использовать, поскольку ширина поля должна быть встроена в строку формата (нет способа передать ее через аргумент с переменным числом аргументов, как это можно сделать в printf
). Это действительно так. scanf
действительно плохо спроектирован в этом отношении. Но, тем не менее, любые утверждения о том, что scanf
каким-то образом безнадежно сломан в отношении безопасности переполнения строкового буфера, являются полностью ложными и обычно делаются ленивыми программистами.
Настоящая проблема с scanf
имеет совершенно иную природу, хотя она также связана с переполнением . Когда функция scanf
используется для преобразования десятичных представлений чисел в значения арифметических типов, она не обеспечивает защиты от арифметического переполнения. Если происходит переполнение, scanf
вызывает неопределенное поведение. По этой причине единственный правильный способ выполнить преобразование в стандартной библиотеке C - это функции из семейства strto ...
.
Итак, резюмируя вышесказанное, проблема с scanf
заключается в том, что его трудно (хотя и возможно) правильно и безопасно использовать со строковыми буферами.И его нельзя безопасно использовать для арифметического ввода. Последнее и есть настоящая проблема. Первое - это просто неудобство.
P.S. Вышеупомянутое предназначено для всего семейства функций scanf
(включая также fscanf
и sscanf
). В частности, с scanf
очевидной проблемой является то, что сама идея использования строго отформатированной функции для чтения потенциально интерактивного ввода весьма сомнительна.
Из часто задаваемых вопросов о 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
Очень сложно заставить 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
.
Проблемы У меня с семейством * scanf ()
:
printf ()
, вы не можете сделать ее аргументом в вызове scanf ()
; он должен быть жестко задан в спецификаторе преобразования. scanf («% d», & value);
успешно преобразует и присвоит 12 значение
, оставив «w4» застрявшим. во входном потоке, чтобы испортить будущее чтение. В идеале вся входная строка должна быть отклонена, но scanf ()
не дает вам простого механизма для этого. Если вы знаете, что ваш ввод всегда будет правильно сформирован со строками фиксированной длины и числовыми значениями, которые не заигрывают с переполнением, тогда scanf ()
- отличный инструмент. Если вы имеете дело с интерактивным вводом или вводом, который не гарантирует правильного формата, используйте что-нибудь другое.
Да, вы правы. В семействе scanf
(scanf
, sscanf
, fscanf
...и т.д.) существует серьезный недостаток безопасности, поскольку они не учитывают длину буфера (в который они читают) при чтении строки.
Пример:
char buf[3];
sscanf("abcdef","%s",buf);
очевидно, что буфер buf
может вместить MAX 3
char. Но sscanf
попытается поместить в него "abcdef"
, что приведет к переполнению буфера.