Как (безопасно) читать пользовательский ввод с помощью функции getline
Getline предлагает более гибкий способ считывания пользовательских данных в вашу программу без нарушения системы.
Раньше чтение строк в C было очень опасным занятием. При чтении вводимых пользователем данных у программистов может возникнуть соблазн использовать функцию gets
из стандартной библиотеки C. Использование gets
достаточно просто:
char *gets(char *string);
То есть gets
считывает данные из стандартного ввода и сохраняет результат в строковой переменной. Использование gets
возвращает указатель на строку или значение NULL, если ничего не было прочитано.
В качестве простого примера мы можем задать пользователю вопрос и прочитать результат в строку:
#include <stdio.h>
#include <string.h>
int
main()
{
char city[10]; // Such as "Chicago"
// this is bad .. please don't use gets
puts("Where do you live?");
gets(city);
printf("<%s> is length %ld\n", city, strlen(city));
return 0;
}
Ввод относительно короткого значения с помощью приведенной выше программы работает достаточно хорошо:
Where do you live?
Chicago
<Chicago> is length 7
Однако функция gets
очень проста и будет считывать данные до тех пор, пока не решит, что пользователь закончил. Но gets
не проверяет, достаточна ли длина строки для хранения введенных пользователем данных. Ввод очень длинного значения приведет к тому, что gets
сохранит больше данных, чем может вместить строковая переменная, что приведет к перезаписи других частей памяти.
Where do you live?
Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch
<Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch> is length 58
Segmentation fault (core dumped)
В лучшем случае перезапись частей памяти просто сломает программу. В худшем случае это приводит к критической ошибке безопасности, когда злоумышленник может вставить произвольные данные в память компьютера через вашу программу.
Вот почему функцию gets
опасно использовать в программе. Используя gets
, вы не можете контролировать, сколько данных ваша программа пытается прочитать от пользователя. Это часто приводит к переполнению буфера.
Функция fgets
исторически была рекомендуемым способом безопасного чтения строк. Эта версия gets
обеспечивает проверку безопасности, считывая только определенное количество символов, передаваемых в качестве аргумента функции:
char *fgets(char *string, int size, FILE *stream);
Функция fgets
считывает данные из указателя файла и сохраняет данные в строковую переменную, но только до длины, указанной в size
. Мы можем проверить это, обновив наш пример программы, чтобы он использовал fgets
вместо gets
:
#include <stdio.h>
#include <string.h>
int
main()
{
char city[10]; // Such as “Chicago”
// fgets is better but not perfect
puts(“Where do you live?”);
fgets(city, 10, stdin);
printf("<%s> is length %ld\n", city, strlen(city));
return 0;
}
Если вы скомпилируете и запустите эту программу, вы сможете ввести в командной строке произвольное длинное название города. Однако программа прочитает только столько данных, сколько поместится в строковую переменную size
=10. А поскольку C добавляет нулевой символ («\0») в конец строки, это означает, что fgets
будет считывать в строку только 9 символов:
Where do you live?
Minneapolis
<Minneapol> is length 9
Хотя это, безусловно, безопаснее, чем использование fgets
для чтения пользовательского ввода, это происходит за счет «отсечения» пользовательского ввода, если он слишком длинный.
Более гибкое решение для чтения длинных данных — позволить функции чтения строк выделять больше памяти для строки, если пользователь ввел больше данных, чем может вместить переменная. При необходимости изменяя размер строковой переменной, в программе всегда будет достаточно места для хранения введенных пользователем данных.
Функция getline
делает именно это. Эта функция считывает вводимые данные из потока ввода, например с клавиатуры или файла, и сохраняет данные в строковой переменной. Но в отличие от fgets
и gets
, getline
изменяет размер строки с помощью realloc
, чтобы обеспечить достаточно памяти для хранения всей строки. вход.
ssize_t getline(char **pstring, size_t *size, FILE *stream);
getline
на самом деле является оболочкой аналогичной функции под названием getdelim
, которая считывает данные до специального символа-разделителя. В этом случае getline
использует новую строку ('\n') в качестве разделителя, поскольку при чтении пользовательского ввода с клавиатуры или из файла строки данных разделяются символом новой строки.
В результате получается гораздо более безопасный метод чтения произвольных данных по одной строке за раз. Чтобы использовать getline
, определите указатель строки и установите для него значение NULL, чтобы указать, что память еще не выделена. Также определите переменную «размер строки» типа size_t
и присвойте ей нулевое значение. Когда вы вызываете getline
, вы будете использовать указатели как на строку, так и на переменные размера строки, а также указывать, где читать данные. Для примера программы мы можем прочитать из стандартного ввода:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int
main()
{
char *string = NULL;
size_t size = 0;
ssize_t chars_read;
// read a long string with getline
puts("Enter a really long string:");
chars_read = getline(&string, &size, stdin);
printf("getline returned %ld\n", chars_read);
// check for errors
if (chars_read < 0) {
puts("couldn't read the input");
free(string);
return 1;
}
// print the string
printf("<%s> is length %ld\n", string, strlen(string));
// free the memory used by string
free(string);
return 0;
}
Когда getline
считывает данные, он автоматически перераспределяет больше памяти для строковой переменной по мере необходимости. Когда функция прочитала все данные из одной строки, она обновляет размер строки через указатель и возвращает количество прочитанных символов, включая разделитель.
Enter a really long string:
Supercalifragilisticexpialidocious
getline returned 35
<Supercalifragilisticexpialidocious
> is length 35
Обратите внимание, что строка включает символ-разделитель. Для getline
разделителем является новая строка, поэтому в выводе присутствует перевод строки. Если вам не нужен разделитель в строковом значении, вы можете использовать другую функцию, чтобы изменить разделитель на нулевой символ в строке.
С помощью getline
программисты могут безопасно избежать одной из распространенных ошибок программирования на C. Вы никогда не можете предсказать, какие данные может попытаться ввести ваш пользователь, поэтому использование gets
небезопасно, а fgets
неудобно. Вместо этого getline
предлагает более гибкий способ считывания пользовательских данных в вашу программу без нарушения системы.