В предыдщуем примере мы уже сталкивались с использованием функции gets():
...
int main(int argc, char **argv) {
char agestring[10];
int age;
int bonus;
printf("Enter your age : ");
gets(agestring);
...
Тут мы получаем данные от пользователя из STDIN, и вписываем их в массив agestring.
Однако — тут имеется серьёзная проблема: gets() примет любое количество символов, которые введёт пользователь программы, несмотря на то, что сам массив объявлен длиной в 10 символов:
...
char agestring[10];
...
Что произойдёт, если ввести большее их количество?
Давайте рассмотрим это на следующей программе:
#include <stdio.h>
void flush_input(){
int ch;
while ((ch = getchar()) != '\n' && ch != EOF);
}
void getinput_with_gets() {
char firstname[5];
char lastname[5];
printf("Enter your first name:");
gets(firstname);
printf("Enter your last name:");
gets(lastname);
printf("Hello, %s, %s\n", firstname, lastname);
}
void getinput_with_fgets() {
char firstname[5];
char lastname[5];
printf("Enter your first name:");
fgets(firstname, 5, stdin);
printf("Enter your last name:");
// fflush(stdin); // This function may not (invariably) work with input!
flush_input();
fgets(lastname, 5, stdin);
flush_input();
printf("Hello, %s, %s\n", firstname, lastname);
}
int main(int argc, char **argv) {
getinput_with_gets();
// getinput_with_fgets();
return 0;
}
Собираем её:
[simterm]
$ gcc getinput.c -o getinput
getinput.c: In function ‘getinput_with_gets’:
getinput.c:12:9: warning: implicit declaration of function ‘gets’; did you mean ‘fgets’? [-Wimplicit-function-declaration]
gets(firstname);
^~~~
fgets
/tmp/ccZ17mWS.o: In function `getinput_with_gets':
getinput.c:(.text+0x43): warning: the `gets' function is dangerous and should not be used.
[/simterm]
Обратите внимание на предупреждение:
warning: the `gets’ function is dangerous and should not be used.
Сейчас мы увидим — почему оно появляется.
В программе выше мы повторяем те же действия — создаём два массива с длиной в 5 элементов:
..
char firstname[5];
char lastname[5];
...
После чего в функции getinput_with_gets() мы используем gets() для получения данных от пользователя.
В данном случае — у нас всё сработает как положено если мы введём 4 символа (5-ый — null-терминатор, символ завершения строки, \0):
[simterm]
$ ./getinput Enter your first name:1234 Enter your last name:1234 Hello, 1234, 1234
[/simterm]
Но если вы введёте большее количество — то gets() примет всю строку ввода, начнёт вносить данные в массив, и т.к. длина массива будет меньше, чем строка — то эта строка выйдет за пределы массива, в другой, непредсказуемый, участок памяти, что может привести к тому что эта строка перезапишет данные, уже имеющиеся в этом участке памяти.
Проверим:
[simterm]
$ ./getinput Enter your first name:1234 Enter your last name:1234 Hello, 1234, 1234
[/simterm]
Можно продемонстрировать происходящее так: в getinput_with_gets() добавим цикл, который будет выводить все элементы массива и их индексы:
...
void getinput_with_gets() {
char firstname[5];
char lastname[5];
printf("Enter your first name:");
gets(firstname);
printf("Enter your last name:");
gets(lastname);
printf("Hello, %s, %s\n", firstname, lastname);
int i;
for (i=0;i < (sizeof (firstname) /sizeof (firstname[0]));i++) {
printf("Element: %d, value: %c\n", i, firstname[i]);
}
}
...
Результат:
[simterm]
$ ./getinput Enter your first name:123456 Enter your last name:123456 Hello, 6, 123456 Element: 0, value: 6 Element: 1, value: Element: 2, value: 3 Element: 3, value: 4 Element: 4, value: 5
[/simterm]
Тут хорошо видно, что gets() просто начал перезаписывать массив сначала: вместо 1 в первом (нулевом) элементе мы получаем последний символ — 6.
Решение этой проблемы — использовать альтернативную функцию fgets() вместо gets(), что сделано в функции getinput_with_fgets() нашего примера выше:
...
void getinput_with_fgets() {
char firstname[5];
char lastname[5];
printf("Enter your first name:");
fgets(firstname, 5, stdin);
printf("Enter your last name:");
// fflush(stdin); // This function may not (invariably) work with input!
flush_input();
fgets(lastname, 5, stdin);
flush_input();
printf("Hello, %s, %s\n", firstname, lastname);
}
...
Обновим main(), меняем используемую функцию:
...
int main(int argc, char **argv) {
// getinput_with_gets();
getinput_with_fgets();
return 0;
}
...
В отличии от gets() — fgets() принимает три аргумента:
... fgets(firstname, 5, stdin) ...
Первым указывается имя массива, в который мы будем вносить данные, вторым — максимальное количество символов, которые fgets() примет на входе. Третьим указывается источник данных, в данном случае — обычный STDIN.
Итак — второй аргумент указывает fgets() принять только указанное кол-во символов -1 (завершение строки).
Таким образом, если вы укажете вторым аргументом строку «abcde» — только первые 4 символа abcd будут внесены в массив firstname + последний символ \0. Следовательно — firstname будет одержать строку «acbd\0«.
Проверяем:
[simterm]
$ ./getinput Enter your first name:abcde Enter your last name:acbde Hello, abcd, acbd
[/simterm]
Казалось бы — всё работает, как и ожидается. Но давайте внимательнее рассмотрим код:
...
fgets(firstname, 5, stdin);
printf("Enter your last name:");
// fflush(stdin); // This function may not (invariably) work with input!
flush_input();
fgets(lastname, 5, stdin);
flush_input();
...
Обратите внимание на функцию flush_input():
...
void flush_input(){
int ch;
while ((ch = getchar()) != '\n' && ch != EOF);
}
...
Зачем она нужна? Давайте закомментируем её вызов, и проверим:
...
void getinput_with_fgets() {
char firstname[5];
char lastname[5];
printf("Enter your first name:");
fgets(firstname, 5, stdin);
printf("Enter your last name:");
// fflush(stdin); // This function may not (invariably) work with input!
// flush_input();
fgets(lastname, 5, stdin);
// flush_input();
printf("Hello, %s, %s\n", firstname, lastname);
}
...
Запускаем:
[simterm]
$ ./getinput Enter your first name:abcde Enter your last name:Hello, abcd, e
[/simterm]
Упс… Наша программа теперь даже не вызывает fgets(lastname, 5, stdin), и вместо этого срабатывает printf() сразу же после fgets(firstname, 5, stdin), при этом присваивая «остаток» первой строки во второй массив lastname[].
Почему так происходит, для чего потребовалось явно создавать и вызывать функцию flush_input() — рассмотрим в следующей части.
Продолжение.
Предыдущая часть
5 282 views