На практике фиксированный размер также не является проблемой; мы знаем, что размер BUFSIZ достаточен для представления всех флагов, которые мы используем. Тем не менее, поскольку мы опытные и знаем, что вещи могут измениться, в getflags2str() есть код, предохраняющий себя от переполнения буфера. (Переменная space_left и код в строках 18–20.)
В качестве отступления, использование BUFSIZ спорно. Эта константа должна использоваться исключительно для буферов ввода/вывода, но часто она используется также для общих строковых буферов. Такой код лучше убрать, определив явные константы, такие, как FLAGVALSIZE, и использовав в строке 11 'sizeof (buffer)'.
Вот сокращенный сеанс GDB, показывающий использование flags2str():
$ gdb gawk /* Запустить GDB с gawk */
GNU gdb 5.3
...
(gdb) break do_print /* Установить контрольную точку */
Breakpoint 1 at 0x805a584: file builtin.c, line 1547.
(gdb) run 'BEGIN { print "hello, world" }' /* Запустить программу */
Starting program: /home/arnold/Gnu/gawk/gawk-3.1.4/gawk 'BEGIN { print "hello, world" }'
Breakpoint 1, do_print (tree=0x80955b8) at builtin.c: 1547 /* Останова в контрольной точке */
1547 struct redirect *rp = NULL;
(gdb) print *tree /* Вывести NODE */
$1 = {sub = {nodep =
{1 = {lptr = 0x8095598, param_name = 0x8095598 "xU\t\b",
ll = 134629464}, 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) print flags2str(tree->flags) /* Вывести значение флага */
$2 = 0x80918a0 "MALLOC"
(gdb) next /* Продолжить */
1553 fp = redirect_to_fp(tree->rnode, &rp);
...
1588 efwrite(t[i]->stptr, sizeof(char), t[i]->stlen, fp, "print", rp, FALSE);
(gdb) print *t[i] /* Снова вывести NODE */
$4 = {sub = {nodep =
{l = {lptr = 0x8095598, parm_name = 0x8095598 "xU\t\b",
ll = 134829464}, r = {rptr = 0x0, pptr = 0, preg = 0x0, hd = 0x0,
av = 0x0, r_ent =0), x = {extra = 0x8095ad8, xl = 134830808,
param_list = 0x8095ad8}, name = 0xc <Address 0xc out of bounds>,
number = 1, reflags = 4294967295}, val = {
fltnum = 6.6614606209589101e-316, sp = 0x8095ad8 "hello, world",
slen = 12, sref = 1, idx = -1}, hash = {next = 0x8095598, name = 0x0,
length = 134830808, value = 0xc, ref = 1}}, type = Node_val, flags = 29}
(gdb) print flags2str(t[i]->flags) /* Вывести значение флага */
$5 = 0x80918a0 "MALLOC|PERM|STRING|STRCUR"
Надеемся, вы согласитесь, что настоящий механизм общего назначения значительно более элегантный и более простой в использовании, чем первоначальный.
Тщательное проектирование и использование массивов структур часто может заменить или слить воедино повторяющийся код.
15.4.1.5. По возможности избегайте объединений
«Не бывает бесплатных обедов»
union С относительно эзотерическая возможность. Она помогает экономить память, сохраняя различные элементы в одном и том же физическом пространстве; как программа интерпретирует его, зависит от способа доступа:
/* ch15-union.c --- краткая демонстрация использования union. */
#include <stdio.h>
int main(void) {
union i_f {
int i;
float f;
} u;
u.f = 12.34; /* Присвоить значение с плавающей точкой */
printf("%f also looks like %#x\n", u.f, u.i};
exit(0);
}
Вот что происходит, когда программа запускается на системе Intel x86 GNU/Linux:
$ ch15-union
12.340000 also looks like 0x414570a4
Программа выводит битовый паттерн, который представляет число с плавающей точкой в виде шестнадцатеричного целого. Оба поля занимают одно и то же место в памяти; разница в том, как этот участок памяти интерпретируется: u.f действует, как число с плавающей точкой, тогда как эти же биты в u.i действуют, как целое число.
Объединения особенно полезны в компиляторах и интерпретаторах, которые часто создают древовидные структуры, представляющие структуру файла с исходным кодом (которая называется деревом грамматического разбора (parse tree)). Это моделирует то, как формально описаны языки программирования: операторы if, операторы while, операторы присваивания и так далее для всех экземпляров более общего типа «оператора». Таким образом, в компиляторе могло бы быть нечто подобное этому:
struct if_stmt { ... }; /* Структура для оператора IF */
struct while_stmt { ... }; /* Структура для оператора WHILE */
struct for_stmt { ... }; /* Структура для оператора */
/* ...структуры для других типов операторов... */
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] Здесь достаточно определений, чтобы дать вам представление о том, что происходит:
171
Мы унаследовали эту схему. В общем, она работает, но все же есть проблемы. Целью данного раздела является передача накопленного нами в ходе работы с объединениями опыта —