Remkomplekty.ru

IT Новости из мира ПК
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Gets си библиотека

Зона кода

Функция gets_s() своими руками

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

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

Для начала рассмотрим функцию gets() более подробно. gets() — это стандартная библиотечная функция, описанная в заголовочном файле stdio.h . Она имеет следующий прототип:

Функция принимает в качестве параметра адрес массива символов и считывает строку из стандартного потока, помещая её в данный массив. Функция возвращает этот же адрес в случае успеха или нулевой адрес в случае неудачи. Считывание строки заканчивается после того, как встречается символ новой строки (при этом он не записывается в массив) или значение EOF (макрос EOF определён в файле stdio.h ; компилятор MinGW64 «приписывает» EOF значение -1). После завершения считывания в массив записывается завершающий нулевой символ.

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

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

В языке C99 функция gets() объявлена как «осуждаемая» (deprecated). Она оставлена в стандарте лишь для совместимости с предыдущей версией языка — C89. В стандарте C11 функция gets() уже отсутствует. Она заменена своей безопасной версией — gets_s() .

Функция gets_s() не входит в стандартную библиотеку C99, но мы создадим «свой» вариант этой функции самостоятельно. Наша функция будет иметь следующий прототип:

Отличие этой функции от gets() заключается в том, что в символьный массив, адресуемый параметром str , будет записано не более num — 1 символов из входного потока (последним символом будет записан нулевой завершающий символ). Если в потоке имеется более num — 1 символов, то «лишние» символы будут проигнорированы.

Функция gets_s() будет основана на функции fgets() из стандартной библиотеки stdio.h , имеющей следующий прототип:

Функция принимает в качестве первого параметра адрес массива символов и считывает строку из входного потока, адресуемого третьим параметром, записывая в массив не более num — 1 символов из данного потока. Функция возвращает адрес массива в случае успеха или нулевой адрес в случае неудачи. Считывание строки заканчивается после того, как встречается символ новой строки (при этом он записывается в массив, если его позиция в считываемой строке не превышает num — 1 ) или значение EOF . После завершения считывания в массив записывается завершающий нулевой символ.

Мы вызовем из функции gets_s() функцию fgets() , передав ей в качестве первого и второго аргументов значения первого и второго формальных параметров функции gets_s() соответственно, а в качестве третьего аргумента — стандартный входной поток stdin (макрос, определённый в файле stdio.h ).

Затем мы проверим, не записан ли в массиве, адресуемом указателем str , символ новой строки » n «. Этот символ может быть записан только перед завершающим нулевым символом. Для выяснения возможной позиции символа » n » мы найдём длину C-строки, адресуемой переменной str , и вычтем из неё единицу. Длину строки будем находить с помощью функции strlen() из стандартной библиотеки string.h , содержащей функции обработки C-строк. Если символ новой строки обнаружится, то мы запишем на его место завершающий нулевой символ, уменьшив, тем самым, длину C-строки на единицу.

Читать еще:  Типы переменных в си шарп

Наконец, функция gets_s() будет возвращать значение, возвращённое функцией fgets() . Ниже приведён код функции gets_s() .

Напишем простую функцию main() для тестирования функции gets_s() :

Тестовая программа считывает из входного потока не более 9 символов и помещает их в символьный массив string , после чего выводит его содержимое на печать. Поместим, в качестве эксперимента, во входной поток (в нашем случае — на консоль) 10 символов:

Как мы видим, программа отработала корректно. Итак, функция gets_s() реализована, и мы сможем смело использовать её в наших будущих программах!

лабы по информатике, егэ

лабораторные работы и задачи по программированию и информатике, егэ по информатике

Урок 1. Часть 3: Инструкции языка Си scanf, gets, getchar — ввод данных

Функция языка Си gets

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

Функция Си gets() помещает в переменную вводимую строку. В качестве параметра функции указывается имя переменной, в которую и помещается строка.

var name:string; begin writeln(‘введите имя’); readln(name); writeln(name); end.

Функция языка Си getchar

Функция Си getchar() запрашивает с клавиатуры единичный символ и практически для всех компиляторов безразлично, к какому типу (char или int) относится данный символ.

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

Для сравнения выполним ту же задачу с функцией Си gets():

Функция языка Си scanf

Функция Си scanf () является более многогранной функцией, так как позволяет вводить в компьютер данные любых типов.

По аналогии с функцией printf() функция Си scanf() тоже может иметь несколько аргументов, позволяя тем самым вводить значения числовых, символьных и строковых переменных в одно и то же время.

Список параметров функции scanf() состоит из двух частей: строки формата и списка данных. В строке формата находятся указатели формата (преобразователи символов, после знака процента), которые определяют то, каким образом должны быть интерпретированы вводимые данные. Вторая часть функции — список данных, в котором перечисляются переменные, в которые должны быть занесены вводимые значения. Порядок следования указателей формата и переменных должен быть одинаков.

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

C programming для начинающих: часть 15 — проблемы gets() и fgets()

В предыдщуем примере мы уже сталкивались с использованием функции gets() :

Тут мы получаем данные от пользователя из STDIN , и вписываем их в массив agestring .

Однако — тут имеется серьёзная проблема: gets() примет любое количество символов, которые введёт пользователь программы, несмотря на то, что сам массив объявлен длиной в 10 символов:

Что произойдёт, если ввести большее их количество?

Давайте рассмотрим это на следующей программе:

Обратите внимание на предупреждение:

warning: the `gets’ function is dangerous and should not be used.

Сейчас мы увидим — почему оно появляется.

В программе выше мы повторяем те же действия — создаём два массива с длиной в 5 элементов:

После чего в функции getinput_with_gets() мы используем gets() для получения данных от пользователя.

В данном случае — у нас всё сработает как положено если мы введём 4 символа (5-ый — null-терминатор, символ завершения строки, ):

Но если вы введёте большее количество — то gets() примет всю строку ввода, начнёт вносить данные в массив, и т.к. длина массива будет меньше, чем строка — то эта строка выйдет за пределы массива, в другой, непредсказуемый, участок памяти, что может привести к тому что эта строка перезапишет данные, уже имеющиеся в этом участке памяти.

Можно продемонстрировать происходящее так: в getinput_with_gets() добавим цикл, который будет выводить все элементы массива и их индексы:

Тут хорошо видно, что gets() просто начал перезаписывать массив сначала: вместо 1 в первом (нулевом) элементе мы получаем последний символ — 6.

Решение этой проблемы — использовать альтернативную функцию fgets() вместо gets() , что сделано в функции getinput_with_fgets() нашего примера выше:

Читать еще:  Численное интегрирование метод прямоугольников паскаль

Обновим main() , меняем используемую функцию:

В отличии от gets() — fgets() принимает три аргумента:

Первым указывается имя массива, в который мы будем вносить данные, вторым — максимальное количество символов, которые fgets() примет на входе. Третьим указывается источник данных, в данном случае — обычный STDIN .

Итак — второй аргумент указывает fgets() принять только указанное кол-во символов -1 (завершение строки).

Таким образом, если вы укажете вторым аргументом строку «abcde» — только первые 4 символа abcd будут внесены в массив firstname + последний символ . Следовательно — firstname будет одержать строку «acbd«.

Казалось бы — всё работает, как и ожидается. Но давайте внимательнее рассмотрим код:

Обратите внимание на функцию flush_input() :

Зачем она нужна? Давайте закомментируем её вызов, и проверим:

Упс… Наша программа теперь даже не вызывает fgets(lastname, 5, stdin) , и вместо этого срабатывает printf() сразу же после fgets(firstname, 5, stdin) , при этом присваивая «остаток» первой строки во второй массив lastname[] .

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

Gets си библиотека

M. УЭИТ С. ПРАТА Д. МАРТИН

Язык Си — руководство для начинающих

Книга: Язык Си — руководство для начинающих

Функция gets( )

Эта функция считывания строки очень удобна для диалоговых систем. Она получает строку от стандартного устройства ввода вашей системы, которым, как мы предполагаем, является клавиатура. Поскольку строка не имеет заранее заданной длины, функция gets( ) должна знать, когда ей прекратить работу. Функция читает символы до тех пор, пока ей не встретится символ новой строки (‘n’), который вы создаете, нажимая клавишу [ввод]. Функция берет все символы до (но не включая) символа новой строки, присоединяет к ним нуль-символ (») и передает строку вызывающей программе. Вот простой способ использования функции.

char name[81]; /* выделение памяти */

printf(» Привет, как вас зовут?n»);

gets(name); /* размещение введенного имени в строку «name» */

printf(» Хорошее имя, %s. n» , name);

Функция примет любое имя (включая пробелы) длиной до 80 символов. (Не забудьте запасти один символ для ».)

Отметим, что мы хотели при помощи функции gets( ) воздействовать на нечто (name) в вызывающей программе. Значит, нужно использовать указатель в качестве аргумента; а имя массива, конечно, является его указателем.

Функция gets( ) обладает большими возможностями, чем показано в последнем примере. Взгляните на эту программу:

printf(» Привет, как вас зовут?n»);

printf(» %s? Ax! %s!n», name, ptr);

Привет, как вас зовут?

Тони де Туна? Ах! Тони де Туна!

Функция gets( ) предоставляет вам два способа ввода строки!

1. Использует метод указателей для передачи строки в name.

2. Использует ключевое слово return для возврата строки в ptr.

Напомним, что ptr является указателем на тип char. Это означает, что gets( ) должна вернуть значение, которое является указателем на тип char. И в приведенном выше изложении вы можете увидеть, что мы так и описали gets( ).

говорит о том, что gets( ) является функцией (отсюда круглые скобки) типа «указатель на тип char» (поэтому * и char). В примере получение имени1 мы обходились без этого описания, потому что мы никогда не пытались использовать возвращенное значение функции gets( ).

Между прочим, вы можете также описать указатель на функцию. Это выглядело бы следующим образом:

и foop был бы указателем на функцию типа char. Мы расскажем немного подробнее о таких причудливых описаниях в гл. 14.

Структура функции gets( ) выглядела бы примерно так:

На самом деле структура немного сложнее, и для gets( ) есть две возможности возврата. Если все идет хорошо, она возвращает считанную строку, как мы уже сказали. Если что-то неправильно или если gets( ) встречает символ EOF, она возвращает NULL, или нулевой адрес. Таким образом gets( ) включает разряд проверки ошибки. Поэтому данная функция удобна для использования в конструкциях, подобных

C++ для людей

std::cout

cin.get() и его друзья

Функция

Читать еще:  Цикл for to do паскаль

int istream::get();

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

Функция get() же стандартного потока ввода cin заставляет систему ожидать ввода пользователем любого символа, который она считывает, и программа завершается.

Проблема в том, что работает cin.get() далеко не всегда. Почему? Рассмотрим ситуацию издалека.

Начнем с того, что же такое поток (stream). Ненаучным языком говоря, поток — последовательность символов. Источником символов может служить в частности клавиатура. Символы идут один за другим:

К ним возможен только последовательный доступ, но не произвольный, иными словами, чтобы извлечь из потока символ d , надо предварительно извлечь из него a , b и c . Извлеченный (прочитанный) символ удаляется из потока.

Надо сказать, что если по каким-то причинам из потока прочитаны не все символы до конца строки (символа ‘n’ ) включительно, то после операции чтения поток не будет пустым. Два самых распространенных способа дают нам два хороших примера.

1)
cout

Уже рассматривавшаяся выше функция get() читает из потока один символ, так что если мы ввели несколько символов, то она оставит за собой непустой поток. И следующий вызов cin.get() будет обречен на «фиаско»: программа вместо того, чтобы остановиться и ждать пользовательского ввода, прочитает из входного потока очередной символ, оставшийся от предыдущего ввода, и продолжит свое выполнение. Или завершится «без спроса». Напомню, что get() возвращает приведенный к типу int код введенного символа, поэтому мы может использовать его в вышеприведенной инструкции — код просто будет выведен на экран.

Собственно вывод: если в конце программы cin.get() не ждет пользовательского ввода, значит, вы оставили за собой непустой входной поток.

2)
int a;
cin>>a;

Перегруженный оператор сдвига, использующийся для ввода данных из потока, который в свою очередь перегружен для работы с целыми числами (так как вызван с параметром a типа int ), считывает символы, являющиеся десятичными цифрами, до тех пор, пока не встретит нецифровой символ. Это может быть пробел, буква, табуляция, конец строки, и так далее.
Все эти символы остались в потоке. Даже если вы просто ввели число и нажали Enter, символ ‘n’ остался в потоке.

На него и «нарывается» впоследствии cin.get() .

Следующая шуточная программка позволяет воочию увидеть «обидчика». Скомпилируйте ее, запустите и введите, к примеру,

Она выведет символ, на котором «споткнулся» cin.get() .

using namespace std;

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

1)
Функция

определенная в классе istream , извлекает из потока символы и отбрасывает их. Причем она так поступает либо с n символами, либо со всеми символами, пока в потоке не встретится символ, заданный параметром delim .

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

cin.ignore(numeric_limits ::max(), ‘n’);

2)
streambuf* istream::rdbuf() const;
streamsize streambuf::in_avail();

У потока ввода есть буфер чтения, в котором он хранит символы. Мы обращаемся к нему, вызывая функцию rdbuf() . А любой уважающий себя буфер знает, сколько символов в нем содержится. Поскольку и мы хотим это узнать, мы вызываем функцию in_avail() этого буфера. Это количество символов нам и нужно проигнорировать, что достигается так:

cin.ignore(cin.rdbuf()->in_avail());

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

3)
int istream::sync();

Использование функции sync() — путь наименьшего сопротивления. Она просто очищает поток от имеющихся в нем символов.

Почему этот способ самый сложный — поищите в Гугле (если есть желание). А я в это время порекомендую Вам выбрать более понравившийся из первых двух и пойду заниматься более полезными делами 😉

Хотя нет, напоследок еще расскажу о функции

Ссылка на основную публикацию
Adblock
detector