Книга: Linux программирование в примерах
15.4.1.2. По возможности избегайте макросов с выражениями
15.4.1.2. По возможности избегайте макросов с выражениями
В общем, макросы препроцессора С являются довольно острой палкой с двумя концами. Они предоставляют вам большую мощь, но также и большую возможность пораниться самому.[169]
Обычно для эффективности или ясности можно видеть такие макросы:
#define RS_is_null (RS_node->var_value == Nnull_string)
...
if (RS_is_null || today == TUESDAY) ...
На первый взгляд, он выглядит замечательно. Условие 'RS_is_null
' ясно и просто для понимания и абстрагирует внутренние детали проверки. Проблема возникает, когда вы пытаетесь вывести значение в GDB:
(gdb) print RS_is_null
No symbol "RS_is_null" in current context.
В таком случае нужно разыскать определение макроса и вывести развернутое значение.
Рекомендация: Для представления важных условий в своей программе используйте переменные, значения которых при изменении условий явным образом меняется в коде.
Вот сокращенный пример из io.c
в дистрибутиве gawk
:
void set_RS() {
...
RS_is_null = FALSE;
if (RS->stlen == 0) {
...
RS_is_null = TRUE;
...
matchrec = rsnullscan;
}
}
После установки и сохранения RS_is_null
ее можно протестировать в коде и вывести из-под отладчика.
ЗАМЕЧАНИЕ. Начиная с 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
- Расширенные возможности указания пользовательских планов
- Возможности, планируемые к реализации в следующих версиях
- Возможности SSH
- Глава 10 Возможности подсистемы хранения данных в различных версиях Windows NT
- Как добавить к Windows новые возможности?
- При входе в систему появляется сообщение о невозможности найти какой-то файл. Как его убрать?
- Функциональные возможности и пользовательский интерфейс программы
- 4.6. Дополнительные возможности защиты
- 4.12.1. Основные возможности iptables
- 5.2.2. Дополнительные возможности OpenSSL
- Выполнение макросов
- Основные возможности программы Total Commander