Выбрать главу

typedef struct exp_node {

 union {

  struct {

   union {

    struct exp_node *lptr;

    char *param_name;

    long ll;

   } l;

   union {

    ...

   } r;

   union {

    ...

   } x;

   char *name;

   short number;

   unsigned long reflags;

   ...

  } nodep;

  struct {

   AWKNUM fltnum;

   char *sp;

   size_t slen;

   long sref;

   int idx;

  } val;

  struct {

   struct exp_node *next;

   char *name;

   size_t length;

   struct exp_node *value;

   long ref;

  } hash;

#define hnext sub.hash.next

#define hname sub.hash.name

#define hlength sub.hash.length

#define hvalue sub.hash.value

  ...

 } sub;

 NODETYPE type;

 unsigned short flags;

 ...

} NODE;

#define vname sub.nodep.name

#define exec_count sub.nodep.reflags

#define lnode sub.nodep.l.lptr

#define nextp sub.nodep.l.lptr

#define source_file sub.nodep.name

#define source_line sub.nodep.number

#define param_cnt sub.nodep.number

#define param sub.nodep.l.param_name

#define stptr sub.val.sp

#define stlen sub.val.slen

#define stref sub.val.sref

#define stfmt sub.val.idx

#define var_value lnode

...

В NODE есть объединение внутри структуры внутри объединения внутри структуры! (Ой.) Поверх всего этого многочисленные «поля» макросов соответствуют одним и тем же компонентам struct/union в зависимости от того, что на самом деле хранится в NODE! (Снова ой.)

Преимуществом такой сложности является то, что код С сравнительно ясный. Нечто вроде 'NF_node->var_value->slen' читать просто.

У такой гибкости, которую предоставляют объединения, конечно, есть своя цена. Когда отладчик находится глубоко во внутренностях вашего кода, вы не можете использовать симпатичные макросы, которые имеются в исходном коде. Вы должны использовать развернутое значение.[172] (А для этого придется найти в заголовочном файле соответствующее определение.)

Например, сравните 'NF_node->var_value->slen' с развернутой формой: 'NF_node->sub.nodep.l.lptr->sub.val.slen'! Чтобы увидеть значение данных, вы должны набрать последнее в GDB. Взгляните снова на это извлечение из приведенного ранее сеанса отладки GDB:

(gdb) print *tree /* Вывести NODE */

$1 = {sub = {nodep =

 {1 = {lptr = 0x8095598, param_name = 0x8095598 "xU\t\b",

 ll = 134829464}, r = {rptr = 0x0, pptr = 0, preg = 0x0,

 hd = 0x0, av = 0x0, r_ent =0), x = {extra = 0x0, xl = 0,

 param_list = 0x0}, name = 0x0, number = 1, reflags = 0},

 val = { fltnum = 6.6614606209589101e-316, sp = 0x0,

 slen = 0, sref = 1, idx = 0),

 hash = {next = 0x8095598, name = 0x0, length = 0,

 value = 0x0, ref = 1}}, type = Node_K_print, flags = 1}

Это куча вязкой массы. Однако, GDB все же несколько упрощает ее обработку. Вы можете использовать выражения вроде '($1).sub.val.slen', чтобы пройти через дерево и перечислить структуры данных.

Есть другие причины для избегания объединений. Прежде всего, объединения не проверяются. Ничто, кроме внимания программиста, не гарантирует, что когда вы получаете доступ к одной части объединения, вы получаете доступ к той части, которая была сохранена последней. Мы видели это в ch15-union.c, в котором доступ к обоим «элементам» объединения осуществлялся одновременно.

Вторая причина, связанная с первой, заключается в осторожности с перекрытиями вложенных комбинаций struct/union. Например, в предыдущей версии gawk[173] был такой код.

/* n->lnode перекрывает размер массива, не вызывайте unref, если это массив */

if (n->type != Node_var_array && n->type != Node_array_ref)

unref(n->lnode);

Первоначально if не было, был только вызов unref(), которая освобождает NODE, на которую указывает n->lnode. Однако, в этот момент gawk могла создать аварийную ситуацию. Можете себе представить, сколько времени потребовало отслеживание в отладчике того факта, что то, что рассматривалось как указатель, на самом деле было размером массива!

В качестве отступления, объединения значительно менее полезны в С++. Наследование и объектно-ориентированные возможности создают при управлении структурами данных совсем другую ситуацию, которая значительно безопаснее.

Рекомендация: по возможности избегайте объединений (union). Если это невозможно, тщательно проектируйте и программируйте их!

15.4.2. Отлаживаемый код времени исполнения

Помимо тех вещей, которые вы добавляете к своему коду для времени компиляции, можно также добавить дополнительный код для обеспечения возможностей отладки времени исполнения. Это особенно полезно для приложений, которые устанавливаются в полевых условиях, когда в системе клиента не будет установленного исходного кода (а может быть, даже и компилятора!)

В данном разделе представлены некоторые методики отладки, которые мы использовали в течение ряда лет, от простых до более сложных. Обратите внимание, что наше рассмотрение ни в коем случае не является исчерпывающим. Это область, в которой стоит иметь некоторое воображение и использовать его!

15.4.2.1. Добавляйте отладочные опции и переменные

Простейшей методикой является наличие опции командной строки, делающих возможным отладку. Такая опция может быть условно откомпилированной для отладки. Однако более гибким подходом является оставить опцию в готовой версии программы. (Вы можете также решить, оставлять или не оставлять эту опцию не документированной. Здесь есть различные компромиссы: ее документирование может дать возможность вашим покупателям или клиентам больше изучить внутренности вашей системы, чего вы можете не хотеть С другой стороны, не документирование ее кажется довольно подлым. Если вы пишете для Open Source или Free Software, лучше документировать опцию.)

вернуться

172

Опять-таки, GCC 3.1 или более новый и GDB 5 дают возможность непосредственного использования макросов, но только лишь если вы используете их совместно, с определенными опциями. Это было описано ранее в разделе 15.4.1.2 «По возможности избегайте макросов с выражениями». — Примеч. автора.

вернуться

173

Эта часть кода была с тех пор пересмотрена, поэтому там больше нет этих строк из примера. — Примеч. автора.