Категории
Самые читаемые
PochitayKnigi » Компьютеры и Интернет » Программное обеспечение » Linux программирование в примерах - Арнольд Роббинс

Linux программирование в примерах - Арнольд Роббинс

Читать онлайн Linux программирование в примерах - Арнольд Роббинс

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 139 140 141 142 143 144 145 146 147 ... 165
Перейти на страницу:

Указатель sp отслеживает положение следующего пустого слота в буфере, тогда как space_left отслеживает количество оставшегося места; это уберегает нас от переполнения буфера.

Основную часть функции составляет цикл (строка 12), проходящий через массив значений флагов. Когда флаг найден (строка 13), код вычисляет, сколько места требуется строке (строка 18) и проверяет, осталось ли столько места (строки 19–20).

Тест 'sp ! = buffer' для первого значения флага завершается неудачей, возвращая 0. Для последующих флагов тест дает значение 1. Это говорит нам, что между значениями должен быть вставлен разделительный символ '|'. Добавляя результат (1 или 0) к длине строки, мы получаем правильное значение space_needed. Тот же тест с той же целью проводится в строке 22 для проверки строк 23 и 24, которые вставляют символ '|'.

В заключение строки 26–29 копируют значение строки, выверяют количество оставшегося места и обновляют указатель sp. Строка 33 возвращает адрес буфера, который содержит печатное представление строки.

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

Более того, статический буфер по определению является буфером фиксированного размера. Что случилось с принципом GNU «никаких произвольных ограничений»?

Для ответа на эти вопросы нужно вспомнить, что это отладочная функция. Обычный код никогда не вызывает getflags2str(); она вызывается лишь человеком, использующим отладчик. Ни у одного вызывающего нет указателя на буфер; как разработчику, осуществляющему отладку, нам нет дела до того, что буфер каждый раз переписывается при вызове функции.

На практике фиксированный размер также не является проблемой; мы знаем, что размер 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 "xUtb",

 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 "xUtb",

 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. По возможности избегайте объединений

«Не бывает бесплатных обедов»

- Lazarus Long -

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 %#xn", 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] Здесь достаточно определений, чтобы дать вам представление о том, что происходит:

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

1 ... 139 140 141 142 143 144 145 146 147 ... 165
Перейти на страницу:
Тут вы можете бесплатно читать книгу Linux программирование в примерах - Арнольд Роббинс.
Комментарии