Rambler's Top100 Service калинин.ru / программирование / c и c++ /  << 15.08.00 >>

Функция gets()

Функция gets(), входящая в состав стандартной библиотеки C, имеет следующий прототип:

char* gets(char* s);

Это определение содержится в stdio.h. Функция предназначена для ввода строки символов из файла stdin. Она возвращает s если чтение прошло успешно и NULL в обратном случае.

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

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

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

char name[10];

// ...

puts("Enter you name:");
gets(name);

Если у пользователя будет имя больше, чем 9 символов, например, 10, то по адресу (name + 10) будет записан 0. Что там на самом деле находится, другие данные или просто незанятое место (возникшее, например, из-за того, что компилятор соответствующим образом выравнял данные), или этот адрес для программы недоступен, неизвестно.

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

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

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

Для это просто подчеркну контекст, в котором происходит чтение строки.

void foo()
{
  char name[10];

  // ...

  puts("Enter you name:");
  gets(name);

  // ...
}

Я имею в виду, что теперь name расположен в стеке. Надеюсь, что читающие эти строки люди имеют представление о том, как обычно выполняется вызов функции. Грубо говоря, сначала в стек помещается адрес возврата, а потом в нем же размещается память под массив name. Так что теперь, когда функция gets() будет писать за пределами массива, она будет портить адрес возврата из функции foo().

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

Немного отвлечемся, потому что это достаточно интересно. В операционной системе Unix есть возможность запускать программы, которые будут иметь привилегии пользователя, отличного от того, кто этот запуск произвел. Самый распространенный пример, это, конечно же, суперпользователь. Например, команды ps или passwd при запуске любым пользователем получают полномочия root'а. Сделано это потому, что копаться в чужой памяти (для ps) может только суперпользователь, так же как и вносить изменения в /etc/passwd. Понятно, что такие программы тщательнейшим образом проверяются на отсутствие ошибок --- через них могут "утечь" полномочия к нехорошим "хакерам" (существуют и хорошие ;) ). Размещение буфера в стеке некоторой функции, чей код выполняется с привилегиями другого пользователя, позволяет при переполнении этого буфера изменить на что-то осмысленное адрес возврата из функции. Как поместить по переданному адресу то, что требуется выполнить (например, запуск командного интерпретатора), это уже другой разговор и он не имеет прямого отношения к программированию на C или C++.

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

fgets(name, 10, stdin);

Резюме

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


Версия для печати


  Ссылки по теме:
Бъерн Страуструп
   Язык программирования C++, 3 издание.
  Рядом в разделе:
Свойства (23.08.00)
   Я уже говорил о и хотя эта заметка не будет посвящена ему, тем не менее я о нем вспомню. Когда только...   >>>>
Коллекции исходных текстов (14.08.00)
   В последние несколько дней я просматривал странички в РуНете "братственной" направленности, т.е. посвященные программированию на C++. Я обнаружил чрезвычайно забавную вещь...   >>>>
  Рядом по дате:
C++: библиотека программиста (16.08.00)
   Первая глава этой книги так и называется: "Зачем нужна еще одна книга о C++?" и начинается со следующих слов: По последним...   >>>>
Коллекции исходных текстов (14.08.00)
   В последние несколько дней я просматривал странички в РуНете "братственной" направленности, т.е. посвященные программированию на C++. Я обнаружил чрезвычайно забавную вещь...   >>>>
  Содержание:
Заглавная страница
Мой блог
Мое резюме
Дайджест
Программирование
   C&C++
Сети
Unix
Алгоритмы
Оптимизация
Соревнования
Отвлеченно
XML
TeX
Просто так
Студенческое
Туризм
  Байки
Фотографии
Комментарии
   Книги
Web-ресурсы
Фильмы
Интернет
Программное обеспечение
Жизнь
Благодарности
Форум
Хронология
 
  В этом разделе:
Простой, но полезный аллокатор памяти (18.02.03)
   Эта заметка --- продолжение "Postfix изнутри" в том смысле, что в качестве примера опять берется postfix. Но если в прошлый раз...   >>>>
C или C++? (09.07.01)
   Существуют два диаметрально противоположенных, но одинаково распространенных мнения, которые можно выразить как "C++ это C с классами" и "C++ и C...   >>>>
Религия и goto (14.04.01)
   Начнем несколько издалека. В программировании существует тенденция к алгоритмизации самого процесса программирования. То есть, выведение некоторых универсальных правил, использование которых в...   >>>>
ploticus (16.10.00)
   Есть такая программа, предназначенная для создания графиков различных видов из командной строки, называется ploticus. Программа сама по себе достаточно удобная ---...   >>>>
Шаманство, или ошибки работы с памятью (25.09.00)
   Когда программа становится внушительной по своему содержанию (то есть, не по количеству строчек, а по непонятности внутренних связей), то ее поведение...   >>>>
Библиотека консорциума W3, libwww (20.09.00)
   Популярный нынче термин "веб-программирование" обычно подразумевает под собой программирование, в лучшем случае, на perl, в худшем --- на PHP, в совсем...   >>>>
Инварианты внутри программы (18.09.00)
   Вы когда-нибудь задумывались, над тем, как вы пишите программы? Если нет, то, я думаю, сегодняшняя заметка будет вам полезна. Итак, как...   >>>>
Содержание раздела полностью...
   Примерно в тоже время
C++: библиотека программиста (16.08.00)
   Первая глава этой книги так и называется: "Зачем нужна еще одна книга о C++?" и начинается со следующих слов: По последним...   >>>>
Коллекции исходных текстов (14.08.00)
   В последние несколько дней я просматривал странички в РуНете "братственной" направленности, т.е. посвященные программированию на C++. Я обнаружил чрезвычайно забавную вещь...   >>>>
Хронология полностью...
   Содержание
Заглавная страница
Мой блог
Мое резюме
Дайджест
Программирование
  C&C++
Сети
Unix
Алгоритмы
Оптимизация
Соревнования
Отвлеченно
XML
TeX
Туризм
  Байки
Фотографии
Комментарии
  Книги
Web-ресурсы
Фильмы
Интернет
Программное обеспечение
Жизнь
Студенческое
Просто так
Благодарности
Форум
Хронология
© 2000-2008, Andrey L. Kalinin
mailto:andrey@kalinin.ru
Rambler's Top100