Поиск по сайту:

Как (безопасно) читать пользовательский ввод с помощью функции 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 предлагает более гибкий способ считывания пользовательских данных в вашу программу без нарушения системы.

Статьи по данной тематике: