#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
' читать просто.
У такой гибкости, которую предоставляют объединения, конечно, есть своя цена. Когда отладчик находится глубоко во внутренностях вашего кода, вы не можете использовать симпатичные макросы, которые имеются в исходном коде. Вы
Например, сравните '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. Отлаживаемый код времени исполнения