getline()
- это функция POSIX.1 , которая считывает строки в динамически размещенные буферы, позволяя использовать строки любой длины (ограниченные только объемом доступной памяти).Он возвращает количество прочитанных символов или -1, если больше нет ввода или произошла ошибка.
Вот один пример использования шаблона:
char *line = NULL;
size_t size = 0;
ssize_t len;
while (1) {
len = getline(&line, &size, stdin);
if (len < 1)
break;
/* Do something with the input line */
}
В любой момент выможет освободить динамически выделенный буфер, используя
free(line);
line = NULL;
size = 0;
Причина, по которой вы хотите очистить указатель на NULL и размер до нуля, заключается в том, что вы случайно не пытаетесь получить доступ к уже освобожденной памяти, но вы можетевызовите getline(&line, &size, handle)
, чтобы прочитать больше строк, так как вызов просто распознает, что у него нет буфера, и выделит новый.
Вы можете манипулировать динамическими данными любым способом, если хотите, еслиВы осторожны.Например:
while (1) {
char *line = NULL;
size_t size = 0;
ssize_t len;
len = getline(&line, &size, stdin);
if (len < 1) {
free(line);
break;
}
/* Do something with the contents of the line */
free(line);
}
будет работать, но это будет довольно медленно, поскольку библиотека C будет выполнять по крайней мере один вызов malloc()
для каждой прочитанной строки и, возможно, дополнительные вызовы realloc()
, в зависимости отдлина строки.
Причина, по которой getline()
написана как есть, заключается в том, что она позволяет повторно использовать один и тот же буфер для любого количества строк.Если вы читаете файлы последовательно, вы можете использовать один и тот же буфер.Давайте рассмотрим более сложный пример:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <locale.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
int main(int argc, char *argv[])
{
unsigned long linenum;
char *line = NULL, *in, *out, *end;
size_t size = 0, n;
ssize_t len;
FILE *src;
int arg;
if (!setlocale(LC_ALL, ""))
fprintf(stderr, "Warning: Your C library does not support your current locale.\n");
if (argc < 2) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s FILENAME [ FILENAME ... ]\n", argv[0]);
fprintf(stderr, "\n");
exit(EXIT_FAILURE);
}
for (arg = 1; arg < argc; arg++) {
src = fopen(argv[arg], "r");
if (!src) {
fprintf(stderr, "%s: %s.\n", argv[arg], strerror(errno));
free(line);
exit(EXIT_FAILURE);
}
linenum = 0;
while (1) {
len = getline(&line, &size, src);
if (len < 1)
break;
linenum++;
/* First character in the line read: */
in = line;
out = line;
/* Pointer to the end-of-string character on the line: */
end = line + len;
/* Skip all leading whitespace characters. */
while (in < end && isspace((unsigned char)(*in)))
in++;
/* Character copy loop. */
while (in < end)
if (isspace((unsigned char)(*in))) {
/* Replace consecutive whitespace characters with spaces. */
*(out++) = ' ';
do {
in++;
} while (in < end && isspace((unsigned char)(*in)));
} else {
/* Copy all other characters as-is. */
*(out++) = *(in++);
}
/* There may be a single space before out. Backtrack it, if so. */
if (out > line && out[-1] == ' ')
out--;
/* Mark the end of the string at out. */
*out = '\0';
/* Calculate the new length, just for output purposes. */
n = (size_t)(out - line);
/* Print the line. */
printf("%s: Line %lu: '%s' (%zu of %zd characters)\n",
argv[arg], linenum, line, n, len);
}
if (!feof(src) || ferror(src)) {
fprintf(stderr, "%s: Read error.\n", argv[arg]);
fclose(src);
free(line);
exit(EXIT_FAILURE);
}
if (fclose(src)) {
fprintf(stderr, "%s: Error closing file: %s.\n",
argv[arg], strerror(errno));
free(line);
exit(EXIT_FAILURE);
}
}
free(line);
line = NULL;
size = 0;
return EXIT_SUCCESS;
}
Если мы сохраним вышеизложенное как, например, example.c , скомпилируем его, например, gcc -Wall -O2 example.c -o example
, и запустим программу, предоставивимена текстовых файлов в качестве параметров, например, ./example example.c
, он будет выводить что-то вроде
example.c: Line 1: '#define _POSIX_C_SOURCE 200809L' (31 of 33 characters)
example.c: Line 2: '#include <stdlib.h>' (19 of 20 characters)
example.c: Line 3: '#include <locale.h>' (19 of 20 characters)
example.c: Line 4: '#include <string.h>' (19 of 20 characters)
example.c: Line 5: '#include <stdio.h>' (18 of 19 characters)
example.c: Line 6: '#include <ctype.h>' (18 of 19 characters)
example.c: Line 7: '#include <errno.h>' (18 of 19 characters)
example.c: Line 8: '' (0 of 1 characters)
example.c: Line 9: 'int main(int argc, char *argv[])' (32 of 33 characters)
Что делает программа, это просто читать каждый указанный файл построчно, удалять любые начальные и конечные пробелы накаждую строку и объединить все последовательные пробелы в один пробел.Меньшее количество символов - это количество оставленных символов (и показанных), большее число - исходное количество символов, считанных из файла.
Дополнительные примечания к примеру программы, если она вас заинтересует
Вызов setlocale(LC_ALL, "")
говорит вашей библиотеке C использовать локаль пользователя (обычно определяется в переменных окружения LANG
или LC_ALL
).Эта программа использует только определения типов символов для набора символов, используемого текущей локалью (чтобы определить, какие коды являются «пробелами»), поэтому это также может быть ограничено с помощью setlocale(LC_CTYPE, "")
.Вызов вернет NULL, если текущая локаль не поддерживается библиотекой C.Обычно это происходит из-за ошибки в пользовательской конфигурации, поэтому полезно, чтобы программа предупреждала затем.
isspace()
(и все другие функции is*()
, определенные в <ctype.h>
) взять код без знака (или EOF).Поскольку тип char
может быть подписанным или без знака, мы явно приводим символ к (unsigned char)
перед передачей в функцию.Рассмотрим этот глупый исторический багаж, с которым нам просто нужно иметь дело.
Поскольку line
указывает на начало динамически распределяемого буфера памяти, мы не должны его изменять (кроме как черезrealloc()
или free()
и затем установите на NULL
).Если мы изменим его, любой последующий вызов getline()
или free()
с использованием этого указателя, скорее всего, выйдет из строя и приведет к аварийному завершению программы, поскольку им действительно нужен указатель, указывающий на начало буфера, а не только где-то внутри него.
Мне нравится использовать указатели (char *in, *out, *end
) вместо индексов.Здесь in
начинается с line
и поднимается до line+len
, но не включает *1072*, где getline()
ставит nul \0
конца строки, чтобы указать конец строки.Вот почему я также часто использую указатель с именем end
, чтобы указать на это.out
начинается также с line
, но увеличивается только тогда, когда символы находятся в строке.
Если вы думаете о ряду плиток с буквами, как в скрэббле, out
указывает на следующую позициюВы положите сохраненную плитку, и in
указывает на следующую плитку, которую вы получите.
Когда getline()
или getdelim()
возвращает ноль или отрицательное значение (или fgets()
возвращает NULL), это означает, что либо не было больше данных для чтения, либо операция завершилась неудачно по какой-либо другой причине .
После цикла (!feof(src) || ferror(src))
проверяет, был ли входной поток прочитан полностью без ошибок. Ну, скорее, обратное: выражение истинно, только если произошла ошибка или весь файл не был прочитан.
Если я записал данные в какой-нибудь файл, скажем, FILE *dst
, я обычно предшествую этому тесту с тестом if (fflush(dst))
. Это правда, если при записи в файл последней из данных, буферизованных библиотекой C, произошла ошибка.
fclose(src)
закрывает файл. Лично я предпочитаю проверять его возвращаемое значение, потому что, хотя в настоящее время он может потерпеть неудачу только в очень специфических обстоятельствах, я, как пользователь, определенно предпочел бы знать, были ли у ОС проблемы с записью моих данных! Тест в основном ничего не стоит, но может иметь решающее значение для пользователя. Я не хочу, чтобы какие-либо программы «забыли», говоря мне, что при работе с моими данными возникла проблема; мои данные важны для меня.
free(NULL)
безопасен и ничего не делает. (Кроме того, realloc(NULL, size)
эквивалентно malloc(size)
, поэтому, если вы инициализируете указатель на NULL, вам не нужен начальный malloc, вы можете просто realloc()
всегда получить нужный вам размер.)
Я предлагаю вам поиграть с приведенным выше кодом. Вы даже можете запустить его в ltrace (ltrace ./example example.c
), чтобы увидеть, какие стандартные вызовы библиотеки C фактически выполняются, и их результаты; или под strace (strace ./example example.c
), чтобы увидеть системные вызовы (от процесса до самого ядра ОС).
В качестве примера можно добавить, скажем,
if (linenum == 7) {
/* We skip line 7, and even destroy it! Muahhahah! */
free(line);
line = NULL;
size = 0;
continue;
}
сразу после строки linenum++
, чтобы увидеть, что происходит с седьмыми строками текстовых файлов. (Они пропускаются, и даже если буфер освобождается, ничего плохого не происходит (потому что continue
запускает следующую итерацию тела цикла while), так как следующий getline()
просто динамически выделит новую строку.
Если вы решили сохранить копию части строки, просто рассчитайте необходимую длину, добавив ее для nul в конце строки (\0
), выделите столько символов для дубликата (* 1130) * в C всегда; поэтому malloc () и др. берут количество символов, выделяемых для действительно), memcpy()
данных и добавляют завершающий nul. Например,
char *sdup(const char *const source, const size_t length)
{
char *s;
s = malloc(length + 1);
if (!s) {
/* Either return NULL, or: */
fprintf(stderr, "sdup(): Not enough memory for %zu chars.\n", length + 1);
exit(EXIT_FAILURE);
}
if (length > 0)
memcpy(s, source, length);
s[length] = '\0';
return s;
}
Если вам нужна полная строка (до конца строки nul), вы можете использовать вместо нее POSIX.1-2008 strdup()
.