Операционная система UNIX. Руководство программиста

         

Усовершенствования $*, $@, $<


Внутренние макросы $*, $@ и $< - полезные обозначения для текущих целевых файлов и устаревших файлов, связанных с целевыми. К этому списку следует дабавить следующие макросы: $(@D), $(@F), $(*D), $(*F), $(<D) и $(<F). Модификатор D обозначает маршрутную часть полного имени файла, соответствующего односимвольному макросу, а модификатор F - простое имя. Эти дополнительные макросы бывают полезны при построении иерархических make-файлов. Они позволяют получить доступ к имени каталога, чтобы воспользоваться командой cd(1). Например, можно написать

cd $(<D); $(MAKE) $(<F)



Устаревший синтаксис


Ряд конструкций более ранней версии языка C теперь недопустимы. Они распадаются на два класса: операции присваивания и конструкции инициализации.

Устаревшие формы операций присваивания (=+, =- и т.д.) могли привести к неоднозначным выражениям, например, таким:

a =-1;

Это выражение можно трактовать как

a =- 1;

или как

a = -1 ;

Ситуация становится в особенности запутанной, если неоднозначные конструкции данного сорта появляются в результате макроподстановки. Более новые и предпочтельные операции (например, +=, -=, ...) не приводят к таким неоднозначностям. lint выдает сообщения, призывающие отказаться от устаревших конструкций.

Аналогичные проблемы возникают с инициализацией. Раньше язык допускал описание вида

int x 1;

инициализировавшее x единицей. Это приводило к трудностям в интерпретации других конструкций. Например, инициализация

int x (-1);

до некоторой степени походит на начало определения функции:

int x (y) . . .



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

int x = -1;

В этом уже нет никакой синтаксической неоднозначности.




Если исходный текст программы размещается в нескольких файлах, при изменении одного из них необходимо выяснять, какие еще модули необходимо перекомпилировать. Используя утилиту make(1), можно записать все зависимости между файлами, гарантировав таким образом автоматическую перекомпиляцию всех модулей, зависимых от измененного. Даже управление такой простой программой, как рассматривавшаяся выше при обсуждении средств отладки, облегчается, если пользоваться утилитой make.

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

для построения целевого файла. Целевым файлом обычно является выполняемая программа. Файл описаний обычно содержит информацию трех типов:

Информацию о зависимостях между модулями, из которых составляется целевая программа.

Выполняемые команды, необходимые для формирования целевой программы. Используя информацию о зависимостях, make определяет, какие команды следует передать shell'у для выполнения.

Макроопределения, позволяющие использовать сокращения. Макроопределения, заданные внутри файла описаний, могут быть заменены макроопределениями, задаваемыми в командной строке при вызове утилиты make.

Утилита make работает, проверяя время последнего изменения модулей, указанных в файле описаний. Если при этом оказывается, что у некоторого модуля время последнего изменения меньше, чем у модуля, от которого он зависит, то указанная команда (обычно компиляция) направляется shell'у для выполнения.

Команда make допускает три типа аргументов: опции, макроопределения и имена целевых файлов. Если в опции командной строки не задано имя файла с описаниями, то make отыскивает в текущем каталоге файлы с именами makefile или Makefile. Ниже приводится пример файла описаний.

OBJECTS = restate.o oppty.o pft.o rfe.o all: restate restate: $(OBJECTS) $(CC) $(CFLAGS) $(LDFLAGS) $(OBJECTS) -o restate

$(OBJECTS): ./recdef.h

clean: rm -f $(OBJECTS)

clobber: clean rm -f restate

В данном примере

Целевой файл restate определяется как зависимый от четырех объектных файлов, каждый из которых определяется как зависимый от включаемого файла "recdef.h", и, по умолчанию, от соответствующих файлов с исходным текстом.

Макроопределение OBJECTS введено как удобное сокращение для ссылки на все составляющие модули.

Если Вы хотите тестировать или отлаживать результаты изменения одного из компонентов программы restate, для изменения опций команды cc можно, например, воспользоваться командой

make CFLAGS=-g restate

Мы провели очень короткий обзор утилиты make; этой утилите посвящена отдельная глава Руководства.



Вещественные и целочисленные значения


Преобразования вещественных значений в целочисленные до некоторой степени машинно-зависимы. В частности, различается способ округления отрицательных чисел. Результат не определен, если он не помещается в отведенное место.

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



Вещественные константы


Вещественная константа состоит из целой части, десятичной точки, дробной части, символа e или E, и целой экспоненты (быть может со знаком). Целая и дробная части являются последовательностями цифр. Целая часть или дробная часть (но не обе сразу) могут быть опущены. Десятичная точка или символ e с экспонентой (но не то и другое сразу) могут быть опущены. Вещественная константа всегда имеет тип double.



ВХОДНОЙ СИНТАКСИС YACC'А


В данном разделе в форме yacc-спецификации описывается входной синтаксис yacc'а. Не рассматриваются контекстные зависимости и аналогичные вопросы. Ирония заключается в том, что, хотя yacc

воспринимает LALR(1)-грамматики, сам входной язык спецификаций yacc'а наиболее естественным образом задается LR(2)-грамматикой: трудным оказывается случай, когда идентификатор идет сразу за действием. Если после идентификатора стоит двоеточие, то это начало следующего правила, в противном случае, это продолжение текущего правила, которое идет за внутренним действием. Реализация такова: лексический анализатор, обнаружив идентификатор, просматривает входной текст вперед и определяет, является ли следующая лексема двоеточием. Если является, лексический анали- затор возвращает C_IDENTIFIER. Если нет, возвращается IDENTIFIER. Литералы (в апострофах) также распознаются как IDENTIFIER, но не как C_IDENTIFIER.

/* грамматика yacc-спецификаций */

/* основные компоненты */ %token IDENTIFIER /* идентификаторы и литералы */ %token C_IDENTIFIER /* идентификатор (но не литерал), */ /* за которым следует двоеточие */ %token NUMBER /* [0-9]+ */

/* зарезервированные слова: %type=>TYPE %left=>LEFT и т.д.*/

%token LEFT RIGHT NONASSOC TOKEN PREC TYPE START UNION

%token MARK /* знак %% */ %token LCURL /* знак %{ */ %token RCURL /* знак %} */

/* ASCII-символы указываются непосредственно */

%%

spec : defs MARK rules tail ;

tail : MARK { В этом действии обрабатывается оставшаяся часть файла } | /* пусто: второй MARK необязателен */ ;

defs : /* пусто */ | defs def ; def : START IDENTIFIER | UNION { Копирование определения объединения в выходной файл } | LCURL { Копирование C-кода в выходной файл } RCURL | rword tag nlist ;

rword : TOKEN | LEFT | RIGHT | NONASSOC | TYPE ;

tag : /* пусто: тег необязателен */ | '<' IDENTIFIER '>' ;

nlist : nmno | nlist nmno | nlist ',' nmno ;

nmno : IDENTIFIER /* Замечание: литерал нельзя использовать с %type */ | IDENTIFIER NUMBER /* Замечание: нельзя использовать с %type */ ;

/* секция правил */

rules : C_IDENTIFIER rbody prec | rules rule ; rule : C_IDENTIFIER rbody prec | '|' rbody prec ;

rbody : /* пусто */ | rbody IDENTIFIER | rbody act ;

act : '{' { Копирование действия, обработка $$ и т.п. } '}' ;

prec : /* пусто */ | PREC IDENTIFIER | PREC IDENTIFIER act | prec ';' ;



Видимость внешних объектов


Если функция обращается к идентификатору, описанному как extern, то в каком-нибудь из файлов (либо библиотек), составляющих полную программу, должно быть по крайней мере одно внешнее определение данного идентификатора. Все функции некоторой программы, обращающиеся к одному и тому же внешнему идентификатору, ссылаются тем самым на один объект; поэтому нужно следить, чтобы тип и размер, специфицированные в определении, были совместимы с теми, которые задаются в функциях, обращающихся к данному объекту.

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

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

Если функция обращается к идентификатору, описанному как extern, то в каком-нибудь из файлов (либо библиотек), составляющих полную программу, должно быть по крайней мере одно внешнее определение данного идентификатора. Все функции некоторой программы, обращающиеся к одному и тому же внешнему идентификатору, ссылаются тем самым на один объект; поэтому нужно следить, чтобы тип и размер, специфицированные в определении, были совместимы с теми, которые задаются в функциях, обращающихся к данному объекту.

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

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

Идентификаторы, описанные на верхнем уровне внешних определений как static, невидимы в других файлах. Функции могут быть описаны как static.



Включаемые файлы


В предыдущих разделах данной главы часто упоминался файл <stdio.h>, был также приведен его полный текст. <stdio.h> - это наиболее часто используемый включаемый файл при программировании на C в ОС UNIX. Разумеется, существует много других включаемых файлов.

Включаемые файлы содержат определения и описания, одновременно используемые более чем одной функцией. Как правило, имена включаемых файлов имеют расширение .h. Содержимое включаемых файлов обрабатывается препроцессором языка C во время компиляции. Для указания препроцессору о необходимости включения файла применяется директива #include, которую нужно поместить в текст программы. Вообще, директивой препроцессора считаются строки программы, начинающиеся с символа #. Чаще всего используются директивы #include и #define. Как уже говорилось, директива #include

используется для вызова (и обработки) содержимого указанного в ней включаемого файла. Директива #define указывает препроцессору, что в тексте программы необходимо заменить каждое вхождение определяемого имени на цепочку лексем. Например, директива

#define _NFILE 20

устанавливает максимальное допустимое количество файлов, одновременно открытых программой, равным 20. Полный список директив препроцессора приведен в статье cpp(1).

В тексте Справочника программиста упоминается около 45 различных включаемых файлов. При этом всегда в директиве #include имя включаемого файла изображается в угловых скобках <>. Пример:

#include <stdio.h>

Угловые скобки в этом случае обозначают, что препроцессор будет считать, что включаемый файл расположен в определенном каталоге. Как правило, таким каталогом является /usr/include. Если Вы хотите какие-либо собственные определения или описания сделать доступными для нескольких файлов, Вы можете создать .h-файл с помощью любого редактора, поместить его в подходящий каталог, и указать его имя в директиве #include следующим образом:

#include "../defs/rec.h"

В данном случае необходимо указать в кавычках ("") относительное маршрутное имя файла. Указывать полное маршрутное имя файла не рекомендуется, так как в этом случае могут возникнуть трудности при переносе программ, а также другие организационные проблемы. Чтобы не указывать полные маршрутные имена, можно при компиляции программ пользоваться опцией препроцессора -Iкаталог. Если указана эта опция, препроцессор разыскивает включаемые файлы, имена которых указаны в кавычках, следующим образом. Сначала производится поиск в каталоге, в котором расположен компилируемый файл, затем в каталогах, указанных в опции (опциях) -I, и, наконец, в каталогах из стандартного списка. Заметим также, что все включаемые файлы, имена которых указаны в угловых скобках <>, сначала отыскиваются в списке каталогов, заданных с помощью опции -I, а затем в каталогах из стандартного списка.


Утилита make предоставляет возможность, аналогичную директиве #include препроцессора языка C. Если первые семь символов в какой-либо строке make-файла составляют цепочку include, а за ними следует пробел либо табуляция, остаток строки считается именем файла, который будет прочитан текущим запуском утилиты make. В именах включаемых файлов могут быть использованы макросы. Описатели читаемых включаемых файлов накапливаются, поэтому число вложенных включаемых файлов ограничено (не может быть больше 16).



Включение файлов


Командная строка вида

#include "имя_файла"

вызывает замену данной строки на содержимое файла с указанным именем. Названный файл ищется сначала в каталоге файла, содержащего #include, затем в последовательности явным образом специфицированных или стандартных каталогов. Напротив, строка вида

#include <имя_файла>

вызывает поиск только в последовательности специфицированных или стандартных каталогов, но не в каталоге файла, содержащего #include. (Способ явной спецификации каталогов поиска формулируется вне языка. См. описание способа спецификации дополнительных библиотек в статье cpp(1) Справочника пользователя).

Включаемый файл также может содержать директивы #include.



ВНЕШНИЕ ОПРЕДЕЛЕНИЯ


C-программа состоит из последовательности внешних определений. Внешнее определение задает идентификатор, принадлежащий классу памяти extern (по умолчанию) либо static, и имеющий специфицированный тип. Спецификатор типа (см. пункт Спецификаторы типа в разделе ОПИСАНИЯ) может быть пустым, в таком случае тип считается равным int. Внешние определения имеют тот же синтаксис, что и все описания; единственное исключение - только на этом уровне можно задать тело функции.


программа: внешнее_определение внешнее_определение программа

внешнее_определение: определение_функции определение_данных

определение_функции: спецификаторы описатель_функции тело_функции

описатель_функции: описатель ( список_параметров )

список_параметров: идентификатор идентификатор , список_параметров

тело_функции: список_описаний составной_оператор

определение_данных: extern описание static описание



Внешние определения данных


Внешнее определение данных имеет вид:

определение_данных: описание

Такие данные могут иметь класс памяти extern (выбирается по умолчанию) либо static, но не auto или register.



Внешние определения функций


Определения функций имеют вид:

определение_функции: спецификаторы описатель_функции тело_функции

Единственно допустимые спецификаторы класса памяти в спецификаторах - extern или static; различие между ними указано в пункте Видимость внешних объектов раздела ПРАВИЛА ВИДИМОСТИ. Описатель_функции подобен описателю "функции, возвращающей ...", за исключением того, что он содержит список формальных параметров определяемой функции.

описатель_функции: описатель ( список_параметров )

список_параметров: идентификатор идентификатор , список_параметров

Тело_функции имеет вид:

тело_функции: список_описаний составной_оператор

В списке_описаний можно описывать идентификаторы из списка_параметров и только их. Если тип идентификатора не задан, он считается равным int. Единственный класс памяти, который можно специфицировать - register; если он указан, соответствующий фактический параметр будет, по возможности, скопирован в регистр в начале выполнения функции.

Простой пример законченного определения функции:

int max (a, b, c) int a, b, c; { int m;

m = (a > b) ? a : b; return ((m > c) ? m : c); }

Здесь int - спецификатор, max(a,b,c) - описатель_функции, int

a, b, c; - список_описаний формальных параметров, { ... } - блок, содержащий операторы функции.

C-программа преобразует все фактические параметры типа float в double, поэтому описания формальных параметров типа float воспринимаются как double. Аналогично, все формальные параметры типов char и unsigned long воспринимаются как int. Кроме того, поскольку ссылка на массив в любом контексте (в частности, в качестве фактического параметра) рассматривается как указатель на первый элемент массива, то описания формальных параметров вида "массив..." воспринимаются как "указатель на ...".



Внутренние блоки


В языке C блок определяется как составной оператор, заключенный в фигурные скобки, { и }. Внутренний блок определяется как блок, находящийся внутри функции. Сама функция также является блоком.

Если в некотором внутреннем блоке определены локальные имена, то в таблицу имен, непосредственно перед элементом для первого такого имени, будет помещен элемент для специального имени .bb. Равным образом, специальное имя .eb помещается сразу после элемента для последнего в этом блоке локального имени. Пример такой последовательности изображен изображен в следующей таблице:

.bb
Имена, локальные для этого блока
.eb

Поскольку внутренние блоки могут быть вложенными, пары .bb и .eb и соответствующие им элементы таблицы имен также могут быть вложенными. Пусть исходный текст выглядит так:

{ /* Блок 1 */ int i; char c; . . . { /* Блок 2 */ long a; . . . { /* Блок 3 */ int x; . . . } /* блок 3 */ . . . } /* блок 2 */ . . . { /* Блок 4 */ long i; . . . } /* блок 4 */ . . . } /* блок 1 */

Структура соответствующего фрагмента таблицы имен показана ниже:

. . .
.bb для блока 1
i
c
.bb для блока 2
a
.bb для блока 3
x
.eb для блока 3
.eb для блока 2
.bb для блока 4
i
.eb для блока 4
.eb для блока 1
. . .



ВОПРОСЫ МОБИЛЬНОСТИ


Некоторые свойства языка C существенно машинно-зависимы. Не предполагается, что приведенный список возможных затруднений полон, однако основные сложности в нем указаны.

Чисто аппаратные вопросы, подобные размеру слова, свойствам вещественной арифметики и целого деления, как показала практика, большой проблемы не составляют. Другие аспекты аппаратуры находят отражение в различающихся реализациях. За некоторыми из досадных различий, в особенности за распространением знака (преобразование отрицательного символа в отрицательное целое) и порядком, в котором байты размещаются в слове, надо тщательно следить. Большинство остальных различий большой проблемы не составляют.

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

Порядок обработки параметров функций в языке не фиксируется, поэтому не определяется и порядок проявления побочных эффектов.

Поскольку символьные константы на самом деле являются объектами типа int, могут быть разрешены константы из нескольких символов. Конкретная реализация сильно зависит от компьютера, так как порядок размещения символсти в нем указаны.

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



Вспомогательные элементы таблицы имен


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

Имя Класс памяти Тип Формат вспомогательного элемента
d1 Базовый тип
.file C_FILE DT_NON T_NULL Имя файла
.text, .data, .bss C_STAT DT_NON T_NULL Секция
Имя структуры,объединения или перечисления C_STRTAG, C_UNTAG, C_ENTAG DT_NON T_NULL Начало структуры, объединения или перечисления
.eos C_EOS DT_NON T_NULL Конец структуры
Имя функции C_EXT, C_STAT DT_FCN Кроме T_MOE Функция
Имя массива C_AUTO, C_STAT, C_MOS, C_MOU, C_TPDEF DT_ARR Кроме T_MOE Массив
.bb, .eb C_BLOCK DT_NON T_NULL Начало и конец блока
.bf, .ef C_FCN DT_NON T_NULL Начало и конец функции
Имя, связанное со структурой, объединением или перечислением C_AUTO, C_STAT, C_MOS, C_MOU, C_TPDEF DT_PTR, DT_ARR, DT_NON T_STRUCT, T_UNION, T_ENUM Имя, связанное со структурой, объединением или перечислением

Здесь имя структуры, объединения или перечисления включает также специальные имена .xfake. Если имя удовлетворяет нескольким из перечисленных условий, то его вспомогательный элемент должен иметь формат объединения.

Примечание

Было бы ошибкой полагаться на какие-либо априорные предположения о числе вспомогательных элементов. Число вспомогательных элементов для имени хранится в поле n_numaux соответствующего основного элемента и должно извлекаться только оттуда.

В следующих пунктах описывается структура вспомогательных элементов.



ВСПОМОГАТЕЛЬНЫЙ ЗАГОЛОВОК СИСТЕМЫ UNIX


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



Встроенные функции


Предоставляемая awk'ом функция length позволяет вычислить длину цепочки символов. Следующая программа печатает длину и содержимое каждой записи:

{ print length, $0 }

В данном случае length эквивалентно length($0), что обозначает длину текущей записи. В общем случае, length (x) возвращает длину аргумента x, трактуемого как цепочка символов.

При использовании countries в качестве входного файла, следующая программа напечатает самое длинное название страны:

length ($1) > max { max = length ($1); name = $1 } END { print name }

Функция split в форме

split (s, array)

присваивает поля цепочки s последовательным элементам массива array. Например, обращение

split ("Now is the time", w)

присваивает значение Now элементу w[1], is - w[2], the - w[3], time - w[4]. Все другие элементы массива w[] устанавливаются равными пустой цепочке. Можно указать, что роль разделителя полей должен играть не пробел, а другой символ. В таком случае используется иная форма функции split, с тремя аргументами:

n = split (s, array, sep)

Данный вызов разбивает цепочку s на поля и помещает их в array[1], ..., array[n]. Результат, возвращаемый split, равен числу обнаруженных полей. Если аргумент sep указан, задаваемая им цепочка используется в качестве разделителя полей; в противном случае используется FS. Это бывает удобно, если требуется в середине awk-программы переопределить для одной записи разделитель полей.

Кроме того, awk предоставляет математические функции

sqrt log exp int

Это функции вычисления квадратного корня, натурального логарифма, экспоненты и целой части числа. Последняя функция возвращает максимальное целочисленное значение, не превосходящее значения аргумента. Перечисленные функции заимствуются из математичекой библиотеки языка C (awk-функции int соответствует функция floor библиотеки libm), поэтому в случае ошибки они возвращают такие же результаты, что и их аналоги из libm (см. Справочник программиста).

Функция substr в форме

substr (s, m, n)

возвращает подцепочку цепочки s, начинающуюся с позиции m и содержащую не более n символов. Если третий аргумент (в данном случае - n) отсутствует, выделяется подцепочка до конца s. Например, программа


{ $1 = substr ($1, 1, 3); print }

позволяет сократить названия стран в файле countries:

Rus 8650 262 Asia Can 3852 24 North America Chi 3692 866 Asia USA 3615 219 North America Bra 3286 116 South America Aus 2968 14 Australia Ind 1269 637 Asia Arg 1072 26 South America Sud 968 19 Africa Alg 920 18 Africa

Если s - число, substr использует его текстовое представление:

substr (123456789, 3, 4) = 3456

Функция

index (s1, s2)

возвращает номер начальной позиции первого вхождения цепочки s2

в цепочку s1, либо нуль, если цепочка s2 не входит в цепочку s1.

Функция sprintf форматирует выражения так же, как это делает оператор printf, однако не отправляет результат на стандартный вывод, а присваивает его некоторой переменной. Например, оператор

x = sprintf ("%10s %6d", $1, $2)

присваивает переменной x цепочку символов, полученную форматным преобразованием значений $1 и $2, после чего x можно использовать в последующих вычислениях.

Функция getline немедленно читает следующую входную запись. Значения полей, переменных $0 и NR переустанавливаются, однако управление остается в том же самом месте awk-программы. getline возвращает 0, если обнаружен конец файла, и 1, если считана обычная запись.




ВСТРОЕННЫЕ ПРАВИЛА


Ниже приводится стандартный набор встроенных правил, используемых утилитой make.

# # Суффиксы, распознаваемые make # .SUFFIXES: .o .c .c~ .y .y~ .l .l~ .s .s~ .h .h~ \ .sh .sh~ .f .f~

# # Предопределенные макросы # MAKE=make AR=ar ARFLAGS=-rv AS=as ARFLAGS= CC=cc CFLAGS=-O F77=f77 F77FLAGS= GET=get GFLAGS= LEX=lex LFLAGS= LD=ld LDFLAGS= YACC=yacc YFLAGS=

# # Правила с одним суффиксом # .c: $(CC) $(CFLAGS) $(LDFLAGS) $< -o $@

.c~: $(GET) $(GFLAGS) $< $(CC) $(CFLAGS) $(LDFLAGS) $*.c -o $* -rm -f $*.c

.f: $(F77) $(F77FLAGS) $(LDFLAGS) $< -o $@

.f~: $(GET) $(GFLAGS) $< $(F77) $(F77FLAGS) $(LDFLAGS) $< -o $* -rm -f $*.f

.sh: cp $< $@; chmod 0777 $@

.sh~: $(GET) $(GFLAGS) $< cp $*.sh $*; chmod 0777 $@ -rm -f $*.sh

# # Правила с двумя суффиксами # .c~.c .f~.f .s~.s .sh~.sh .y~.y .l~.l .h~.h: $(GET) $(GFLAGS) $<

.c.a: $(CC) -c $(CFLAGS) $< $(AR) $(ARFLAGS) $@ $*.o rm -f $*.o

.c~.a: $(GET) $(GFLAGS) $< $(CC) -c $(CFLAGS) $*.c $(AR) $(ARFLAGS) $@ $*.o rm -f $*.[co]

.c.o: $(CC) -c $(CFLAGS) $<

.c~.o: $(GET) $(GFLAGS) $< $(CC) -c $(CFLAGS) $*.c -rm -f $*.c

.f.a: $(F77) $(F77FLAGS) $(LDFLAGS) -c $*.f $(AR) $(ARFLAGS) $@ $*.o -rm -f $*.o

.f~.a: $(GET) $(GFLAGS) $< $(F77) $(F77FLAGS) $(LDFLAGS) -c $*.f $(AR) $(ARFLAGS) $@ $*.o -rm -f $*.[fo]

.f.o: $(F77) $(F77FLAGS) $(LDFLAGS) -c $*.f

.f~.o: $(GET) $(GFLAGS) $< $(F77) $(F77FLAGS) $(LDFLAGS) -c $*.f -rm -f $*.f

.s~.a: $(GET) $(GFLAGS) $< $(AS) $(ASFLAGS) -o $*.o $*.s $(AR) $(ARFLAGS) $@ $*.o -rm -f $*.[so]

.s.o: $(AS) $(ASFLAGS) -o $@ $<

.s~.o: $(GET) $(GFLAGS) $< $(AS) $(ASFLAGS) -o $*.o $*.s -rm -f $*.s

.l.c: $(LEX) $(LFLAGS) $< mv lex.yy.c $@

.l~.c: $(GET) $(GFLAGS) $< $(LEX) $(LFLAGS) $*.l mv lex.yy.c $@ rm -f $*.l

.l.o: $(LEX) $(LFLAGS) $< $(CC) $(CFLAGS) -c lex.yy.c rm lex.yy.c mv lex.yy.o $@

.l~.o: $(GET) $(GFLAGS) $< $(LEX) $(LFLAGS) $*.l $(CC) $(CFLAGS) -c lex.yy.c rm lex.yy.c $*.l mv lex.yy.o $*.o

.y.c: $(YACC) $(YFLAGS) $< mv y.tab.c $@

.y~.c: $(GET) $(GFLAGS) $< $(YACC) $(YFLAGS) $*.y mv y.tab.c $@ -rm -f $*.y

.y.o: $(YACC) $(YFLAGS) $< $(CC) $(CFLAGS) -c y.tab.c rm y.tab.c mv y.tab.o $@

.y~.o: $(GET) $(GFLAGS) $< $(YACC) $(YFLAGS) $*.y $(CC) $(CFLAGS) -c y.tab.c rm -f y.tab.c $*.y mv y.tab.o $*.o



В 1983 году Кен Томпсон


В 1983 году Кен Томпсон и Деннис Ритчи были удостоены Премии Тьюринга Ассоциации Пользователей Вычислительных Машин (ACM) за разработку операционной системы UNIX. В аннотации, в частности, говорилось:
Успех системы UNIX основывается на удачном выборе нескольких ключевых идей и их элегантной реализации. Благодаря системе UNIX появилось поколение разработчиков программного обеспечения с новой идеологией программирования, основой которой является многократное использование имеющихся программ.
Должны ли программисты, использующие ОС UNIX, интересоваться тем, что делали Томпсон и Ритчи? Имеет ли это сегодня какой-либо смысл? Это необходимо, поскольку понимание идеологии, заложенной в проект системы, а также знание атмосферы, в которой проходила ее реализация, способствует наиболее быстрому ее освоению.



Данная глава предназначается тем, кто только учится программировать в среде ОС UNIX. По классификации, приведенной в предыдущей главе, это, как правило, программисты-одиночки, не интересующиеся программированием глубоко. Может оказаться, что в данной главе и в связанной с ней справочной информации имеется все, что им необходимо для подготовки и запуска программ на компьютере.
Программистам с более высокими требованиями, например, участвующим в различных прикладных проектах или разрабатывающим программы, которые будут переноситься на другие компьютеры, следует рассматривать эту главу всего лишь как введение.



В текущей версии системы UNIX реализованы два вида блокировки сегментов и файлов: слабая и сильная. Назначение этих средств - дать возможность программам, одновременно обрабатывающим одни и те же данные, синхронизировать свою работу. Поскольку подобный режим обработки данных характерен для многопользовательских приложений, необходимость общего решения проблемы синхронизации признана такими авторитетными в области стандартов организациями, как /usr/group, которая объединяет многочисленных пользователей системы UNIX во многих странах.
Слабая блокировка файлов и сегментов может быть использована для взаимной синхронизации процессов. При использовании сильной блокировки стандартные подпрограммы и системные вызовы ввода-вывода учитывают выполнение протокола блокировок. Таким образом, сильная блокировка обеспечивает дополнительный контроль очередности обращений к данным за счет небольшой потери эффективности.
Далее приводится описание использования возможностей блокировки сегментов и файлов. Рассматриваются примеры корректного исполь- зования средств блокировки сегментов. Мы старались рассеять возможные заблуждения относительно уровня защиты данных, который обеспечивается блокировкой сегментов: она должна рассматриваться в качестве средства синхронизации, а не как механизм обеспечения безопасности данных.
В связи с тем, что ниже содержатся ссылки на описания системного вызова fcntl(2), библиотечной функции lockf(3C), а также на команды и структуры данных fcntl(5), Вам, прежде чем продолжать чтение, следует ознакомиться с соответствующими разделами Справочника программиста.



Какой бы ни была мощность компьютера, работающего под управлением ОС UNIX, рано или поздно встает задача максимально эффективного использования процессора, оперативной и дисковой памяти. Разделяемые библиотеки позволяют достичь экономии каждого из перечисленных ресурсов. Например, хорошо организованная разделяемая библиотека уменьшает расход как дискового пространства, необходимого для хранения выполняемых объектных файлов, так и оперативной памяти, отводимой под процессы.
Изучение раздела 2, Использование разделяемых библиотек, поможет Вам овладеть методами работы с разделяемыми библиотеками в ОС UNIX V. В разделе объясняется, что такое разделяемая библиотека, и как ее использовать при построении выполняемых файлов. В нем также содержатся рекомендации относительно того, когда следует (или не следует) применять разделяемые библиотеки, и как определить, использует ли выполняемый файл подобную библиотеку.
В разделе 3, Создание разделяемой библиотеки, описывается процесс построения разделяемых библиотек. Чтобы уметь пользоваться разделяемыми библиотеками, нет необходимости читать эту часть, она предназначена для хорошо подготовленных программистов, которые, возможно, захотят создать свою собственную разделяемую библиотеку.
Примечание
Разделяемые библиотеки являются особенностью версии 3.0 ОС UNIX V. Выполняемые объектные файлы, использующие разделяемые библиотеки, в более ранних версиях ОС UNIX будут неработоспособны.


Ввод


Подпрограммы curses, предназначенные для чтения с текущего терминала, подобны тем, которые содержатся в stdio(3S) и читают из файла. Они позволяют:

Читать символы по одному: getch().

Читать строку, заканчивающуюся символом перевода строки: getstr().

Сканировать вводимые символы, извлекая значения и присваивая их переменным из списка аргументов: scanw().

Первичной подпрограммой является getch(), которая обрабатывает и возвращает значение одного введенного символа. Она подобна подпрограмме getchar(3S) из библиотеки языка C, за исключением того, что она может производить некоторые зависящие от системы или терминала действия, которые не выполняет getchar(). Например, getch() можно использовать совместно с подпрограммой keypad() из библиотеки curses. Это позволяет программе распознавать и обрабатывать, как один символ, последовательности, начинающиеся с ESC, которые передаются, например, при нажатии клавиш управления курсором или функциональных клавиш. Дополнительную информацию о keypad() см. в описаниях getch() и keypad() в curses(3X).

Ниже описываются основные подпрограммы для ввода с терминала и даются примеры их использования.

getch( )

СИНТАКСИС
  #include <curses.h>

int getch ( )

ОПИСАНИЕ
  getch() читает один символ с текущего терминала.

getch() возвращает значение этого символа или ERR при возникновении ситуации "конец файла", получении сигнала, или при чтении без ожидания, если символ еще не введен.

getch() является макросом.

См. далее, а также в curses(3X) описания echo(), noecho(), cbreak(), nocbreak(), raw(), noraw(), halfdelay(), nodelay() и keypad().

ПРИМЕР
  #include <curses.h>

main () { int ch; initscr (); cbreak (); /* Пояснения см. в разделе "Опции ввода" */ addstr ("Введите любой символ: "); refresh (); ch=getch (); printw ("\n\n\nВы ввели '%c'.\n", ch); refresh (); endwin (); }

Посмотрим, что выводит эта программа. Первый refresh() выводит цепочку символов, указанную в addstr(), из stdscr на экран терминала:




Пусть на клавиатуре нажали w. getch() принимает символ и его значение присваивается ch. Наконец, второй раз вызывается refresh() и экран становится таким:



Другой пример использования getch() см. в программе show в разделе Примеры программ, работающих с curses.

getstr( )

СИНТАКСИС
  #include <curses.h>

int getstr (str) char *str;
ОПИСАНИЕ
  getstr() читает символы и сохраняет их в буфере до тех пор, пока не будет нажат возврат каретки, перевод строки или клавиша ввода.
getstr() не проверяет буфер на переполнение.

Прочитанные символы пересылаются в цепочку str.

getstr() является макросом и вызывает getch() для чтения каждого символа.

getstr() возвращает ERR только тогда, когда getch()

возвращает ERR. В остальных случаях возвращается OK.

См. далее, а также в curses(3X) описания echo(), noecho(), cbreak(), nocbreak(), raw(), noraw(), halfdelay(), nodelay() и keypad().

ПРИМЕР
  #include <curses.h>

main () { char str [256]; initscr (); cbreak (); /* Пояснения см. в разделе "Опции ввода" */ addstr ("Введите строку символов,"); addstr (" оканчивающуюся <CR>:\n\n"); refresh (); getstr (str); printw ("\n\n\nВы ввели \n'%s'\n", str); refresh (); endwin (); }
Допустим, Вы ввели строку "Мне нравится изучать систему UNIX". После нажатия возврата каретки экран будет выглядеть так:



scanw( )

СИНТАКСИС
  #include <curses.h>

int scanw (fmt [,arg...]) char *fmt;
ОПИСАНИЕ
  scanw() вызывает getstr() и сканирует введенную строку.
Подобно scanf(3S), scanw() использует формат для преобразования введенной строки и присваивает значения переменному количеству аргументов.

scanw() возвращает те же значения, что и scanf().

Дополнительную информацию см. в описании scanf(3S).

ПРИМЕР
  include <curses.h>

main () { char string [100]; float number; initscr (); cbreak (); /* Пояснения см. в разделе "Опции ввода" */ echo (); addstr ("Введите число и текст,"); addstr (" разделенные запятой:"); refresh (); scanw ("%f,%s", &number, string); clear (); printw ("Вы ввели текст \"%s\" и число %f.", string, number); refresh (); endwin (); }
Обратите внимание на два вызова refresh(). После первого вызова на экране появляется строка, переданная addstr(), после второго - строка, которую возвращает scanw(). Кроме того, обратите внимание на вызов clear(). Допустим, Вы ввели строку 2,twin. После выполнения программы экран терминала будет выглядеть так:






Ввод из файла


В качестве примера рассмотрим файл countries. Данный файл состоит из строк, содержащих площадь (в тысячах квадратных миль), население (в миллионах человек) и континент для десяти крупнейших по площади стран мира. (Данные взяты на 1978 год; Россия отнесена к Азии.)

Russia 8650 262 Asia Canada 3852 24 North America China 3692 866 Asia USA 3615 219 North America Brazil 3286 116 South America Australia 2968 14 Australia India 1269 637 Asia Argentina 1072 26 South America Sudan 968 19 Africa Algeria 920 18 Africa

Широкие промежутки между колонками при первоначальном вводе заданы табуляциями; слова "North" ("South") и "America" отделяется одиночным пробелом. Данный файл будет использоваться в этой главе в качестве исходного во многих awk-программах, он типичен для того рода информации, для обработки которой лучше всего приспособлен awk (смесь слов и чисел, организованных в колонки или поля, разделенные пробелами либо табуляциями).

Каждая из строк файла countries состоит из четырех или пяти слов, если считать, что поля разделяются пробелами и/или табуляциями, как и подразумевается в awk'е по умолчанию, если не задано противное. В приведенном примере первой записью является

Russia 8650 262 Asia

После того, как эта запись прочитана awk'ом, она присваивается переменной $0. Если требуется сослаться на всю запись целиком, это делается при помощи $0. Например, действие

{ print $0 }

распечатывает всю запись.

Поля, принадлежащие записи, присваиваются переменным $1, $2, $3

и т.д.; это означает, что в awk-программе для обращения к первому полю текущей записи используется переменная $1, для обращения ко второму полю - переменная $2, для обращения к i-ому полю - переменная $i. Так, в приведенном выше примере (файл countries) для первой записи:

$1 эквивалентно цепочке "Russia"

$2 эквивалентно цепочке "8650"

$3 эквивалентно цепочке "262"

$4 эквивалентно цепочке "Asia"

$5 эквивалентно пустой цепочке

. . .

Чтобы напечатать континент, затем название страны, и наконец, численность населения, можно использовать следующую команду:

awk '{ print $4, $1, $3 }' countries

Можно заметить, что она породит не совсем тот вывод, который требуется, поскольку по умолчанию разделителем полей считается не только табуляция, но и пробел. Неудобство состоит в том, что South America и North America содержат пробел. Поэтому правильнее будет использовать команду

awk -F\t '{ print $4, $1, $3 }' countries



Ввод из командной строки


Ранее, в разделе Запуск программы на выполнение, уже говорилось, что для того, чтобы задать awk-программу, которую требуется выполнить, можно либо вставить ее, заключив в одинарные кавычки, в командую строку, либо поместить в файл, а в командной строке указать имя этого файла, поместив перед ним флаг -f. Кроме того, в командной строке можно устанавливать значения переменных.

В awk'е значения переменным можно присваивать внутри awk-программы. Поскольку типы переменных не описываются, переменная создается просто посредством обращения к ней. Пример присваивания переменной нового значения:

x=5

Данный оператор awk-программы присваивает переменной x значение 5. Такой вид присваивания можно задать и в командной строке - это еще один способ указать исходные данные для awk-программы. Например, команда

awk '{ print x }' x=5 -

будет распечатывать значение 5 после прочтения очередной записи. Знак - в конце командной строки необходим для того, чтобы указать, что исходные данные надо брать со стандартного ввода, а не из файла с именем x=5. После ввода команды пользователь должен ввести исходные данные, завершив их сомволом CTRL+D.

Если исходные данные берутся из файла (например, с именем file1), команда должна выглядеть так:

awk '{ print x }' x=5 file1

Если необходимо изменить разделитель полей или разделитель записей, это также можно сделать в командной строке, как в следующем примере:

awk -f awkprog RS=":" file1

В данном примере разделитель записей устанавливается равным символу :. В результате программа, содержащаяся в файле awkprog, обрабатывает записи, разделенные не символами перевода строки, а двоеточиями; исходные данные берутся из файла file1. Подобным же образом в командной строке можно изменить разделитель полей.

Предусмотрена специальная опция, -Fx; если она указана, значение разделителя полей изменяется с пробела или табуляции на символ x. Например, командная строка

awk -F: -f awkprog file1

заменяет разделитель полей на символ :. Отметим, что если разделитель полей явно установлен равным табуляции (то есть при помощи опции -F или посредством присваивания переменной FS), пробелы не считаются разделителями полей. Однако обратное не верно: даже если разделитель полей явно установлен равным пробелу, табуляции тем не менее считаются разделителями полей.



Ввод/вывод


Ввод/вывод уже упоминался в данной главе в связи с системными вызовами и библиотечными функциями. Стандартный пакет ввода/вывода языка C состоит из целого множества библиотечных функций. Кроме того, имеется несколько системных вызовов, предназначенных для организации ввода/вывода. Далее вопросы ввода/вывода в языке C будут обсуждаться несколько более подробно. К таким вопросам можно отнести:

Создание и удаление файлов.

Открытие и закрытие файлов, используемых программой.

Передачу информации из файла в программу (чтение).

Передачу информации из программы в файл (запись).

В данном разделе будут описаны несколько функций, предназначенных для передачи информации, но в основном будут обсуждаться функции для различных манипуляций с файлами.


Для ввода и вывода данных в окна и спецокна используются подпрограммы, похожие на те, которые работают с stdscr. Единственная разница состоит в том, что необходимо указать окно, к которому относится операция. Как правило, имена этих подпрограмм получаются путем добавления буквы w в начало названия соответствующей подпрограммы для stdscr, а также имени окна в качестве первого параметра. Например, если нужно вывести символ c в окно mywin, addch ('c') превращается в waddch (mywin,'c'). Далее приводится список работающих с окнами подпрограмм, соответствующих тем, которые были описаны в разделе Несложный ввод/вывод.

waddch (win, ch)

mvwaddch (win, y, x, ch)

waddstr (win, str)

mvwaddstr (win, y, x, str)

wprintw (win, fmt[,arg...])

mvwprintw (win, y, x, fmt[,arg...])

wmove (win, y, x)

wclear (win)

werase (win)

wclrtoeol (win)

wclrtobot (win)

wrefresh (win)

Как видно из описаний, эти подпрограммы отличаются от тех, которые работают с stdscr, только своими именами и добавлением аргумента win. Заметьте, что у подпрограмм, чьи имена начинаются на mvw, аргумент win предшествует координатам y, x, хотя, судя по их именам, следовало бы ожидать обратного. См. в curses(3X) дополнительные данные об этих подпрограммах, вариантах подпрограмм getch(), getstr() и другую информацию, касающуюся работы с окнами.

Все подпрограммы с буквой w в имени могут использоваться и со спецокнами, за исключением wrefresh() и wnoutrefresh() (см. ниже). Вместо них со спецокнами нужно использовать соответственно prefresh() и pnoutrefresh().



Ввод: записи и поля


awk читает входные записи по одной. Если разделитель записей не переустановлен, запись - это последовательность символов, начиная с той, на которой остановился ввод, и до символа перевода строки либо до конца файла. После того, как цепочка символов считана, она присваивается переменной $0.

Прочитав запись, awk разбивает ее на поля, из которых она составлена. Если разделитель полей не переустановлен, полем является цепочка символов, отделенная пробелами или табуляциями.



Выбор адресов секций команд и данных


В первую очередь следует выбрать адреса секций для разделяемой библиотеки.

Секции разделяемых библиотек должны располагаться с границы, кратной 1 Мб. В следующей таблице приведен список зарезервированных областей памяти (на момент написания данного руководства) и виртуальные адреса, которые могут использовать вновь создаваемые разделяемые библиотеки.

Виртуальный адрес   Описание   Маршрутное имя 
 0x70000000   Разделяемая библиотека языка C   /shlib/libc881_s 
 0x70200000   Сетевая библиотека   /shlib/libnsl_s 
 0x70400000   Системный резерв   не назначено 
 0x74000000   Для частного использования   не назначено 
. . .      
 0x77FFFFFF       

Чем полезна такая таблица? Во-первых, разделяемая библиотека языка C и сетевая разделяемая библиотека находятся там, где это указано, и используют адреса из указанных диапазонов. Если Вы создадите разделяемую библиотеку, использующую зарезервированные адреса, Вы рискуете войти в конфликт с возможными будущими разработками.

Примечание

В принципе разные библиотеки могут использовать одни и те же виртуальные адреса, если работа с ними ведется в рамках разных процессов. Конфликты по адресам разделяемых библиотек возможны не между процессами, а только внутри процесса.

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

Примечание

Если Вы собираетесь разработать коммерческую разделяемую библиотеку, настоятельно рекомендуем создать также совместимую архивную версию этой библиотеки, так как некоторые потенциальные пользователи могут найти использование разделяемой версии невыгодным, а другим нужно будет выполнять свои программы на версиях ОС UNIX, не поддерживающих разделяемых библиотек.



ВЫБОР ЯЗЫКА ПРОГРАММИРОВАНИЯ


Какой язык программирования использовать? На этот вопрос можно ответить, например, так: "Я всегда пишу программы на ФОРГОЛе, потому что знаю его лучше всего." В некоторых обстоятельствах это действительно разумное решение. Но если предположить, что Вы знаете более одного языка, что различные языки имеют сильные и слабые стороны, что, зная один язык, относительно легко изучить другой, вопрос выбора языка становится более содержательным. При выборе языка можно попытаться ответить на следующие вопросы:

Какова природа задачи, которую Вы программируете? Предстоит ли реализовать сложный алгоритм, или же нужно написать простую процедуру из нескольких строк? Имеет ли задача много независимых частей? Можно ли поделить программу на несколько раздельно компилируемых функций, или это будет один модуль? Как скоро программа должна быть готова? Нужно ли написать программу быстро, не заботясь об ее эффективности, или имеется достаточно времени для разработки наиболее эффективной программы? Какова область применения программы? Будет ли программа использоваться только ее автором, или она будет широко распространяться? Будет ли программа переноситься на другие системы? Как долго будет эксплуатироваться программа? Будет ли она использована всего несколько раз, или планируется ее применение в течение нескольких лет?



Выбор маршрутного имени разделяемой библиотеки выполнения


После определения адресов секций, необходимо выбрать маршрутное имя для разделяемой библиотеки выполнения. Так, было выбрано имя /shlib/libc_s для стандартной разделяемой библиотеки языка C и /shlib/libnsl_s для сетевой библиотеки (ранее отмечалось, что в маршрутных именах всех статически отредактированных библиотек используется суффикс _s). Чтобы выбрать маршрутное имя для Вашей разделяемой библиотеки, просмотрите список каталогов, имеющихся на Вашем компьютере, или обратитесь к системному администратору. Примите во внимание, что, как правило, разделяемые библиотеки, необходимые для загрузки ОС UNIX, находятся в /shlib; разделяемые библиотеки других приложений обычно находятся в /usr/lib или в каталогах, предоставленных для этих приложений. Разумеется, если разделяемая библиотека предназначена только для Вашего личного пользования, можно выбрать любое подходящее маршрутное имя для разделяемой библиотеки выполнения.



ВЫБОР СЛАБОЙ ИЛИ СИЛЬНОЙ БЛОКИРОВКИ


По причинам, описанным ниже (см. Сильная блокировка. Некоторые предупреждения), использование сильной блокировки не рекомендуется. Будет ли задействован контроль блокировки при любых системных вызовах, обслуживающих ввод-вывод, выясняется в момент этих вызовов в зависимости от режима доступа к файлу [см. chmod(2)]. Чтобы такой контроль осуществлялся, файл должен быть обычным, со взведенным битом переустановки идентификатора группы и без права на выполнение членами группы. Если эти условия не выполняются, все блокировки сегментов будут слабыми. Сильная блокировка может быть обеспечена следующим исходным текстом:

#include <sys/types.h> #include <sys/stat.h>

int mode; struct stat buf; . . . if (stat (filename, &buf) < 0) { perror ("программа"); exit (2); } /* Получение текущего режима доступа */ mode = buf.st_mode; /* Удаление разрешения на выполнение членами группы */ mode &= (S_IEXEC >> 3); /* Взведение бита переустановки идентификатора группы */ mode |= S_ISGID; if (chmod (filename, mode) < 0) { perror ("программа"); exit (2); } . . .

С файлами, в которых должны блокироваться сегменты, не должно быть связано каких бы то ни было прав на выполнение. Это требование вызвано тем, что операционная система не поддерживает протокол блокировки сегментов при выполнении файла.

Помимо рассмотренного способа, сильная блокировка файла может быть легко установлена с использованием утилиты chmod(1):

chmod +l файл

Утилита ls(1) также была изменена с тем, чтобы при запросе с опцией -l отображать установленный режим блокировки. В результате выполнения запроса

ls -l f1

будет выведена строка вида

-rw---l--- 1 abc other 1576 Dec 3 11:44 f1



Выходные преобразования макросов


Вхождения макросов в команды shell'а раскрываются в момент выполнения. В общем случае обращение к макросу выглядит так:

$(макро:цепочка1=цепочка2)

Подобная конструкция преобразуется следующим образом. Сначала определяется значение $(макро). Затем каждое вхождение цепочки1

в данное значение заменяется на цепочку2; при этом вхождение цепочки1 в $(макро) понимается как сопоставление с регулярным выражением вида

.*цепочка1[табуляция|пробел]

то есть значение $(макро) рассматривается как набор разделенных пробелами или табуляциями цепочек символов и в конце этих цепочек цепочка1 заменяется на цепочку2.

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

$(LIB) : $(LIB)(a.o) $(LIB)(b.o) $(LIB)(c.o) $(CC) -c $(CFLAGS) $(?:.o=.c) $(AR) $(ARFLAGS) $(LIB) $? rm $?

Зависимость указанного вида необходима для каждого из типов исходных файлов (суффиксов), результаты трансляции которых образуют архивную библиотеку. Выходные преобразования позволяют воспользоваться той богатой информацией, которую предоставляет make.



Выяснение характеристик терминала


После создания цепочки имен, для правильного описания нового терминала следует выяснить значения его характеристик. Для этого Вам надлежит сделать следующее:

Изучить руководство по работе с Вашим терминалом. В нем должна содержаться информация о возможностях терминала и соответствующих им последовательностях управляющих символов.

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

stty -echo; cat -vu нажмите тестируемые клавиши,

например, стрелку вправо.

<CR> CTRL+D stty echo

или

cat >dev/null нажмите клавиши тестируемой

управляющей последовательности,

например, \E[H. CTRL+D

Первая строка для каждого из способов тестирования готовит терминал к выполнению теста. CTRL+D помогает вернуть терминал в нормальный режим.

См. terminfo(4), где перечислены все названия характеристик терминалов, которые используются для его описания. Подробности излагаются в следующем разделе, Указание характеристик терминала.



ВЫПОЛНЕНИЕ LEX'А В СИСТЕМЕ UNIX


Прежде чем продвигаться дальше, вернемся на некоторое время к рисунку в начале главы. Для порождения C-текста лексического анализатора следует выполнить команду

lex файл

где файл содержащит lex-спецификацию. Обычно в качестве имени файла берется lex.l, однако в принципе имя файла может быть любым. Порожденный lex'ом выходной файл будет автоматически назван lex.yy.c. Он содержит созданную C-программу лексического анализа, которую следует откомпилировать и выполнить редактирование связей с обязательным подключением библиотеки lex'a libl.a, что достигается указанием опции -ll:

cc lex.yy.c -ll

Библиотека предоставляет недостающую программу main(), которая по имени yylex вызывает лексический анализатор, так что создавать свою собственную программу main() нет необходимости.

Если lex-спецификация состоит из нескольких файлов, можно для каждого из них запустить lex, обязательно переименовывая результирующий файл lex.yy.c [при помощи команды mv(1)] перед следующим запуском lex'а. В противном случае каждый следующий результат будет затирать предыдущий. После того как все .c-файлы сгенерированы, можно скомпилировать их все разом посредством одной командной строки.

Если выполняемый файл a.out порожден, с его помощью можно проанализировать любой входной текст. Предположим, текст помещен в файл с именем textin (имя выбирается произвольно). Лексический анализатор по умолчанию осуществляет ввод с терминала. Чтобы использовать файл textin, достаточно переназначить ввод:

a.out < textin

По умолчанию и вывод осуществляется на терминал, но его также можно переназначить:

a.out < textin > textout

При использовании lex'а совместно с yacc'ом любой из инструментов может быть запущен первым. Команды

yacc -d grammar.y lex lex.l

формируют процедуру разбора в файле y.tab.c. (Если указана опция -d, создается файл y.tab.h, который содержит операторы #define, связывающие назначенные yacc'ом целочисленные значения типов лексем с определенными пользователем именами лексем). Чтобы откомпилировать порожденные файлы и отредактировать связи, введите командную строку


cc lex.yy.c y.tab.c -ly -ll

Заметим, что библиотека yacc'а загружается (опция -ly) перед библиотекой lex'а (опция -ll), что гарантирует использование той программы main(), которая вызывает процедуру разбора.

У команды lex есть несколько допустимых опций, которые следует указывать между именем команды lex и именем файла-аргумента. Для того, чтобы lex выводил сгенерированную C-программу lex.yy.c на терминал (устройство вывода, заданное по умолчанию), используется опция -t:

lex -t lex.l

По опции -v распечатывается небольшая статистическая сводка, характеризующая так называемый конечный автомат, который реализует порожденная lex'ом C-программа lex.yy.c.

Для представления конечного автомата lex использует таблицу (двумерный массив в C). Максимально возможное число состояний конечного автомата по умолчанию устанавливается равным 500. Если lex-спецификация содержит много правил или очень сложные правила, может потребоваться большее число состояний. Увеличение максимального числа состояний достигается добавлением к секции определений строки вида

%n 700

Подобная строка заставляет lex сделать таблицу достаточно большой, чтобы вместить до 700 состояний. (Опция -v позволит узнать, какое число состояний было фактически использовано). Для того, чтобы увеличить максимально допустимое число переходов между состояниями с подразумеваемого значения 2000, предусмотрен параметр a:

%a 2800

В заключение рекомендуем изучить статью lex(1) из Справочника пользователя, в которой содержится список всех опций, предусмотренных у данной команды. Описание нотации регулярных выражений можно найти, например, в статье ed(1) Справочника пользователя.

Вы прочитали введение в разработку lex-программ. Чтобы овладеть lex'ом, как и любым другим программистским инструментом, нужно им пользоваться - чем больше, тем лучше.


Выполнение программы, которая использует curses


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

TERM=тип_текущего_терминала

export TERM tput init

Смысл этих строк объясняется в разделе Взаимодействие curses и terminfo. Пользователи curses-программы могут, кроме того, установить в файле .profile значения переменных окружения $LINES, $COLUMNS и $TERMINFO, однако, в отличие от случая с $TERM, делать это не обязательно.

Если Ваша curses-программа не работает должным образом, Вы, возможно, попытаетесь ее отладить с помощью sdb(1), как это описывается в Справочнике программиста. Пользуясь sdb, нужно учитывать ряд обстоятельств. Во-первых, curses-программа интерактивна и контролирует положение курсора. Однако, интерактивный отладчик, подобный sdb, может произвести на экране изменения, о которых не будет знать отлаживаемая программа.

Во-вторых, программа, работающая с curses, выводит в окно до тех пор, пока не будет вызвана refresh() или подобная ей подпрограмма. В связи с такой задержкой вывода на экран, отладка его правильности может быть затруднена.

В-третьих, во входящих в curses макросах нельзя установить точки прерывания. Вместо макросов нужно использовать подпрограммы, к которым эти макросы обращаются - например, к wrefresh()

вместо refresh(), Более подробную информацию о макросах см. выше в разделе Файл <curses.h>.



Выравнивание секций в выходном файле


Для выравнивания секций внутри выходного файла используется опция BLOCK, которую можно указывать как для отдельной секции, так и в предложении GROUP. Опция BLOCK не влияет ни на процесс редактирования связей, ни на адрес размещения выходной секции, и отражается только на расположении секции в пределах выходного файла. Пример:

SECTIONS { .text BLOCK (0x200): {} .data ALIGN (0x2000) BLOCK (0x200): {} }

Указанные предложения SECTIONS предписывают ld(1), чтобы каждая из секций .text и .data оказались в выходном файле со смещением от начала, кратным 0x200 - например, 0x0, 0x200, 0x400 и т.д.

Полезно рассмотреть содержимое файла /lib/default.ld, с помощью которого можно изменять подразумеваемые соглашения редактора связей:

MEMORY { valid : org = 0x0, len = 0x90000000 } SECTIONS { GROUP BLOCK(1024): { .text: {*(.init) *(.text) *(.fini)} } GROUP ALIGN(1048576) BLOCK(1024): { .data: {} .bss: {} } }



Выравнивание указателей


Некоторые конструкции присваивания указателей могут быть приемлемыми на одних машинах и недопустимыми на других в силу ограничений, связанных с их выравниванием. lint пытается выявить присваивания указателей, которые могут привести к проблемам подобного сорта. В такой ситуации выдается сообщение

possible pointer alignment problem



Выравнивание выходной секции


Можно потребовать, чтобы начальный виртуальный адрес выходной секции был бы выравнен на границу n байт, где n есть степень 2. Это достигается использованием в предложении SECTIONS, на месте адреса, функции ALIGN. Следующие две формы записи адреса начала секции эквивалентны:

ALIGN (n)

(. + n - 1) & ~(n - 1)

Рассмотрим пример.

SECTIONS { outsec ALIGN (0x20000): { . . . } . . . }

Здесь выходной секции outsec не назначается никакой заранее определенный адрес, но она будет размещена по некоторому адресу, кратному 0x20000 (например, может быть назначен адрес 0x0, 0x20000, 0x40000, 0x60000 и т.д.).



Выражения


В выражениях могут использоваться глобальные имена, константы и большинство основных операций языка C (см. Синтаксис управляющего языка редактора связей). Как и в языке C, числовые константы считаются десятичными, если только им не предшествует 0 для восьмеричных и 0x для шестнадцатеричных. Все числа трактуются как длинные целые. Имена могут содержать прописные и строчные буквы, цифры и символ подчеркивания, _. Если имя появляется внутри выражения, то в качестве значения используется его адрес. Редактор связей не просматривает таблицу имен и не пытается выяснить значения переменных, размерности массивов и т.п.

Для распознавания имен, чисел, операций и т.п. редактор связей использует сканер, сгенерированный с помощью утилиты lex(1). Ниже перечислены слова, которые сканер считает зарезервированными, и которые нельзя поэтому использовать в качестве имен или названий секций:

ADDR BLOCK GROUP NEXT RANGE SPARE ALIGN COMMON INFO NOLOAD REGIONS PHY ASSIGN COPY LENGTH ORIGIN SECTIONS TV BIND DSECT MEMORY OVERLAY SIZEOF

addr block length origin sizeof align group next phy spare assign l o range bind len org s

В следующей таблице приведены, в порядке убывания приоритета, знаки допустимых операций:

Знак операции
! ~ - (унарный минус) 

  * / % 

  + - (бинарный минус) 

  >> << 

  == != > < <= >= 

  & 

  | 

  && 

    

  = += -= *= /= 

Перечисленные операции имеют тот же смысл, что и в языке C. Операции, знаки которых находятся на одной строке, имеют одинаковый приоритет.


Выражения в awk'е имеют одну из следующих форм:

терм

терм терм ... пер присвоп выражение




Основные выражения таковы:

выражение: первичное_выражение * выражение & л_значение - выражение ! выражение ~ выражение ++ л_значение -- л_значение л_значение ++

л_значение --

sizeof выражение sizeof ( имя_типа )

( имя_типа ) выражение выражение биноп выражение выражение ? выражение : выражение л_значение присвоп выражение выражение , выражение

первичное_выражение: идентификатор константа текстовая_константа ( выражение )

первичное_выражение [ выражение ]

первичное_выражение ( список_выражений )

первичное_выражение . идентификатор первичное_выражение -> идентификатор

список_выражений: выражение список_выражений , выражение

л_значение: идентификатор первичное_выражение [ выражение ]

л_значение . идентификатор b>* выражение ( л_значение )

Операции, образующие первичные выражения,

( ) [ ] . ->

имеют высший приоритет и группируются слева направо. Унарные операции

* & - ! ~ ++ -- sizeof ( имя_типа )

имеют приоритет ниже первичных операций, но выше, чем любая бинарная операция, и группируются справа налево. Бинарные операции группируются слева направо; их приоритет понижается в соответствии с приведенным ниже перечнем.

биноп: * / % + - >> <<



ВЫРАЖЕНИЯ И ОПЕРАЦИИ


Основные пункты данного раздела, описывающие различные операции, упорядочены по убыванию приоритета операций. Поэтому, например, выражения, являющиеся операндами + (см. Аддитивные операции), - это выражения, описанные в пунктах Первичные выражения, Унарные операции и Мультипликативные операции. Операции, описанные в рамках одного пункта, имеют одинаковый приоритет. В каждом пункте определяется левая или правая ассоциативность обсуждаемых операций. Приоритет и ассоциативность операций резюмируются в разделе СВОДКА СИНТАКСИСА.

В остальном порядок обработки выражений не определен. В частности, компилятор волен вычислять подвыражения в том порядке, который он считает наиболее эффективным, даже если подвыражения имеют побочные эффекты. Выражения, содержащие коммутативные и ассоциативные операции (*, +, &, |, ^), могут быть произвольным образом переупорядочены даже при наличии скобок. Чтобы навязать определенный порядок вычислений, нужно использовать явное присваивание временным переменным.

Реакция на переполнение и деление на нуль при вычислении выражений не определяется. Большинство существующих реализаций игнорируют целые переполнения; интерпретация деления на 0 и нюансы вещественной арифметики различаются для разных компьютеров и обычно регулируются библиотечными функциями.



Выражения-присваивания


Выражение-присваивание имеет следующий вид:

пер присвоп выражение

где присвоп - одна из шести операций присваивания:

= += -= *= /= %=

Предпочтительным считается то же значение пер, что и предпочтительное значение выражения.

В результате присваивания вида

пер = выражение

числовое и текстовое значение пер становятся равными соответствующим значениям выражения. Присваивание

пер оп= выражение

эквивалентно присваиванию

пер = пер оп выражение

где оп - одна из операций +, -, *, /, %. Операции присваивания правоассоциативны и имеют меньший приоритет, чем любая другая операция. Так, выражение a += b *= c-2 эквивалентно последовательности присваиваний

b = b * (c-2) a = a + b



Выражения сравнения


Произвольные выражения, включающие сравнения цепочек символов или чисел, являются допустимыми шаблонами awk'а. Например, если требуется распечатать информацию только о странах, население которых превышает 100 миллионов, можно использовать шаблон

$3 > 100

Простая awk-программа, состоящая из одного этого шаблона без всякого действия, распечатает только те записи, значение третьего поля в которых больше 100:

Russia 8650 262 Asia China 3692 866 Asia USA 3615 219 North America Brazil 3286 116 South America India 1269 637 Asia

Чтобы напечатать названия стран, расположенных в Азии, надо набрать программу

$4 == "Asia" { print $1 }

Ее результатом будет

Russia China India

Проверяемые условия задаются знаками операций <, <=, ==, !=, >=, >. Если в выражении сравнения оба операнда являются числами, выполняется числовое сравнение; в противном случае операнды сравниваются как цепочки символов. Так, шаблон

$1 >= "S"

отбирает строки, начинающиеся с S, T, U и т.д.; в нашем случае это

USA 3615 219 North America Sudan 968 19 Africa

Если дополнительная информация о типе отсутствует, поля трактуются как цепочки символов, поэтому программа

$1 == $4

сравнивает первое и четвертое поля как цепочки символов и выводит на печать единственную строку:

Australia 2968 14 Australia



curses содержит подпрограммы для записи


curses содержит подпрограммы для записи в stdscr, похожие на подпрограммы из stdio(3S) для записи в файл. Они позволяют:
Выводить одиночный символ - addch().
Выводить цепочку символов - addstr().
Форматировать строку из нескольких входных аргументов - printw().
Перемещать курсор, возможно, с одновременным выводом символа (символов) - move(), mvaddch(), mvaddstr(), mvprintw().
Очистить весь экран или его часть - clear(), erase(), crltoeol(), clrtobot().
Далее следуют описания и примеры применения этих подпрограмм.
Предостережение
Библиотека curses располагает своим собственным набором подпрограмм ввода/вывода. Если Вы работаете с curses, Вам не следует использовать для ввода/вывода других подпрограмм или обращений к системе, например, read(2) или write(2); в противном случае при выполнении подпрограммы могут возникнуть нежелательные эффекты.
addch( )

СИНТАКСИС
#include <curses.h>
int addch (ch) chtype ch;


ОПИСАНИЕ
  addch() записывает один символ в stdscr.

Этот символ имеет тип chtype, который определяется в <curses.h>. chtype содержит данные и атрибуты (об атрибутах см. ниже в разделе Атрибуты вывода).
Работая с переменными этого типа, объявляйте их принадлежащими именно к типу chtype, а не к тому основному типу (скажем, short), через который объявляется chtype
в <curses.h>. Это обеспечит совместимость с будущими версиями.
addch() производит некоторую перекодировку. Так, она преобразует:
символ перевода строки в последовательность, обеспечивающую очистку до конца строки и переход к новой строке; символ табуляции в соответствующее количество пробелов; другие управляющие символы в их запись в нотации ^X.
addch() обычно возвращает OK, за исключением случая вывода символа в правом нижнем углу нероллируемого окна, когда возвращается ERR.
addch() является макросом.

ПРИМЕР
  #include <curses.h>
main () { initscr (); addch ('a'); refresh (); endwin (); }

Эта программа выводит следующее:

См. также программу show в разделе Примеры программ, работающих с curses.


addstr( )

СИНТАКСИС
  #include <curses.h>
int addstr (str) char *str;


ОПИСАНИЕ
  addstr() выводит цепочку символов в stdscr.

addstr() вызывает addch() для вывода каждого символа.
addstr() производит ту же перекодировку, что и addch().
addstr() возвращает OK при успешном завершении и ERR
при ошибке.
addstr() является макросом.

ПРИМЕР
  См. примеры программ во Введении.

printw( )

СИНТАКСИС
  #include <curses.h>
int printw(fmt [,arg...]) char *fmt;


ОПИСАНИЕ
  printw() осуществляет форматированный вывод в stdscr.

Подобно printf(3S), printw() получает формат и переменное количество аргументов.
Подобно addstr(), printw() обращается к addch() для вывода каждого символа.
printw() возвращает OK при успешном завершении и ERR
при ошибке.

ПРИМЕР
  #include <curses.h>
main () { char *title = "Не указано"; int no = 0; . . . initscr (); . . . printw ("%s отсутствует на складе.", title); printw ("Попросите кассира заказать %d для Вас.\n", no); refresh (); endwin (); }

Эта программа выводит следующее:

move( )

СИНТАКСИС
  #include <curses.h>
int move (y, x); int y, x;


ОПИСАНИЕ
  move() устанавливает курсор в позицию "строка y, колонка x" окна stdscr.

Обратите внимание, что первым аргументом move() является координата y, а вторым - x. Координаты левого верхнего угла stdscr равны (0, 0), а правого нижнего - (LINES-1, COLS-1). Подробнее см. в разделе Подпрограммы initscr(), refresh() и endwin().
Перемещение курсора можно производить одновременно с выводом данных, а именно:
mvaddch (y, x, ch) перемещает курсор и выводит символ; mvaddstr (y, x, str) перемещает курсор и выводит цепочку символов; mvprintw (y, x, fmt[,arg...]) перемещает курсор и выводит форматированную строку.
move() возвращает OK при нормальном завершении и ERR
при ошибке. К ошибке приводит попытка сдвинуть курсор в позицию, не находящуюся между (0, 0) и (LINES-1, COLS-1).
move() является макросом.



ПРИМЕР
  #include <curses.h>
main() { initscr (); addstr ("Курсор должен быть здесь -->"); addstr (" если move () работает."); printw ("\n\n\nНажмите <CR> для завершения теста."); move (0, 28); refresh (); getch (); /* Вводит <CR>, см. ниже */ endwin (); }

Эта программа выводит следующее:

После нажатия возврата каретки экран будет выглядеть так:

Другой пример использования move() можно найти в программе scatter в разделе Примеры программ, работающих с curses.
clear( ), erase( )

СИНТАКСИС
  #include <curses.h>
int clear ( )
int erase ( )


ОПИСАНИЕ
  Обе подпрограммы заполняют все окно stdscr пробелами.

clear() допускает наличие на экране мусора, о котором она не знает; эта подпрограмма вызывает сначала erase(), а затем clearok(), что приводит к полной очистке физического экрана при следующем вызове refresh() для stdscr. Подробнее о clearok() см. curses(3X).
initscr() автоматически вызывает clear().
clear() всегда возвращает OK, erase() не возвращает ничего полезного.
И clear(), и erase() являются макросами.
clrtoeol( ), clrtobot( )

СИНТАКСИС
  #include <curses.h>
int clrtoeol ( )
int clrtobot ( )


ОПИСАНИЕ
  clrtoeol() заменяет остаток строки пробелами.

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

ПРИМЕР
  Приведем текст программы, использующей clrtobot().
#include <curses.h>
main () { initscr (); addstr ("Нажмите <CR> для удаления отсюда "); addstr ("до конца строки и далее."); addstr ("\nУдалите это тоже.\nИ это."); move (0, 32); refresh (); getch (); clrtobot (); refresh (); endwin (); }

Эта программа выводит следующее:

Обратите внимание на два вызова refresh() - первый выводит этот текст на экран, второй очищает экран с отмеченного курсором места до конца.
После нажатия возврата каретки экран будет выглядеть так:

Примеры использования clrtoeol() см. в программах show и two в разделе Примеры программ, работающих с curses.


Вывод на печать


Действию может не соответствовать шаблон; в таком случае действие выполняется для всех строк, как следующая простая программа распечатки:

{ print }

Это одно из простейших действий, выполняемых awk'ом. Оно выдает каждую строку на печать. Полезнее бывает печатать не всю строку, а одно или несколько полей. Например, при использовании файла countries, описанного ранее, командная строка

awk '{ print $1, $3 }' countries

распечатывает название и население стран:

Russia 262 Canada 24 China 866 USA 219 Brazil 116 Australia 14 India 637 Argentina 26 Sudan 19 Algeria 18

Точка с запятой в конце списка операторов необязательна. awk

допускает как действие

{ print $1 }

так и

{ print $1; }

Эти два действия эквивалентны. Однако, если требуется поместить два awk-оператора в одну строку awk-текста, точка с запятой необходима. Например, число 5 можно напечатать так:

{ x=5; print x }

Скобки для оператора print также необязательны. Действие

{ print $3, $2 }

эквивалентно

{ print ($3, $2) }

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

{ print "hello, world " }

Как уже было сказано, awk предоставляет несколько специальных переменных, значениями которых бывает удобно управлять, например, FS и RS. В следующем примере вводятся еще две специальные переменные. NR и NF имеют целочисленные значения, равные соответственно номеру текущей прочитанной записи и числу полей в ней. Так, действие

{ print NR, NF, $0 }

для каждой записи выводит на печать ее номер, число полей в ней, а затем саму запись. Применение этой программы к файлу countries дает следующий результат:

1 4 Russia 8650 262 Asia 2 5 Canada 3852 24 North America 3 4 China 3692 866 Asia 4 5 USA 3615 219 North America 5 5 Brazil 3286 116 South America 6 4 Australia 2968 14 Australia 7 4 India 1269 637 Asia 8 5 Argentina 1072 26 South America 9 4 Sudan 968 19 Africa 10 4 Algeria 920 18 Africa


Программа
{ print NR, $1 }
выводит на печать
1 Russia 2 Canada 3 China 4 USA 5 Brazil 6 Australia 7 India 8 Argentina 9 Sudan 10 Algeria
Этот пример демонстрирует полезный прием: как приписать к элементам списка их последовательные номера. Оператор print без аргументов печатает текущую входную запись. Чтобы напечатать пустую строку, надо использовать
{ print "" }
awk предоставляет также оператор printf, позволяющий программисту самостоятельно задавать требуемый формат вывода. Для распечатываемых числовых значений print использует по умолчанию формат %.6g. Оператор
printf "формат", выражение, выражение, ...
преобразует указанные в списке аргументов выражения в соответствии со спецификацией, задаваемой цепочкой формат, и распечатывает их. Оператор printf практически идентичен функции printf(3S) из C-библиотеки. Например, действие
{ printf "%10s %6d %6d\n", $1, $2, $3 }
распечатывает $1 как цепочку из 10 символов (выравненную по правому краю). Второе и третье поля (6-значные числа) образуют аккуратную таблицу, состоящую из двух колонок:
Russia 8650 262 Canada 3852 24 China 3692 866 USA 3615 219 Brazil 3286 116 Australia 2968 14 India 1269 637 Argentina 1072 26 Sudan 968 19 Algeria 920 18
Оператор printf не порождает автоматически никаких выходных разделителей полей или переводов строк. Программист должен добавить их самостоятельно, как это сделано в данном примере. Можно указывать управляющие символы \n, \t, \b (забой), \r
(возврат каретки).
Есть еще одна, третья ситуация, в которой может иметь место выдача на стандартный вывод. Это происходит, когда в awk-операторе не специфицировано действие, а только шаблон. В таком случае распечатывается запись $0 целиком. Например, программа
/x/
распечатывает все записи, содержащие символ x.
Вывод на печать сопровождается использованием двух специальных переменных, OFS и ORS. По умолчанию они устанавливаются равными, соответственно, пробелу и символу перевода строки. Переменная OFS выдается на стандартный вывод, когда в списке аргумен- тов оператора print встречается запятая. Например, оператор:
{ x="hello"; y="world" print x, y }
выводит
hello world
Однако, если запятой нет:
{ x="hello"; y="world" print x y }
результат будет таким:
helloworld
Чтобы вывести запятую, можно либо явно указать ее в операторе print:
{ x="hello"; y="world" print x"," y }
либо в секции BEGIN переопределить OFS:
BEGIN { OFS=", " } { x="hello"; y="world" print x, y }
В обоих случаях результатом будет:
hello, world
Отметим, что при печати $0 выходной разделитель полей не используется.


Вывод в каналы


Вывод можно направить не только в обычный файл, но и в канал. Пример:

{ if ($2 == "XX") print | "mailx mary" }

где mary - имя, под которым пользователь входит в систему. Любая запись со вторым полем, равным XX, отправляется пользователю mary в виде почты. awk ждет, пока не выполнится программа целиком, а затем выполняется команда, соединенная с ней каналом [в данном случае - команда mailx(1)]. Программа

{ print $1 | "sort" }

выделяет из каждой входной записи первое поле, сортирует эти поля и затем распечатывает их.

Еще один пример использования каналов - следующая общеупотребимая конструкция, гарантирующая, что весь вывод обязательно будет направлен на Ваш терминал:

{ print ... | "cat -v > /dev/tty" }

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



Вывод в различные файлы


shell позволяет переназначать стандартный вывод в файл. awk

также позволяет направить вывод во многие различные файлы, причем это можно сделать внутри awk-программы. Вернемся к входному файлу countries. Допустим, требуется вывести всю информацию о странах из Азии в файл, называемый ASIA, всю информацию о странах из Африки в файл AFRICA, и т.д. Это можно сделать при помощи следующей awk-программы:

{ if ($4 == "Asia") print > "ASIA" if ($4 == "Europe") print > "EUROPE" if ($4 == "North") print > "NORTH_AMERICA" if ($4 == "South") print > "SOUTH_AMERICA" if ($4 == "Australia") print > "AUSTRALIA" if ($4 == "Africa") print > "AFRICA" }

Операторы, управляющие последовательностью вычислений, обсуждаются позднее.

В общем случае можно направить вывод в файл, указав его имя после оператора print или printf. В операторе вида

printf > "имя_файла"

имя_файла задает файл, в который направляются данные. У оператора могут быть указаны произвольные допустимые аргументы.

Подчеркнем, что имя файла заключается в кавычки. При отсутствии кавычек имена файлов трактуются как переменные, которые могут оказаться неинициализированными; в таком случае весь вывод направляется в stdout, если он не переназначен в командной строке.

Если вместо > указать >>, вывод будет добавлен в конец файла, а не заменит его содержимое. Отметим, что существует ограничение на максимальное число файлов, в которые можно таким образом направить вывод. Сейчас оно равно десяти.



Взаимодействие curses и terminfo


Подпрограммы пакета curses извлекают из базы данных terminfo

информацию о том типе терминала, на котором программа выполняется. В дальнейшем этот терминал мы будем называть текущим терминалом.

Предположим, что программа, текст которой приведен в конце раздела Что такое curses?, выполняется на терминале AT&T Teletype 5425. Чтобы отработать должным образом, то есть вывести BullsEye в центр экрана, программа должна иметь информацию о количестве строк и столбцов на экране. Эти данные хранятся в описании терминала AT&T Teletype 5425 в базе данных terminfo. Чтобы получить их, программе, работающей с curses, достаточно знать только название терминала, на котором она выполняется. Можно передать ей это название, поместив его в переменную окружения $TERM при подключении к системе UNIX, или путем присвоения значения и помещения в окружение переменной $TERM в Вашем файле .profile [см. profile(4)]. Располагая значением $TERM, программа, использующая curses, может извлечь описание текущего терминала из базы данных terminfo.

Пусть, например, в .profile включены следующие строки:

TERM=5425 export TERM tput init

В первой строке устанавливается название терминала, а во второй оно помещается в окружение. Третья строка примера требует от системы UNIX проинициализировать текущий терминал, то есть обеспечить соответствие состояния терминала его описанию в базе данных terminfo. (Порядок этих строк важен. Чтобы при вызове tput произошла правильная инициализация текущего терминала, $TERM должна быть уже установлена и помещена в окружение). Если, имея такие строки в файле .profile, Вы запустите программу, работающую с curses, она получит нужную ей информацию о терминале из файла, соответствующего значению $TERM, то есть из файла /usr/lib/terminfo/a/att5425.



Взаимодействие с shell'ом


Обычно awk-программа либо помещается в файл, либо указывается в командной строке (при этом ее текст заключается в одинарные кавычки):

awk '{ print $1 }' ...

Использование одинарных кавычек позволяет избежать интерпретации shell'ом текста awk-программы. Это необходимо, потому что многие специальные символы awk'а совпадают со специальными сим- волами shell'а (например, $ или ").

Предположим, требуется написать awk-программу, печатающую n-ое поле записи, где n - параметр, задаваемый во время запуска программы. Более подробно, мы хотим написать программу с именем field, которая по запросу

field n

выполняла бы команду

awk '{ print $n }'

Как передать значение n в awk-программу?

Это можно сделать несколькими способами. Во-первых, можно поместить в файл field строку

awk '{ print $'$1' }'

В данном случае существенно отсутствие пробелов: эта запись воспринимается как единый аргумент, несмотря на то, что указаны две пары кавычек. $1 находится вне кавычек, доступен shell'у и, следовательно, должным образом подставится в текст программы при выполнении shell-процедуры field.

Еще один способ решения задачи основывается на том, что shell интерпретирует $-параметры, которые содержатся в цепочках, заключенных в двойные кавычки:

awk "{ print \$ $1 }"

Небольшая хитрость состоит в экранировании первого символа $ с помощью \; $1, как и в предыдущем случае, заменяется при обращении к field на требуемое число.



Yacc(1)


С помощью yacc(1) можно сгенерировать C-программу для выполнения синтаксического анализа входного потока согласно правилам, заданным в файле спецификаций. Кроме описания грамматики, этот файл содержит описания действий, которые должны быть выполнены, когда лексемы во входном потоке сопоставляются с тем или иным правилом грамматики. Для выделения лексем во входном потоке удобно использовать lex.



ЗАГОЛОВКИ СЕКЦИЙ


В каждом объектном файле есть таблица заголовков секций, определяющая расположение данных в файле. В этой таблице каждой секции соответствует один элемент. В следующей таблице описана информация, содержащаяся в заголовке секции.

Байты Описание Имя Смысл
0-7 char s_name 8-символьное имя секции, дополненное нулями
8-11 long int s_paddr Физический адрес секции
12-15 long int s_vaddr Виртуальный адрес секции
16-19 long int s_size Размер секции в байтах
20-23 long int s_scnptr Указатель в файле на содержимое секции
24-27 long int s_relptr Указатель в файле на информацию о настройке ссылок
28-31 long int s_lnnoptr Указатель в файле на информацию о номерах строк
32-33 unsigned short s_nreloc Число элементов в таблице настройки ссылок
34-35 unsigned short s_nlnno Число элементов в таблице номеров строк
36-39 long int s_flags Флаги (см. следующую таблицу)

Размер секции увеличивается до ближайшего числа, кратного четырем. Указатели в файле - это смещения в байтах, позволяющие определить начало данных в этой секции и ее таблиц настройки ссылок и номеров строк. Указатели могут использоваться функцией fseek(3S) операционной системы UNIX.



ЗАГОЛОВОК ФАЙЛА


Заголовок файла состоит из 20 байт; его формат показан в следующей таблице. Последние два байта содержат флаги, используемые редактором связей ld(1) и другими утилитами, обрабатывающими объектные файлы.

Байты Описание Имя Смысл
0-1 unsigned short f_magic Магическое число
2-3 unsigned short f_nscns Количество секций в файле
4-7 long int f_timdat Дата/время создания файла. Выражается количеством секунд, прошедших с 00:00:00 1 января 1970г. (по Гринвичу)
8-11 long int f_symptr Указатель в файле. Содержит адрес начала таблицы имен
12-15 long int f_nsyms Число элементов в таблице имен
16-17 unsigned short f_opthdr Размер вспомогательного заголовка в байтах
18-19 unsigned short f_flags Флаги (см. следующую таблицу)



Заголовок секции неинициализированных данных


Единственным отклонением от обычной структуры таблицы заголовков секций является заголовок для секции неинициализированных данных .bss. Секция .bss имеет размер, с ней связаны имена, которые ссылаются на нее, и имена, которые определены в ней. В то же время информация о настройке ссылок, таблица номеров строк и содержимое отсутствуют. Таким образом, секции .bss соответствует элемент таблицы заголовков секций, но сама она не занимает места где-либо еще в файле. В заголовке секции .bss значения числа элементов в таблицах настройки ссылок и номеров строк, а также значения всех указателей равны нулю. То же верно и для секций типа STYP_NOLOAD и STYP_DSECT.



Замена лексем


Командная строка вида

#define идентификатор цепочка_лексем

заставляет препроцессор заменить последующие вхождения идентификатора на указанную цепочку_лексем. Знаки точки с запятой внутри или в конце цепочки_лексем являются частью цепочки. Строка вида

#define идентификатор(идентификатор,...) цепочка_лексем

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

Фактические параметры - это последовательности лексем, разделенные запятыми; однако запятые в текстовых константах или внутри вложенной пары скобок параметры не разделяют. Число формальных и фактических параметров должно быть одинаковым. Препроцессор просматривает текстовые и символьные константы в цепочке_лексем из определения и выделяет в них формальные параметры, однако вне цепочки_лексем подобные константы на предмет выполнения подстановок не просматриваются.

После подстановки цепочки_лексем результирующий текст вновь просматривается в поисках других вхождений идентификаторов, определенных директивами #define. Длинное определение может быть продолжено на следующей строке, для чего в конце продолжаемой строки нужно поместить знак \.

Описанная возможность особенно полезна для наделения констант мнемоничными именами, как в следующем примере:

#define TABSIZE 100

int table [TABSIZE];

Командная строка вида

#undef идентификатор

заставляет препроцессор отбросить определение идентификатора (если таковое было).

Если идентификатор в последующей директиве #define определяется повторно, а директивы #undef между определениями нет, то две цепочки_лексем сравниваются текстуально. Если они не совпадают (все символы-"невидимки" в данном контексте эквивалентны), считается, что идентификатор переопределяется. Об этом выдается предупреждающее сообщение.



Запуск программ на выполнение


Есть два способа запустить на выполнение awk-программу, состоящую из операторов вида шаблон {действие}:

1. Если программа короткая (одна-две строки), зачастую проще всего указать программу в качестве первого аргумента командной строки:

awk 'программа' [имя_файла...]

где программа - это awk-программа, которую надо выполнить, а имя-файла... - необязательный аргумент, определяющий входной файл (файлы). Отметим, что одинарные кавычки, в которые заключен текст программы, нужны для того, чтобы shell воспринял всю эту цепочку (программу) как первый аргумент команды awk. Например, чтобы выполнить над файлом file1 awk-программу

/x/ { print }

надо передать shell'у команду

awk '/x/ { print }' file1

Если входной файл не специфицирован, awk ожидает исходные данные со стандартного ввода, stdin. Кроме того, можно специфицировать, что stdin будет одним из входных файлов; для этого в командной строке указывается символ -. Командная строка

awk 'программа' файл1 -

вызывает сначала обработку файла1, а затем - стандартного ввода.

2. Если, напротив, программа длинная, или если Вы хотите сохранить ее для того, чтобы использовать впоследствии, удобно поместить программу в отдельный файл, и указать awk'у имя файла, из которого ее надо брать. Это можно сделать, использовав в командной строке опцию -f:

awk -f имя_программы [имя_файла ...]

Здесь имя_программы указывает файл, содержащий awk-программу, а имя_файла ... - необязательный аргумент, определяющий входной файл (файлы), среди которых, как сказано выше, может быть стандартный ввод.

Проиллюстрируем альтернативные способы запуска на обработку awk-программ. Выполнение shell'ом командной строки

awk 'BEGIN { print "hello, world"; exit }'

приводит к выдаче на стандартный вывод

hello, world

Эту же awk-программу можно выполнить, поместив ее текст

BEGIN { print "hello, world"; exit }

в файл awkprog, а затем набрав в shell'е команду

awk -f awkprog

Результат будет тот же.



ЗАПУСК УТИЛИТЫ LINT


Вызов утилиты lint имеет вид

lint [опции] файлы описатели_библиотек

Опции определяют набор проверок, выполняемых lint'ом, и степень подробности выдаваемых сообщений. Имена верифицируемых файлов должны оканчиваться на .c или .ln. Описатели-библиотек - это список имен библиотек, которые должны использоваться при анализе программы.

Опциям утилиты lint приписан следующий смысл:

-a  Не выдавать сообщений о присваиваниях long-значений переменным, не описанным как long.
 -b  Не выдавать сообщений о недостижимых операторах break.
 -c  Ограничиться проверкой ошибок в пределах каждого из .c-файлов; поместить информацию о внешних свойствах в файлы, оканчивающиеся на .ln.
 -h  Не применять эвристики (предназначенные для того, чтобы попытаться обнаружить ошибки, улучшить стиль и уменьшить сложность программы).
 -n  Не проверять совместимость со стандартной или мобильной lint-библиотеками.
 -o библ  Создать из исходных файлов lint-библиотеку с именем llib-lбибл.ln.
 -p  Попытаться проверить мобильность программы.
 -u  Не выдавать сообщения о функциях и внешних переменных, используемых, но не определенных, или определенных, но не используемых.
 -v  Не выдавать сообщения о неиспользуемых аргументах функций.
 -x  Не сообщать о внешних переменных, которые нигде не используются.

Если требуется указать более одной опции, их следует объединять в единый аргумент, такой как -ab или -xha.

Имена файлов, содержащих исходные C-тексты, должны оканчиваться на .c, что обязательно не только для lint'а, но и для C-компилятора.

Помимо перечисленных, утилита lint поддерживает опции вида

-lбибл

Они специфицируют библиотеки, содержащие функции, используемые в анализируемой C-программе. Исходный текст программы проверяется на совместимость с этими библиотеками. Проверка выполняется на основе файлов описания библиотек, имена которых формируются исходя из указанных аргументов. Данные файлы всегда открываются комментарием


/* LINTLIBRARY */

и содержат серию фиктивных определений функций. Наиболее существенными частями в этих определениях являются указания, возвращает ли функция значение и если да, то какого типа, каковы число и тип аргументов. Чтобы специфицировать особые свойства некоторых библиотечных функций, можно воспользоваться комментариями VARARGS и ARGSUSED. Как это сделать, написано в сле- дующем разделе, ТИПЫ СООБЩЕНИЙ LINT'А.

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

По умолчанию lint проверяет программы совместно со стандартной lint-библиотекой, содержащей описания функций, обычно загружаемых при выполнении C-программ. Если указана опция -p, проверяется еще один файл, содержащий описания функций из стандартной библиотеки, которая считается мобильной. Чтобы подавить всякую проверку библиотек, можно воспользоваться опцией -n.

Дополнительную информацию об опциях команды lint можно найти в статье lint(1) Справочника пользователя.




ЗАПУСК УТИЛИТЫ MAKE


Описание утилиты make содержится в статье make(1) Справочника пользователя.



Зарезервированные слова


Некоторые языки программирования позволяют использовать слова, обычно резервируемые (подобные if), в качестве меток или имен переменных, и гарантируют при этом, что такое использование не приведет к конфликту. При работе с yacc'ом сделать это чрезвычайно тяжело. Трудно передать лексическому анализатору информацию о том, что одно вхождение if является ключевым словом, а другое - именем переменной. Пользователь может применить механизм, описанный в предыдущем пункте, но сделать это непросто.

Есть несколько предложений, как при развитии yacc'а облегчить решение данной проблемы. А пока лучше резервировать ключевые слова, то есть запрещать их использование в качестве идентификаторов. В пользу этого есть и убедительные стилистические причины.



Значения функций


Иногда функции возвращают значения, которые нигде не используются; иногда в программах делается попытка использовать значения функций, которых они не возвращают. lint рассматривает данную проблему с нескольких точек зрения.

Если в теле функции встречается как оператор

return (выражение);

так и оператор

return ;

это настораживает; lint выдаст сообщение

function name has return(e) and return

Сложнее всего проанализировать тот возврат из функции, который происходит при достижении потоком управления ее конца. Рассмотрим пример.

f (a) { if (a) return (3); g (); }

Если условие ложно, f вызовет g, а затем завершится, не возвра- тив никакого определенного результата; такая конструкция будет причиной сообщения lint'а. Если g, подобно exit, никогда управ- ления не возвращает, сообщение тем не менее будет выдаваться, хотя на самом деле никакой ошибки нет. Комментарий

/* NOTREACHED */

в исходном тексте будет подавлять данное сообщение.

При анализе программы в целом lint выявляет ситуации, когда функция возвращает значения, которые иногда (или никогда) не используются. Если возвращаемые значения никогда не используются, в определении функции имеется некоторая неэффективность. Можно "легально" проигнорировать результат функции при помощи явного преобразования к типу void, например

(void) fprintf (stderr, "Файл занят. Попробуйте позже!\n");

Игнорирование результата может быть просто проявлением дурного стиля (например, частичное отсутствие проверок неудачного завершения функции).

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



Знаки операций


awk предоставляет арифметические, логические операции, операции сравнения и присваивания, аналогичные соответствующим операциям языка программирования C, а также операции сопоставления с шаблонами, заданными регулярными выражениями, аналогичные тем, ко- торые используются в egrep(1).

Следующие таблицы содержат описание операций языка awk:

Операции присваивания:

знак операции   использование   описание 
=  присвоить    +=  сложить и присвоить   X += Y аналогично X = X+Y -=  вычесть и присвоить   X -= Y аналогично X = X-Y *=  умножить и присвоить   X *= Y аналогично X = X*Y /=  разделить и присвоить   X /= Y аналогично X = X/Y %=  вычислить остаток и присвоить  X %= Y аналогично X = X%Y ++  префиксное и постфиксное увеличение   ++X и X++ аналогично X = X+1 --  префиксное и постфиксное уменьшение   --X и Y-- аналогично X = X-1

Арифметические операции:

 знак операции   описание 
+  унарный и бинарный плюс  -  унарный и бинарный минус  *  произведение  /  частное  %  остаток от деления  (...)  группировка 

Операции сравнения:

 знак операции   описание 
<  меньше  <=  меньше или равно  ==  равно  !=  не равно  >=  больше или равно  >  больше 

Логические операции:

 знак операции   описание 
&&  и   или  !  отрицание 

Операции сопоставления:

 знак операции   описание 
~  сопоставляется  !~  не сопоставляется 



Звонки, свист, вспышки


Иногда бывает нужно привлечь внимание пользователя. Для этого в пакет curses включены две подпрограммы, которые могут заставить терминал звенеть, а его экран - вспыхивать.

flash() заставляет экран терминала вспыхнуть, если это возможно, если же нет - включает звуковой сигнал. Вспышка является альтернативой звуковому сигналу и особенно полезна, когда звук мешает кому-либо, сидящему неподалеку от терминала. Подпрограмма beep() вызывается, когда нужно подать звуковой сигнал (если это почему-либо невозможно на данном терминале, beep() пытается произвести вспышку).

beep( ), flash( )

СИНТАКСИС
  #include <curses.h>

int flash ( )

int beep ( )

ОПИСАНИЕ
  flash() пытается заставить вспыхнуть экран терминала. Если это невозможно, flash() пытается включить на терминале звуковой сигнал.

beep() пытается включить на терминале звуковой сигнал. Если это невозможно, beep() пытается заставить вспыхнуть экран терминала.

Ни одна из подпрограмм не возвращает какого-либо осмысленного значения.