16 while (interpret(expression_value) && inrec(iop) == 0)
17 continue;
18 * Теперь развернуто для простоты отладки.
19 */
20 rvall = inrec(iop);
21 if (rvall == 0) {
22 for (;;) {
23 rval2 = rval3 = -1; /* для отладки */
24 rval2 = interpret(expression_value);
25 if (rval2 != 0)
26 rval3 = inrec(iop);
27 if (rval2 == 0 || rval3 != 0)
28 break;
29 }
30 }
31 if (exiting)
32 break;
33 }
34 }
(Номера
строк приведены относительно начала этой процедуры, а не файла.) Эта функция является основой главного цикла обработки
gawk
. Внешний цикл (строки 12 и 33) проходит через файлы данных командной строки. Комментарий в строках 13–19 показывает оригинальный код, который читает из текущего файла каждую запись и обрабатывает ее
Возвращаемое
inrec
значение 0 означает, что все в порядке, тогда как ненулевое возвращаемое значение
interpret
означает, что все в порядке. Когда мы попытались пройти через этот цикл, проверяя процесс чтения записей, возникла необходимость выполнить каждый шаг отдельно.
Строки 20–30 представляют переписанный код, который вызывает каждую функцию отдельно, сохраняя возвращаемые значения в локальных переменных, чтобы их можно было напечатать из отладчика. Обратите внимание, как в строке 23 этим переменным каждый раз присваиваются известные, ошибочные значения: в противном случае они могли бы сохранить свои значения от предыдущих итераций цикла. Строка 27 является тестом завершения, поскольку код изменился, превратившись в бесконечный цикл (сравните строку 22 со строкой 16), тест завершения цикла является противоположным первоначальному.
В качестве отступления, мы признаемся, что нам пришлось тщательно изучить переделку, когда мы ее сделали, чтобы убедиться, что она точно соответствует первоначальному коду; она соответствовала. Теперь нам кажется, что, возможно, вот эта версия цикла была бы ближе к оригиналу:
/* Возможная замена для строк 22 - 29 */
do {
rval2 = rval3 = -1; /* для отладки */
rval2 = interpret(expression_value);
if (rval2 != 0)
rval3 = inrec(iop);
} while (rval2 != 0 && rval3 == 0);
Правда в том, что обе версии труднее воспринимать, чем оригинал, и поэтому, возможно, содержат ошибки. Однако, поскольку текущий код работает, мы решили оставить как есть.
Наконец, мы обращаем внимание, что не все программисты-эксперты согласились бы здесь с нашим советом. Когда каждый компонент условия является вызовом функции, можно установить на каждую контрольную точку, использовать
step
для входа в каждую функцию, а затем использовать
finish
для ее завершения. GDB сообщит вам возвращаемое функцией значение, и с этого места вы можете использовать для продолжения
cont
или
step
. Нам нравится наш подход, поскольку результаты сохраняются в переменных, которые можно проверить (и неоднократно) после вызова функции и даже спустя несколько операторов.
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] , сохранение незначительного размера является важным.
170
Серьезно! Часто люди пропускают через
gawk
мегабайты данных. Помните, никаких произвольных ограничений! — Примеч. автора.
Что это должно делать с отладкой? Разве мы не рекомендовали только что использовать для именованных констант
enum
? Ну, в случае объединяемых побитовыми ИЛИ значений
enum
не помогают, поскольку они больше не являются индивидуально распознаваемыми!
Рекомендация: предусмотрите функцию для преобразования флагов в строки. Если у вас есть несколько независимых флагов, установите процедуру общего назначения.
ЗАМЕЧАНИЕ. Необычность этих функций отладки заключается в том, что код приложения никогда их не вызывает. Они существуют лишь для того, чтобы их можно было вызывать из отладчика. Такие функции всегда должны быть откомпилированы с кодом, даже без окружающих
#ifdef
, чтобы их можно было использовать. не предпринимая никаких дополнительных шагов. Увеличение (обычно минимальное) размера кода оправдывается экономией времени разработчика