ЗАМЕЧАНИЕ. Начиная с GCC 3.1 и версии 5 GDB, если вы компилируете свою программу с опциями -gdwarf-2
и -g3
, вы можете использовать макросы из-под GDB. В руководстве по GDB утверждается, что разработчики GDB надеются найти в конце концов более компактное представление для макросов, и что опция -g3
будет отнесена к группе -g
.
Однако, использовать макросы таким способам позволяет лишь комбинация GCC, GDB и специальных опций: если вы не используете GCC (или если вы используете более старую версию), у вас все еще есть проблема. Мы придерживаемся своей рекомендации избегать по возможности таких макросов.
Проблема с макросами распространяется также и на фрагменты кода. Если макрос определяет несколько операторов, вы не можете установить контрольную точку в середине макроса. Это верно также для inline-функций C99 и С++: если компилятор заменяет тело inline-функции сгенерированным кодом, снова невозможно или трудно установить внутри него контрольную точку. Это имеет связь с нашим советом компилировать лишь с одной опцией -g
; в этом случае компиляторы обычно не используют inline-функции.
Обычно с такими строками используется переменная, представляющая определенное состояние. Довольно просто, и это рекомендуется многими книгами по программированию на С, определять с помощью #define
для таких состояний именованные константы. Например:
/* Различные состояния, в которых можно
находиться при поиске конца записи. */
#define NOSTATE 1 /* сканирование еще не началось (все) */
#define INLEADER 2 /* пропуск начальных данных (RS = "") */
#define INDATA 3 /* в теле записи (все) */
#define INTERM 4 /* терминатор сканирования (RS = RS = regexp) */
int state;
...
state = NOSTATE;
...
state = INLEADER;
...
if (state != INTERM) ...
На уровне исходного кода это выглядит замечательно. Но опять-таки, есть проблема, когда вы пытаетесь просмотреть код из GDB:
(gdb) print state
$1 = 2
Здесь вы также вынуждены возвращаться обратно и смотреть в заголовочный файл, чтобы выяснить, что означает 2. Какова же альтернатива?
Рекомендация: Для определения именованных констант используйте вместо макросов перечисления (enum). Использование исходного кода такое же, а значения enum может выводить также и отладчик.
Пример, тоже из io.c
в gawk
:
typedef enum scanstate {
NOSTATE, /* сканирование еще не начато (все) */
INLEADER, /* пропуск начальных данных (RS = "") */
INDATA, /* в теле записи (все) */
INTERM, /* терминатор сканирования (RS = "", RS = regexp) */
} SCANSTATE;
SCANSTATE state;
/* ... остальной код без изменений! ... */
Теперь при просмотре state из GDB мы видим что-то полезное:
(gdb) print state
$1 = NOSTATE
15.4.1.3. При необходимости переставляйте код
Довольно часто условие в if
или while
состоит из нескольких проверок, разделенных &&
или ||
. Если эти проверки являются вызовами функций (или даже не являются ими), невозможно осуществить пошаговое прохождение каждой отдельной части условия. Команды GDB step
и next
работают на основе
Рекомендация: перепишите исходный код, явно используя временные переменные, в которых сохраняются значения или условные результаты, так что вы можете проверить их в отладчике. Первоначальный код должен быть сохранен в комментарии, чтобы вы (или программист после вас) могли сказать, что происходит.
Вот конкретный пример: функция do_input()
из файла io.c gawk
:
1 /* do_input --- главный цикл обработки ввода */
2
3 void
4 do_input()