Книга: 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

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


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