Linux программирование в примерах - Роббинс Арнольд
Шрифт:
Интервал:
Закладка:
/* ...структуры для других типов операторов... */
typedef enum stmt_type {
IF, WHILE, FOR, ...
} TYPE; /* Что у нас есть в действительности */
/* Здесь содержатся тип и объединения отдельных видов операторов. */
struct statement {
TYPE type;
union stmt {
struct if_stmt if_st;
struct while_stmt while_st;
struct for_stmt for_st;
...
} u;
};
Вместе с объединением удобно использовать макрос, который представляет компоненты объединения, как если бы они были полями структуры. Например:
#define if_s u.if_st /* Так что можно использовать s->if_s вместо s->u.if_st */
#define while_s u.while_st /* И так далее... */
#define for_s u.for_st
...
На только что представленном уровне это кажется разумным и выглядит осуществимым. В действительности, однако, все сложнее, и в реальных компиляторах и интерпретаторах часто есть несколько уровней вложенных структур и объединений. Сюда относится и gawk, в котором определение NODE, значение его флагов и макросов для доступа к компонентам объединения занимают свыше 120 строк![171] Здесь достаточно определений, чтобы дать вам представление о том, что происходит:
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) <b>print *tree</b> /* Вывести NODE */
$1 = {sub = {nodep =
{1 = {lptr = 0x8095598, param_name = 0x8095598 "xUtb",
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},