Функция gets() в C

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

Если я использую функцию gets(), я мог бы это сделать. Введите свое имя: Keanu Reeves.

Если я использую scanf(), я мог бы это сделать. Введите свое имя: Keanu

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

Ответ 1

это инструмент дьявола для создания переполнения буфера

Поскольку gets не принимает параметр длины, он не знает, насколько велик ваш буфер ввода. Если вы переходите в 10-символьный буфер, и пользователь вводит 100 символов - ну, вы получите точку.

fgets является более безопасной альтернативой gets, поскольку он принимает длину буфера в качестве параметра, поэтому вы можете вызвать его следующим образом:

fgets(str, 10, stdin);

и он будет читать не более 9 символов.

проблема в том, что некоторые из моих кодов больше не работают

Возможно, из-за того, что fgets также сохраняет символ новой строки (\n) в вашем буфере - если ваш код не ожидает этого, вы должны удалить его вручную:

int len = strlen(str);
if (len > 0 && str[len-1] == '\n')
  str[len-1] = '\0';

Ответ 2

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

Один из первых распространенных червей, выпущенный в 1988 году, использовал gets(), чтобы пропагандировать себя в Интернете. Здесь интересный отрывок из программы Expert C от Peter Van Der Linden, в которой обсуждается, как это работает:

Ранняя ошибка получает() интернет-червь

Проблемы в C не ограничиваются только языком. Некоторые процедуры в стандартной библиотеке имеют небезопасную семантику. Это было резко продемонстрировано в ноябре 1988 года программой червя, которая извивалась через тысячи компьютеров в сети Интернет. Когда дым был очищен, и исследования были завершены, было установлено, что один из способов распространения червя был вызван слабостью демона finger, который принимает запросы по сети о том, кто в настоящий момент зарегистрирован. Демон finger, in.fingerd, используется стандартная процедура ввода/вывода gets().

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

main(argc, argv)
char *argv[];
{
char line[512];
...
gets(line);

Здесь line - это 512-байтовый массив, выделенный автоматически в стеке. Когда пользователь предоставляет больше ввода, чем тот, который используется для демона finger, подпрограмма gets() будет помещать его в стек. Большинство архитектур уязвимы для перезаписи существующей записи в середине стека с чем-то большим, что также перезаписывает соседние записи. Стоимость проверки доступа каждого стека к размеру и разрешению была бы непомерной в программном обеспечении. Знающий злоумышленник может изменить адрес возврата в записи активации процедуры в стеке, запустив правильные двоичные паттерны в строке аргумента. Это приведет к отвлечению потока выполнения не назад к тому месту, откуда он пришел, а к специальной последовательности команд (также аккуратно помещенной в стек), которая вызывает execv(), чтобы заменить запущенное изображение на оболочку. Voilà, теперь вы разговариваете с оболочкой на удаленной машине вместо демона finger, и вы можете выдавать команды для перетаскивания копии вируса на другую машину.

По иронии судьбы, подпрограмма gets() - это устаревшая функция, обеспечивающая совместимость с самой первой версией портативной библиотеки ввода-вывода, и ее заменили стандартным вводом-выводом более десяти лет назад. В manpage даже настоятельно рекомендуется использовать fgets(). Процедура fgets() устанавливает ограничение на количество прочитанных символов, поэтому оно не будет превышать размер буфера. Демон finger был защищен с заменой двух строк:

gets(line);

по строкам:

if (fgets(line, sizeof(line), stdin) == NULL)
exit(1);

Это проглатывает ограниченный объем ввода и, следовательно, нельзя манипулировать переписыванием важных мест кем-то, кто запускает программу. Однако стандарт ANSI C не удалял gets() с языка. Таким образом, хотя эта конкретная программа была защищена, основной дефект в стандартной библиотеке C не был удален.

Ответ 3

Вы можете посмотреть на этот вопрос: Безопасная альтернатива gets(). Существует ряд полезных ответов.

Вы должны уточнить, почему ваш код не работает с fgets(). Как объясняют ответы в другом вопросе, вы должны иметь дело с новой строкой, которую gets() не указывается.

Ответ 4

Чтобы прочитать все слова, используя scanf, вы можете сделать это следующим образом:

Пример:

printf("Enter name: ");

scanf("%[^\n]s",name);       //[^\n] is the trick

Ответ 5

Вы можете прочитать несколько полей с scanf(), чтобы вы могли:

scanf("%s %s\n", first_name,  last_name);

Тем не менее, я думаю, что было бы лучше прочитать строку, а затем разбить ее самостоятельно, так как они, возможно, не ввели только имя, или first/middle/last.

Каковы проблемы с fgets()?

Проблема с gets() заключается в том, что она возвращает столько символов, сколько пользователь вводит - вы, как вызывающий, не контролируете это. Таким образом, вы можете выделить 80 символов, пользователь может ввести 100 символов, а последние 20 будут списаны с конца выделенной памяти, топая, кто знает что.

Ответ 6

Вы можете использовать scanf для имитации gets. Это не очень, хотя.

#include <stdio.h>

#define S_HELPER(X) # X
#define STRINGIZE(X) S_HELPER(X)
#define MAX_NAME_LEN 20

int flushinput(void) {
  int ch;
  while (((ch = getchar()) != EOF) && (ch != '\n')) /* void */;
  return ch;
}

int main(void) {
  char name[MAX_NAME_LEN + 1] = {0};

  while (name[0] != '*') {
    printf("Enter a name (* to quit): ");
    fflush(stdout);
    scanf("%" STRINGIZE(MAX_NAME_LEN) "[^\n]", name); /* safe gets */
    if (flushinput() == EOF) break;
    printf("Name: [%s]\n", name);
    puts("");
  }

  return 0;
}

Вам гораздо лучше читать с помощью fgets и анализировать (при необходимости) с помощью sscanf.


EDIT, объясняя вызов scanf и окружающий код.

"% [" спецификация преобразования scanf принимает максимальную ширину поля, которая не включает нулевой ограничитель. Таким образом, массив для хранения ввода должен иметь еще 1 символ, чем читать с помощью scanf.

Чтобы сделать это только с одной константой, я использовал макрос STRINGIZE. С помощью этого макроса я могу использовать константу # define'd как размер массива (для определения переменной) в виде строки (для спецификатора).

Вот еще один аспект, который заслуживает упоминания: flushinput. При использовании gets все данные записываются в память (даже при переполнении буфера) вплоть до новой строки. Чтобы подражать этому, scanf читает ограниченное число символов, но не включает в себя новую строку, и, в отличие от gets, сохраняет новую строку во входном буфере. Чтобы новая строка была удалена и что делает flushinput.

Остальная часть кода была в основном для настройки тестовой среды.