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

15.4.1.4. Используйте вспомогательные функции отладки

15.4.1.4. Используйте вспомогательные функции отладки

Типичной методикой, применимой во многих случаях, является использование набора значений флагов; когда флаг установлен (т.е. равен true), имеет место определенный факт или применяется определенное условие. Обычно это осуществляется при помощи именованных констант #define и битовых операторов С. (Использование битовых флагов и операторы работы с битами мы обсуждали во врезке к разделу 8.3.1 «Стиль POSIX: statvfs() и fstatvfs()».)

Например, главная структура данных gawk называется NODE. У нее большое количество полей, последнее из которых является набором значений флагов. Из файла awk.h:

typedef struct exp_node {
 /* ... Куча материала опущена */
 unsigned short flags;
#define MALLOC       1 /* может быть освобожден */
#define TEMP         2 /* должен быть освобожден */
#define PERM         4 /* не может быть освобожден */
#define STRING       8 /* назначен в виде строки */
#define STRCUR      16 /* текущее значение строковое */
#define NUMCUR      32 /* текущее значение числовое */
#define NUMBER      64 /* назначен в виде числа */
#define MAYBE_NUM  128 /* ввод пользователя: если NUMERIC, тогда
                        * NUMBER */
#define ARRAYMAXED 256 /* размер массива максимальный */
#define FUNC       512 /* параметр представляет имя функции;
                        * см. awkgram.y */
#define FIELD     1024 /* это является полем */
#define INTLSTR   2048 /* использовать локализованную версию */
} NODE;

Причина для использования значений флагов заключается в том, что они значительно экономят пространство данных. Если бы структура NODE для каждого флага использовала отдельное поле char, потребовалось бы 12 байтов вместо 2, используемых unsigned short. Текущий размер NODE (на Intel x86) 32 байта. Добавление лишних 10 байтов увеличило бы ее до 42 байтов. Поскольку gawk может потенциально выделять сотни и тысячи (или даже миллионы) NODE[170], сохранение незначительного размера является важным.

Что это должно делать с отладкой? Разве мы не рекомендовали только что использовать для именованных констант enum? Ну, в случае объединяемых побитовыми ИЛИ значений enum не помогают, поскольку они больше не являются индивидуально распознаваемыми!

Рекомендация: предусмотрите функцию для преобразования флагов в строки. Если у вас есть несколько независимых флагов, установите процедуру общего назначения.

ЗАМЕЧАНИЕ. Необычность этих функций отладки заключается в том, что код приложения никогда их не вызывает. Они существуют лишь для того, чтобы их можно было вызывать из отладчика. Такие функции всегда должны быть откомпилированы с кодом, даже без окружающих #ifdef, чтобы их можно было использовать. не предпринимая никаких дополнительных шагов. Увеличение (обычно минимальное) размера кода оправдывается экономией времени разработчика

Сначала мы покажем вам, как мы это делали первоначально. Вот (сокращенная версия) flags2str() из ранней версии gawk (3.0.6):

1  /* flags2str --- делает значения флагов удобочитаемыми */
2
3  char *
4  flags2str(flagval)
5  int flagval;
6  {
7   static char buffer[BUFSIZ];
8   char *sp;
9
10  sp = buffer;
11
12  if (flagval & MALLOC) {
13   strcpy(sp, "MALLOC");
14   sp += strlen(sp);
15  }
16  if (flagval & TEMP) {
17   if (sp >= buffer)
18   *sp++ = '|';
19   strcpy(sp, "TEMP");
20   sp += strlen(sp);
21  }
22  if (flagval & PERM) {
23   if (sp != buffer)
24    *sp++ = '|';
25   strcpy(sp, "PERM");
26   sp += strlen(sp);
27  }
    /* ...многое то же самое, опущено для краткости... */
82
83  return buffer;
84 }

(Номера строк даны относительно начала функции.) Результатом является строка, что- то наподобие "MALLOC | PERM | NUMBER". Каждый флаг тестируется отдельно, и если он присутствует, действие каждый раз одно и то же: проверка того, что он не в начале буфера и что можно добавить символ '|', скопировать строку на место и обновить указатель. Сходные функции существовали для форматирования и отображения других видов флагов в программе.

Этот код является повторяющимся и склонным к ошибкам, и для gawk 3.1 мы смогли упростить и обобщить его. Вот как gawk делает это сейчас. Начиная с этого определения в awk.h:

/* для целей отладки */
struct flagtab {
 int val;          /* Целое значение флага */
 const char *name; /* Строковое имя */
};

Эту структуру можно использовать для представления любого набора флагов с соответствующими строковыми значениями. Каждая отдельная группа флагов имеет соответствующую функцию, которая возвращает печатное представление флагов, которые установлены в настоящее время. Из eval.c:

/* flags2str --- делает значения флагов удобочитаемыми */
const char *flags2str(int flagval) {
 static const struct flagtab values[] = {
  { MALLOC, "MALLOC" },
  { TEMP, "TEMP" },
  { PERM, "PERM" },
  { STRING, "STRING" },
  { STRCUR, "STRCUR" },
  { NUMCUR, "NUMCUR" },
  { NUMBER, "NUMBER" },
  { MAYBE_NUM, "MAYBE_NUM" },
  { ARRAYMAXED, "ARRAYMAXED" },
  { FUNC, "FUNC" },
  { FIELD, "FIELD" },
  { INTLSTR, "INTLSTR" },
  { 0, NULL },
 };
 return genflags2str(flagval, values);
}
flags2str()
определяет массив сопоставлений флагов со строками. По соглашению, значение флага 0 означает конец массива. Код вызывает для осуществления работы genflags2str() («общий флаг в строку»). getflags2str() является процедурой общего назначения, которая преобразует значение флага в строку. Из eval.c:

1  /* genflags2str --- общая процедура для преобразования значения флага в строковое представление */
2
3  const char *
4  genflags2str(int flagval, const struct flagtab *tab)
5  {
6   static char buffer(BUFSIZ];
7   char *sp;
8   int i, space_left, space_needed;
9
10  sp = buffer;
11  space_left = BUFSIZ;
12  for (i = 0; tab[i].name != NULL; i++) {
13   if ((flagval & tab[i].val) != 0) {
14    /*
15     * обратите внимание на уловку, нам нужны 1 или 0, чтобы
16     * определить, нужен ли нам символ '|'.
17     */
18    space_needed = (strlen(tab[i].name) + (sp != buffer));
19    if (space_left < space_needed)
20     fatal(_("buffer overflow in genflags2str"));
21
22    if (sp >= buffer) {
23     *sp++ = '|';
24     space_left--;
25    }
26    strcpy(sp, tab[i].name);
27    /* обратите внимание на расположение! */
28    space_left -= strlen(sp);
29    sp += strlen(sp);
30   }
31  }
32
33  return buffer;
34 }

(Номера строк приведены относительно начала функции, а не файла.) Как и в предыдущей версии, идея заключалась в заполнении статического буфера строковыми значениями, такими, как "MALLOC | PERM | STRING | MAYBE_NUM", и возвращении адреса этого буфера. Мы вскоре обсудим причины использования статического буфера; сначала давайте исследуем код.

Указатель sp отслеживает положение следующего пустого слота в буфере, тогда как space_left отслеживает количество оставшегося места; это уберегает нас от переполнения буфера.

Основную часть функции составляет цикл (строка 12), проходящий через массив значений флагов. Когда флаг найден (строка 13), код вычисляет, сколько места требуется строке (строка 18) и проверяет, осталось ли столько места (строки 19–20).

Тест 'sp ! = buffer' для первого значения флага завершается неудачей, возвращая 0. Для последующих флагов тест дает значение 1. Это говорит нам, что между значениями должен быть вставлен разделительный символ '|'. Добавляя результат (1 или 0) к длине строки, мы получаем правильное значение space_needed. Тот же тест с той же целью проводится в строке 22 для проверки строк 23 и 24, которые вставляют символ '|'.

В заключение строки 26–29 копируют значение строки, выверяют количество оставшегося места и обновляют указатель sp. Строка 33 возвращает адрес буфера, который содержит печатное представление строки.

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

Более того, статический буфер по определению является буфером фиксированного размера. Что случилось с принципом GNU «никаких произвольных ограничений»?

Для ответа на эти вопросы нужно вспомнить, что это отладочная функция. Обычный код никогда не вызывает getflags2str(); она вызывается лишь человеком, использующим отладчик. Ни у одного вызывающего нет указателя на буфер; как разработчику, осуществляющему отладку, нам нет дела до того, что буфер каждый раз переписывается при вызове функции.

На практике фиксированный размер также не является проблемой; мы знаем, что размер BUFSIZ достаточен для представления всех флагов, которые мы используем. Тем не менее, поскольку мы опытные и знаем, что вещи могут измениться, в getflags2str() есть код, предохраняющий себя от переполнения буфера. (Переменная space_left и код в строках 18–20.)

В качестве отступления, использование BUFSIZ спорно. Эта константа должна использоваться исключительно для буферов ввода/вывода, но часто она используется также для общих строковых буферов. Такой код лучше убрать, определив явные константы, такие, как FLAGVALSIZE, и использовав в строке 11 'sizeof (buffer)'.

Вот сокращенный сеанс GDB, показывающий использование flags2str():

$ gdb gawk /* Запустить GDB с gawk */
GNU gdb 5.3
...
(gdb) break do_print /* Установить контрольную точку */
Breakpoint 1 at 0x805a584: file builtin.c, line 1547.
(gdb) run 'BEGIN { print "hello, world" }' /* Запустить программу */
Starting program: /home/arnold/Gnu/gawk/gawk-3.1.4/gawk 'BEGIN { print "hello, world" }'
Breakpoint 1, do_print (tree=0x80955b8) at builtin.c: 1547 /* Останова в контрольной точке */
1547 struct redirect *rp = NULL;
(gdb) print *tree /* Вывести NODE */
$1 = {sub = {nodep =
 {1 = {lptr = 0x8095598, param_name = 0x8095598 "xUtb",
 ll = 134629464}, r = {rptr = 0x0, pptr = 0, preg = 0x0, hd = 0x0,
 av = 0x0, r_ent =0}, x = {extra = 0x0, xl = 0, param_list = 0x0},
 name = 0x0, number = 1, reflags = 0), val = {
 fltnum = 6.6614606209589101e-316, sp = 0x0, slen = 0, sref = 1,
 idx = 0}, hash = {next = 0x8095598, name = 0x0, length = 0, value = 0x0,
 ref = 1}}, type = Node_K_print, flags = 1}
(gdb) print flags2str(tree->flags) /* Вывести значение флага */
$2 = 0x80918a0 "MALLOC"
(gdb) next /* Продолжить */
1553 fp = redirect_to_fp(tree->rnode, &rp);
...
1588 efwrite(t[i]->stptr, sizeof(char), t[i]->stlen, fp, "print", rp, FALSE);
(gdb) print *t[i] /* Снова вывести NODE */
$4 = {sub = {nodep =
 {l = {lptr = 0x8095598, parm_name = 0x8095598 "xUtb",
 ll = 134829464}, r = {rptr = 0x0, pptr = 0, preg = 0x0, hd = 0x0,
 av = 0x0, r_ent =0), x = {extra = 0x8095ad8, xl = 134830808,
 param_list = 0x8095ad8}, name = 0xc <Address 0xc out of bounds>,
 number = 1, reflags = 4294967295}, val = {
 fltnum = 6.6614606209589101e-316, sp = 0x8095ad8 "hello, world",
 slen = 12, sref = 1, idx = -1}, hash = {next = 0x8095598, name = 0x0,
 length = 134830808, value = 0xc, ref = 1}}, type = Node_val, flags = 29}
(gdb) print flags2str(t[i]->flags) /* Вывести значение флага */
$5 = 0x80918a0 "MALLOC|PERM|STRING|STRCUR"

Надеемся, вы согласитесь, что настоящий механизм общего назначения значительно более элегантный и более простой в использовании, чем первоначальный.

Тщательное проектирование и использование массивов структур часто может заменить или слить воедино повторяющийся код.

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


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