5 {
6 IOBUF *iop;
7 extern int exiting;
8 int rval1, rval2, rval3;
9
10 (void)setjmp(filebuf); /* for 'nextfile' */
11
12 while ((iop = nextfile(FALSE)) != NULL) {
13 /*
14 * Здесь было:
15 if (inrec(iop) == 0)
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
. Нам нравится наш подход, поскольку результаты сохраняются в переменных, которые можно проверить (и неоднократно) после вызова функции и даже спустя несколько операторов.
Типичной методикой, применимой во многих случаях, является использование набора значений #define
и битовых операторов С. (Использование битовых флагов и операторы работы с битами мы обсуждали во врезке к разделу 8.3.1 «Стиль POSIX: statvfs()
и fstatvfs()
».)
Например, главная структура данных gawk
называется NODE
. У нее большое количество полей, последнее из которых является набором значений флагов. Из файла awk.h
:
typedef struct exp_node {
/* ... Куча материала опущена */
unsigned short flags;
#define MALLOC 1 /* может быть освобожден */