Книга: Linux программирование в примерах

3.2.1.8. Пример: чтение строк произвольной длины

3.2.1.8. Пример: чтение строк произвольной длины

Поскольку это, в конце концов, Программирование на Linux в примерах, настало время для примера из реальной жизни. Следующий код является функцией readline() из GNU Make 3.80 (ftp://ftp.gnu.org/gnu/make/make-3.80.tar.gz). Ее можно найти в файле read.c.

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

Вторичной задачей является распоряжение продлением строк. Как и в С, строки, заканчивающиеся обратным слешем, логически продолжаются со следующей строки. Используется стратегия поддержания буфера. В нем хранится столько строк, сколько помещается в буфер, причем указатели отслеживают начало буфера, текущую строку и следующую строку. Вот структура:

struct ebuffer {
 char *buffer;      /* Начало текущей строки в буфере. */
 char *bufnext;     /* Начало следующей строки в буфере. */
 char *bufstart;    /* Начало всего буфера. */
 unsigned int size; /* Размер буфера для malloc. */
 FILE *fp;          /* Файл или NULL, если это внутренний буфер. */
 struct floc floc;  /* Информация о файле в fp (если он есть). */
};

Поле size отслеживает размер всего буфера, a fp является указателем типа FILE для файла ввода. Структура floc не представляет интереса при изучении процедуры.

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

1  static long
2  readline(ebuf) /* static long readline(struct ebuffer *ebuf) */
3  struct ebuffer *ebuf;
4  {
5   char *p;
6   char *end;
7   char *start;
8   long nlines = 0;
9
10  /* Использование строковых буферов и буферов потоков достаточно
11     различается, чтобы использовать разные функции. */
12
13  if (!ebuf->fp)
14   return readstring(ebuf);
15
16  /* При чтении из файла для каждой новой строки мы всегда
17     начинаем с начала буфера. */
18
19  p = start = ebuf->bufstart;
20  end = p + ebuf->size;
21  *p = '';

Для начала заметим, что GNU Make написан на С K&R для максимальной переносимости. В исходной части объявляются переменные, и если ввод осуществляется из строки (как в случае расширения макроса), код вызывает другую функцию, readstring() (строки 13 и 14). Строка '!ebuf->fp' (строка 13) является более короткой (и менее понятной, по нашему мнению) проверкой на пустой указатель; это то же самое, что и 'ebuf->fp==NULL'.

Строки 19-21 инициализируют указатели и вводят байт NUL, который является символом завершения строки С в конце буфера. Затем функция входит в цикл (строки 23–95), который продолжается до завершения всего ввода.

23 while (fgets(p, end - р, ebuf->fp) != 0)
24 {
25  char *p2;
26  unsigned long len;
27  int backslash;
28
29  len = strlen(p);
30  if (len == 0)
31  {
32   /* Это случается лишь тогда, когда первый символ строки ''.
33      Это довольно безнадежный случай, но (верите или нет) ляп Афины
34      бьет снова! (xmkmf помещает NUL в свои makefile.)
35      Здесь на самом деле нечего делать; мы создаем новую строку, чтобы
36      следующая строка не была частью данной строки. */
37   error (&ebuf->floc,
38    _("warning: NUL character seen; rest of line ignored"));
39   p[0] = 'n';
40   len = l;
41  }

Функция fgets() (строка 23) принимает указатель на буфер, количество байтов для прочтения и переменную FILE* для файла, из которого осуществляется чтение. Она читает на один байт меньше указанного, чтобы можно было завершить буфер символом ''. Эта функция подходит, поскольку она позволяет избежать переполнения буфера. Она прекращает чтение, когда встречается с символами конца строки или конца файла; если это символ новой строки, он помещается в буфер. Функция возвращает NULL при неудаче или значение указателя первого аргумента при успешном завершении.

В этом случае аргументами являются указатель на свободную область буфера, размер оставшейся части буфера и указатель FILE для чтения.

Комментарии в строках 32–36 очевидны; если встречается нулевой байт, программа выводит сообщение об ошибке и представляет вывод как пустую строку. После компенсирования нулевого байта (строки 30–41) код продолжает работу.

43 /* Обойти только что прочитанный текст. */
44 p += len;
45
46 /* Если последний символ - не конец строки, она не поместилась
47    целиком в буфер. Увеличить буфер и попытаться снова. */
48 if (p[-1] != 'n')
49  goto more_buffer;
50
51 /* Мы получили новую строку, увеличить число строк. */
52 ++nlines;

Строки 43–52 увеличивают указатель на участок буфера за только что прочитанными данными. Затем код проверяет, является ли последний прочитанный символ символом конца строки. Конструкция p[-1] (строка 48) проверяет символ перед p, также как p[0] является текущим символом, а p[1] — следующим. Сначала это кажется странным, но если вы переведете это на язык математики указателей, *(p-1), это приобретет больший смысл, а индексированная форма, возможно, проще для чтения.

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

54 #if !defined(WINDOWS32) && !defined(__MSDOS__)
55 /* Проверить, что строка завершилась CRLF; если так,
56    игнорировать CR. */
57 if ((p - start) > 1 && p[-2] == 'r')
58 {
59  --p;
60  p[-1] = 'n';
61 }
62 #endif

Строки 54–62 обрабатывают вводимые строки, следующие соглашению Microsoft по завершению строк комбинацией символов возврата каретки и перевода строки (CR-LF), а не просто символом перевода строки (новой строки), который является соглашением Linux/Unix. Обратите внимание, что #ifdef исключает этот код на платформе Microsoft, очевидно, библиотека <stdio.h> на этих системах автоматически осуществляет это преобразование. Это верно также для других не-Unix систем, поддерживающих стандартный С.

64  backslash = 0;
65  for (p2 = p - 2; p2 >= start; --p2)
66  {
67   if (*p2 != '')
68   break;
69   backslash = !backslash;
70  }
71
72  if (!backslash)
73  {
74   p[-1] = '';
75   break;
76  }
77
78  /* Это была комбинация обратный слеш/новая строка. Если есть
79     место, прочесть еще одну строку. */
80  if (end - p >= 80)
81   continue;
82
83  /* В конце буфера нужно больше места, поэтому выделить еще.
84     Позаботиться о сохранении текущего смещения в p. */
85 more_buffer:
86  {
87   unsigned long off = p - start;
88   ebuf->size *= 2;
89   start = ebuf->buffer=ebuf->bufstart=(char*)xrealloc(start,
90    ebuf->size);
91   p = start + off;
92   end = start + ebuf->size;
93   *p = '';
94  }
95 }

До сих пор мы имели дело с механизмом получения в буфер по крайней мере одной полной строки. Следующий участок обрабатывает случай строки с продолжением. Хотя он должен гарантировать, что конечный символ обратного слеша не является частью нескольких обратных слешей в конце строки. Код проверяет, является ли общее число таких символов четным или нечетным путем простого переключения переменной backslash из 0 в 1 и обратно. (Строки 64–70.)

Если число четное, условие '!backshlash' (строка 72) будет истинным. В этом случае конечный символ конца строки замещается байтом NUL, и код выходит из цикла.

С другой стороны, если число нечетно, строка содержит четное число пар обратных слешей (представляющих символы , как в С), и конечную комбинацию символов обратного слеша и конца строки.[43] В этом случае, если в буфере остались по крайней мере 80 свободных байтов, программа продолжает чтение в цикле следующей строки (строки 78–81). (Использование магического числа 80 не очень здорово; было бы лучше определить и использовать макроподстановку.)

По достижении строки 83 программе нужно больше места в буфере. Именно здесь вступает в игру динамическое управление памятью. Обратите внимание на комментарий относительно сохранения значения p (строки 83-84); мы обсуждали это ранее в терминах повторной инициализации указателей для динамической памяти. Значение end также устанавливается повторно. Строка 89 изменяет размер памяти.

Обратите внимание, что здесь вызывается функция xrealloc(). Многие программы GNU используют вместо malloc() и realloc() функции-оболочки, которые автоматически выводят сообщение об ошибке и завершают программу, когда стандартные процедуры возвращают NULL. Такая функция-оболочка может выглядеть таким образом:

extern const char *myname; /* установлено в main() */
void *xrealloc(void *ptr, size_t amount) {
 void *p = realloc(ptr, amount);
 if (p == NULL) {
  fprintf(stderr, "%s: out of memory!n", myname);
  exit(1);
 }
 return p;
}

Таким образом, если функция xrealloc() возвращается, она гарантированно возвращает действительный указатель. (Эта стратегия соответствует принципу «проверки каждого вызова на ошибки», избегая в то же время беспорядка в коде, который происходит при таких проверках с непосредственным использованием стандартных процедур.) Вдобавок, это позволяет эффективно использовать конструкцию 'ptr = xrealloc(ptr, new_size)', против которой мы предостерегали ранее.

Обратите внимание, что не всегда подходит использование такой оболочки. Если вы сами хотите обработать ошибки, не следует использовать оболочку. С другой стороны, если нехватка памяти всегда является фатальной ошибкой, такая оболочка вполне удобна.

97   if (ferror(ebuf->fp))
98    pfatal_with_name(ebuf->floc.filenm);
99
100  /* Если обнаружено несколько строк, возвратить их число.
101     Если не несколько, но _что-то_ нашли, значит, прочитана
102     последняя строка файла без завершающего символа конца
103     строки; вернуть 1. Если ничего не прочитано, это EOF;
104     возвратить -1. */
105  return nlines ? nlines : p == ebuf->bufstart ? -1 : 1;
106 }

В заключение, функция readline() проверяет ошибки ввода/вывода, а затем возвращает описательное значение. Функция pfatal_with_name() (строка 98) не возвращается.[44]

Оглавление книги


Генерация: 0.071. Запросов К БД/Cache: 0 / 0
поделиться
Вверх Вниз