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

         

Форма описания синтаксиса языка


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

{ выражение }

обозначает необязательное выражение, заключенное в фигурные скобки. В конце главы приводится СВОДКА СИНТАКСИСА.



Фортран


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



Функции


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

exp (выражение) int (выражение) log (выражение) sqrt (выражение)

Арифметические функции (exp, int, log, sqrt) вычисляют, соответственно, экспоненту, целую часть, натуральный логарифм и квадратный корень числового значения выражения. (выражение) может быть опущено, в таком случае функция применяется к $0. Предпочтительным считается числовое значение арифметической функции.

Операции над цепочками символов:

getline index (выражение1, выражение2) length length (выражение) split (выражение, идентификатор, выражение2) split (выражение, идентификатор) sprintf (формат, выражение1, выражение2 ...) substr (выражение1, выражение2) substr (выражение1, выражение2, выражение3)

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

Функция index (e1, e2) по текстовым значениям выражений e1 и e2

находит первое вхождение цепочки e2 в e1 и возвращает номер начальной позиции. Если e2 не входит в e1, функция index возвращает 0. Пример:



index ("abc", "bc") = 2 index ("abc", "ac") = 0

Функция length без аргументов возвращает число символов в текущей входной записи. Если указан аргумент-выражение, length (e)

возвращает число символов в текстовом значении e. Пример:

length ("abc") = 3 length (17) = 2

Функция split (e, array, sep) разбивает текстовое значение выражения e на поля, которые помещаются затем в array[1], array[2], ... array[n]; в качестве разделителя полей используется текстовое значение аргумента sep. Результат, возвращаемый функцией, равен числу обнаруженных полей. Если третий аргумент опущен, функция split в качестве разделителя полей использует текущее значение FS. Например, после обращения


n = split ($0, a)

a[1], a[2], ... a[n] - это та же самая последовательность, что и $1, $2, ... $NF.

Функция sprintf (f, e1, e2, ...) преобразует текстовые значения выражений e1, e2, ... в соответствии с форматом, специфицированным текстовым значением выражения f. Соглашения об управлении форматом такие же, как и для функции printf(3S) в языке программирования C (исключение: не допускается использование символа * для обозначения ширины поля или точности).

Функция substr (string, pos) возвращает окончание цепочки символов string, начиная с позиции pos. Функция substr (string, pos, length) возвращает подцепочку аргумента string, начинающуюся с позиции pos и имеющую длину length. Если длина pos+length

больше, чем длина аргумента string, то оба варианта substr эквивалентны. Пример:

substr ("abc", 2, 1) = "b" substr ("abc", 2, 2) = "bc" substr ("abc", 2, 3) = "bc"

Значения аргумента pos, меньшие 1, принимаются равными 1. Отрицательное или нулевое значение аргумента length приводит к пустому результату. Предпочтительным для функций sprintf и substr

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




Функция


Вспомогательные элементы для функций имеют следующий формат:

Байты Описание Имя Смысл
0-3 long int x_tagndx Номер основного элемента для функции
4-7 long int x_fsize Размер функции в байтах
8-11 long int x_lnnoptr Указатель в файле на соответствующий элемент таблицы номеров строк
12-15 long int x_endndx Номер элемента, следующего за элементами для этой функции
16-17 unsigned short x_tvndx Номер адреса функции в таблице адресов переходов (в ОС UNIX не используется)



Где можно найти справочную информацию


Системные вызовы описаны в алфавитном порядке в разделе 2 Справочника программиста. Информация о библиотечных функциях содержится в разделе 3. В данном Руководстве выше были описаны функции из первого подраздела раздела 3. В остальных его подразделах имеется информация о:

3M - функциях, составляющих математическую библиотеку, libm.

3X - различных специальных функциях.



Генерация отчетов


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

Smith draw 3 Brown eqn 1 Jones nroff 4 Smith nroff 1 Jones spell 5 Brown spell 9 Smith draw 6

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

{ use [$1 " " $2] += $3 } END { for (np in use) print np "\t" use [np] | "sort +0 +2nr" }

Если использовать в качестве входного файл prog.usage, данная программа сформирует следующий результат:

Brown eqn 1 Brown spell 9 Jones nroff 4 Jones spell 5 Smith draw 9 Smith nroff 1

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

{ if ($1 != prev) { print $1 ":" prev = $1 } print "\t" $2 "\t" $3 }

Переменная prev используется для того, чтобы убедиться, что каждое уникальное значение $1 печатается ровно один раз. Команда

awk -f list1 prog.usage | awk -f format1

выдаст такой результат:

Brown: eqn 1 spell 9 Jones: nroff 4 spell 5 Smith: draw 9 nroff 1

Часто оказывается удобным объединить несколько разных awk-программ с другими командами shell'а, такими как sort(1), что и было сделано в программе list1.



Группировка выходных секций


Подразумеваемый алгоритм размещения секций для редактора связей ld(1) таков:

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

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

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

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

SECTIONS { .text размер_заголовков : { *(.init) *(.text) } } GROUP BIND (NEXT (граница_выравнивания) + (SIZEOF (.text) + ADDR (.text)) % 0x2000) : { .data : { } .bss : { } } }

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

Если необходимо сгруппировать секции .text, .data и .bss, следует использовать такое предложение SECTIONS:

SECTIONS { GROUP: { .text: {} .data: {} .bss: {} } }


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

Группу выходных секций как единое целое можно связать с конкретным адресом или произвести ее выравнивание на определенную границу, просто указав нужный адрес в предложении GROUP. Так, для связывания с адресом 0xС0000 достаточно воспользоваться конструкцией

GROUP 0xС0000: {

а для выравнивания на границу, кратную 0x10000, - конструкцией

GROUP ALIGN (0x10000): {

Если сделать одно из этих добавлений к указанному выше примеру, то выходная секция .text будет размещена по адресу 0xС0000 (соответственно выравнена на границу 0x10000); затем остальные члены группы, в порядке их указания, размещаются по ближайшим доступным адресам.

Если предложение GROUP не используется, то каждая выходная секция рассматривается отдельно:

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

Секция .text связывается с виртуальным адресом 0x0 (если он находится в пределах конфигурируемой памяти). Секция .data связывается с виртуальным адресом, кратным 0x2000. Если хватит места, секция .bss будет следовать сразу за секцией .text, если же не хватит - то сразу за секцией .data. Порядок, в котором имена выходных секций появляются в предложении SECTIONS, не определяет порядка следования этих секций в выходном файле.




Идентификаторы


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



Идентификаторы (имена)


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



Имена и функции


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

Имя функции
.bf
Локальные имена
.ef



ИМЕНА SCCS-ФАЙЛОВ (ТИЛЬДА)


Синтаксис make'а не позволяет непосредственно ссылаться на префиксы имен файлов. Для большинства типов файлов в операционной системе UNIX это приемлемо, поскольку почти повсеместно для определения типа файлов используется суффикс. Исключение составляют SCCS-файлы. В этом случае s. является префиксом компонента имени файла в полном маршрутном имени.

Чтобы облегчить утилите make обращение с префиксом .s, в качестве признака SCCS-файлов используется символ ~ (тильда). Так, .c~.o обозначает правило, преобразующее исходный SCCS-файл на языке C в объектный файл. Именно, встроенное правило выглядит так:

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

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

Встроенными являются следующие суффиксы SCCS:

.c~ .f~ .y~ .l~ .s~ .sh~ .h~

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

.c~: .f~: .sh~: .c~.a: .c~.c: .c~.o: .f~.a: .f~.f: .f~.o: .s~.a: .s~.s: .s~.o: .y~.c: .y~.o: .l~.l: .l~.o: .h~.h:

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



Имена, связанные со структурой, объединением или перечислением


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

Байты Описание Имя Смысл
0-3 long int x_tagndx Номер элемента для начала структуры
4-5 - - Не используются (заполнены нулями)
6-7 unsigned short x_size Размер структуры, объединения или перечисления
8-17 - - Не используются (заполнены нулями)

Имена, определенные в операторах typedef, могут иметь, но могут и не иметь вспомогательных элементов. Пример:

typedef struct people STUDENT;

struct people { char name [20]; long id; };

typedef struct people EMPLOYEE;

Имя EMPLOYEE будет иметь вспомогательный элемент в таблице имен, а имя STUDENT - нет, поскольку его описание предшествует описанию структуры.



Имена типов


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

имя_типа: спецификатор_типа абстрактный_описатель

абстрактный_описатель: пусто ( абстрактный_описатель )

* абстрактный_описатель абстрактный_описатель ( )

абстрактный_описатель [ константное_выражение ]

Чтобы избежать неоднозначности, в конструкции

( абстрактный_описатель )

абстрактный_описатель должен быть непустым. Благодаря этому ограничению в абстрактном_описателе можно однозначно определить позицию, в которой следовало указать идентификатор, если бы рассматриваемая конструкция являлась обычным описателем. Имя_типа в таком случае совпадает с типом гипотетического идентификатора. Например, конструкции

int int * int * [3] int (*) [3] int * () int (*) () int (* [3]) ()

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



Именованные файлы


Чтобы осуществлять операции ввода/вывода с потоками, отличными от stdin, stdout и stderr, необходимо предварительно открыть их. Это можно проделать с помощью стандартной библиотечной функции fopen(). Получив в качестве аргумента маршрутное имя файла (то есть имя, под которым файл зарегистрирован в файловой системе ОС UNIX), функция fopen() организует поток, ассоциированный с этим файлом, и возвращает указатель на структуру типа FILE. Указатель впоследствии будет передаваться функциям, выполняющим запись и чтение.

Тип FILE определен во включаемом файле <stdio.h>. Чтобы открыть поток, в Вашей программе должна быть соответствущая директива #include и описание вида

FILE *fin;

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

fin = fopen ("filename", "r");

где filename - это маршрутное имя открываемого файла. "r" означает, что файл открывается на чтение. Этот аргумент называется обычно режимом доступа к файлу. Как можно догадаться, имеются режимы чтения, записи, а также одновременно чтения и записи. Так как попытка открыть файл может завершиться неудачей, вызов функции fopen(), как правило, включают в условный оператор. Пример:

if ((fin = fopen ("filename", "r")) == NULL) (void) fprintf (stderr, "%s: Неудача при открытии файла %s\n",argv[0],"filename");

Здесь используется тот факт, что функция fopen() возвращает NULL в случае, если указанный файл не удается открыть.

Если файл удалось открыть, в последующих операциях ввода/вывода для ссылки на этот файл используется указатель fin. Пример:

int c; c = getc (fin);

В этом примере макрос getc() считывает из потока один символ и помещает его в целую переменную c. Хотя из потока считываются символы, переменная c описана как целая, поскольку макрос getc() возвращает целое. Чтение символа часто включается в какую-либо управляющую конструкцию, например так:


while ((c = getc (fin)) != EOF) . . .

Здесь символы будут считываться до тех пор, пока не будет достигнут конец файла. Константы EOF, NULL, а также макрос getc()

определены в <stdio.h>. getc()

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

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




Имя файла


Вспомогательный элемент для имени файла в байтах с 0-го по 13-й содержит 14-символьное имя файла, дополненное нулями.



ИНФОРМАЦИЯ О НАСТРОЙКЕ ССЫЛОК


Таблица настройки содержит по одному элементу для каждой ссылки (среди команд или данных), требующей настройки. Элементы имеют следующий формат:

Байты Описание Имя Смысл
0-3 long int r_vaddr (Виртуальный) адрес ссылки
4-7 long int r_symndx Номер в таблице имен
8-9 unsigned short r_type Тип ссылки

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

Обозначение Значение Смысл
R_ABS 0 Абсолютная ссылка, настройки не требуется. Элемент игнорируется
R_RELWORD 020 16-битный виртуальный адрес имени
R_RELLONG 022 32-битный виртуальный адрес имени



ИНФОРМАЦИЯ О НОМЕРАХ СТРОК


Использование команд cc -g и svs +d приводит к тому, что для каждой строки исходного текста, на которую можно установить точка прерывания, в объектный файл помещается элемент с информацией о строке. Эту информацию используют символьные отладчики, такие как sdb(1) и КРОТ. В пределах секции элементы сгруппированы по функциям, как показано в следующей таблице:

Номер в таблице имен 0
физический адрес номер строки
физический адрес номер строки
. . . . . .
Номер в таблице имен 0
физический адрес номер строки
физический адрес номер строки

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



Информация о зависимостях


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



Информация об определениях/использованиях значений


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

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

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



Инициализация


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

инициализатор: = выражение = { список_инициализаторов }

= { список_инициализаторов , }

список_инициализаторов: выражение список_инициализаторов , список_инициализаторов { список_инициализаторов }

{ список_инициализаторов , }

Все выражения в инициализаторе статической или внешней переменной должны быть константными (см. КОНСТАНТНЫЕ ВЫРАЖЕНИЯ), или выражениями, которые сводятся к адресу уже описанной переменной, возможно сдвинутому на значение константного выражения. Автоматические и регистровые переменные можно инициализировать произвольными выражениями, включающими константы и описанные перед этим переменные и функции.

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

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

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

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


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

Например, конструкция

int x [] = {1, 3, 5};

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

float y [4] [3] = { {1, 3, 5}, {2, 4, 6}, {3, 5, 7}, };

- это инициализация с полным набором скобок; 1, 3 и 5 инициализируют первую строку массива y[0], а именно элементы y[0][0], y[0][1] и y[0][2]. Аналогично инициализируются две следующие строки. Инициализатор преждевременно заканчивается, поэтому y[3] инициализируется нулями. В точности тот же результат можно было бы получить при помощи инициализации

float y [4] [3] = { 1, 3, 5, 2, 4, 6, 3, 5, 7, };

Здесь список_инициализаторов записан без внутренних скобок, поэтому для инициализации первой строки массива будут взяты три первых элемента списка. Аналогично, следующие три берутся для y[1] и затем для y[2]. Запись

float y[4][3] = { {1}, {2}, {3}, {4} };

инициализирует первый столбец y (считающегося двумерным массивом) и заполняет все остальное нулями.

Наконец, запись

char msg [] = "Syntax error on line %s\n";

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




Инициализация переменных


В предыдущем примере переменные pop и n не были инициализированы; тем не менее, программа работала нормально. Это происходит потому, что (по умолчанию) переменные инициализируются пустой цепочкой, числовое значение которой равно 0. Данное соглашение устраняет необходимость большинства инициализаций переменных в секции BEGIN.

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

maxpop < $3 { maxpop = $3 country = $1 } END { print country, maxpop }

Ее результат:

CHINA 866



Инициализация пустот и секций .bss


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

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

Потребовать инициализации можно как для выходной секции в целом, так и для отдельной ее части. Однако в связи с тем, что неинициализированная секция .bss физически не занимает места в выходном дайле, ее нельзя проинициализировать частично. Даже если заказана инициализация только части секции .bss, она будет проинициализирована целиком. Итак, если секция .bss объединяется с секциями .text или .data (разумеется, инициализируемыми), или если инициализируется часть секции .bss, то произойдет одно из двух:

Если опция инициализации задана, она будет отнесена ко всем частям выходной секции, полученным из входных секций .bss и не имеющим явной инициализации.

Если опция инициализации не задана, ld(1) заполнит то же место подразумеваемым заполнителем.

Рассмотрим следующее определение секции:

SECTIONS { sec1: { f1.o . += 0x200; f2.o (.text) } = 0xDFFF sec2: { f1.o (.bss) f2.o (.bss) = 0x1234 } sec3: { f3.o (.bss) . . . } = 0xFFFF sec4: {f4.o (.bss)} }

Здесь "дыра" размером 0x200 байт в секции sec1 заполняется значениями 0xDFFF. В секции sec2 f1.o (.bss) заполняется подразумеваемым значением 0x00, а f2.o (.bss) инициализируется последовательностью 0x1234. Все секции .bss, входящие в sec3, как и пустоты, заполняются значением 0xFFFF. Секция sec4 не инициализируется, иными словами, в объектном файле не будет данных для этой секции.



Инкрементальное редактирование связей


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

Шаг 1:

ld -r -o outfile1 ifile1 infile1.o

/* ifile1 */ SECTIONS { ss1: { f1.o f2.o . . . fn.o } }

Шаг 2:

ld -r -o outfile2 ifile2 infile2.o

/* ifile2 */ SECTIONS { ss2: { g1.o g2.o . . . gn.o } }

Шаг 3:

ld -a -o final.out outfile1 outfile2

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

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

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



ИНСТРУМЕНТАРИЙ ОС UNIX. ГДЕ О НЕМ МОЖНО ПРОЧИТАТЬ


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



ИНТЕРФЕЙС МЕЖДУ ЯЗЫКОМ ПРОГРАММИРОВАНИЯ И ОС UNIX


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

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

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



Исходный текст программ


Исходный текст программ на C или ассемблере не зависит от того, будет ли при редактировании связей использоваться разделяемая библиотека, или же архивная. Поэтому при переходе на разделяемую библиотеку не придется изменять текст уже написанных программ. Новые приложения Вы также можете создавать, не меняя принятых у Вас соглашений по записи программ.

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

Не переопределяйте библиотечные имена.

Хотя возможны исключения, лучше не переопределять стандартные библиотечные подпрограммы, например, printf(3S) или strcmp(3C). Замены, несовместимые с библиотечным вариантом, могут привести к ошибкам при использовании любой библиотеки, разделяемой или архивной.

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

Используйте только те подпрограммы и определения данных, которые описаны в разделе 3 Справочника программиста. Например, не пытайтесь состязаться с разработчиками ctype(3C), манипулируя всем тем, что сопровождает это определение.



Исполняемые команды


Если целевой файл должен быть создан, выполняется последовательность команд. Обычно каждая командная строка распечатывается и затем, после подстановки макросов, для ее выполнения запускается очередной экземпляр shell'а. Печать может быть подавлена в режиме "молчания" (опция -s утилиты make) или в том случае, если командная строка в файле описаний начинается со знака @. make обычно прекращает работу, если какая-либо команда сигнализирует об ошибке, возвращая ненулевой код завершения. Ошибки игнорируются, если в командной строке make'а указана опция -i, или если в файле описаний указано фиктивное целевое имя .IGNORE, или если командная строка в файле описаний начинается со знака минус. Если известно, что программа возвращает бессодержательное значение, полезно указывать минус перед строкой, ее запускающей. Поскольку каждая командная строка передается отдельному экземпляру shell'а, при использовании собственных команд shell'а [например, cd(1)] надо проявлять осторожность, потому что они имеют смысл только в пределах одного shell-процесса. Перед выполнением следующей строки результаты выполнения этих команд утрачиваются.

Перед вызовом любой команды устанавливаются некоторые встроенные макросы. Макрос $@ устанавливается равным полному имени текущего целевого файла. Он вычисляется только для явно указанных зависимостей. Макрос $? устанавливается равным цепочке имен файлов, которые оказались более свежими, чем целевой; он также вычисляется только при обработке явных правил make-файла. Если команда порождена неявным правилом, макрос $< равен имени файла, вызвавшего действие; макрос $* - префикс имени, общий для текущего файла и файла из строки зависимостей. Если файл должен быть получен, но нет явных команд или встроенных правил, используются команды, сопоставленные фиктивному целевому имени .DEFAULT. Если такого имени нет, make выдает сообщение и прекращает работу.

Кроме того, в файле описаний можно использовать следующие связанные с упомянутыми выше макросы: $(@D), $(@F), $(*D), $(*F), $(<D) и $(<F) (см. ниже).



ИСПОЛЬЗОВАНИЕ БАЗЫ ДАННЫХ TERMINFO


База данных terminfo содержит описания многих терминалов, с которыми могут работать как подпрограммы curses, так и некоторые средства системы UNIX, например vi(1). Каждое описание терминала представляет собой скомпилированный файл, содержащий имена, под которыми этот терминал известен, и группу разделенных запятыми полей, описывающих возможные действия и характеристики терминала. В этом разделе описываются база данных terminfo, средства для работы с ней и ее связь с библиотекой curses.



Использование библиотек объектных файлов


Каждый элемент такой библиотеки (например, библиотеки libc.a) является полноценным объектным файлом. Команда ar(1) создает библиотеки из объектных файлов, генерируемых компиляторами. Библиотеки обрабатываются редактором связей избирательно: используются только те элементы, которые разрешают внешние ссылки. Библиотеки могут упоминаться как внутри предложений, определяющих секции, так и вне их. В обоих случаях объектный файл - элемент библиотеки используется для редактирования внешних связей, если выполнены следующие два условия:

Существует неразрешенная ссылка на имя, определенное в этом файле.

Такая ссылка обнаружена ld(1) до завершения просмотра библиотеки.

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

Необходимо запомнить следующее:

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

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

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

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


Рассмотрим следующий пример:

В каждом из входных файлов file1.o и file2.o есть ссылки на внешнюю функцию FCN.

Входной файл file1.o содержит ссылку на внешнее имя ABC.

Входной файл file2.o содержит ссылку на внешнее имя XYZ.

Элемент 0 библиотеки liba.a содержит определение имени XYZ.

Элемент 0 библиотеки libc.a содержит определение имени ABC.

В обеих библиотеках в элементе 1 определяется функция FCN.

Пусть командная строка с вызовом ld(1) выглядит следующим образом:

ld file1.o -la file2.o -lc

Тогда ссылки на FCN разрешаются элементом 1 библиотеки liba.a, ссылка на ABC - элементом 0 библиотеки libc.a, а ссылка на XYZ

остается неразрешенной, так как библиотека liba.a просматривается раньше редактирования связей файла file2.o. Если же команда ld вводится таким образом:

ld file1.o file2.o -la -lc

то ссылки на FCN и ABC разрешаются как в предыдущем примере, а ссылка на XYZ разрешается элементом 0 библиотеки liba.a. Пусть, наконец, команда ld(1) введена так:

ld file1.o file2.o -lc -la

Отличие от предыдущего примера выразится в том, что для разрешения ссылки на FCN будет извлечен элемент 1 библиотеки libc.a, а не liba.a.

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

ld -u rout1 -la

создается неопределенное имя rout1 и, если в каком-либо объектном файле библиотеки liba.a это имя определяется, то этот файл (а с ним, быть может, и некоторые другие) извлекается для редактирования связей. Без опции -u ld(1) не просматривал бы библиотеку вообще ввиду отсутствия неразрешенных ссылок и неопределенных имен.




Использование импортируемых имен


Функции разделяемой библиотеки не могут прямо ссылаться на определяемые вне библиотеки имена, но существует способ обойти это ограничение: в области данных нужно определить указатель на это имя и обеспечить правильную его инициалазацию на этапе выполнения. Внешними (импортируемыми) могут быть как имена функций, так и имена переменных. Более того, импортируемыми могут быть имена, определяемые пользователем, определяемые в другой библиотеке, либо даже определяемые в самой разделяемой библиотеке. На рисунке, приведенном ниже, внешние имена _libc.ptr1 и _libc.ptr2 определяются пользователем, а _libc_malloc определяется внутри разделяемой библиотеки.

Далее мы рассмотрим некоторые вопросы использования внешних имен.

Внешние имена, определяемые вне библиотеки

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

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

Внешние имена, которые пользователь мог бы переопределить

Разделяемые библиотеки могут объявлять свои внутренние имена импортируемыми. С первого взгляда это может показаться излишним, однако рассмотрим следующую проблему. Функции семейства malloc имеются в двух архивах, libc и libmalloc. Хотя команды ОС UNIX, как правило, используют malloc из libc, они могут пользоваться любой библиотекой, или же определять malloc самостоятельно.


Когда мы создавали разделяемую библиотеку языка C...
У нас было три варианта работы с malloc. Во-первых, мы могли не включать malloc(3X) в библиотеку. malloc вызывается другими функциями библиотеки, поэтому она стала бы импортируемой. Недостаток этого варианта заключается в уменьшении экономии памяти.
Во-вторых, мы могли бы включить в библиотеку семейство malloc(3X), не импортируя его. Это дало бы экономию памяти для большинства приложений. Недостаток этого варианта заключается в том, что некоторые приложения переопределяют malloc(3X). Обращения из библиотечных функций, однако, по-прежнему направлялись бы к библиотечной malloc. Кроме того, редактор внешних связей мог бы обнаружить повторное определение malloc(3X), и чтобы избежать этого, пришлось бы либо отказаться от использования разделяемой библиотеки, либо разработчику библиотеки нужно было бы изменить ее исходный текст, устранив оттуда имя malloc(3X).

Наконец, объявив имя malloc(3X) импортируемым, мы могли бы, тем не менее, включить malloc(3X) в разделяемую библиотеку, что мы и сделали. Хотя malloc(3X) и находится в библиотеке, на нее нигде нет прямых ссылок. Если прикладная программа не переопределяет malloc(3X), как пользовательские, так и библиотечные обращения к ней используют библиотечную версию. Соответственно, если malloc переопределяется пользователем, все обращения направляются к его версии.

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

Техника импорта имен

Пусть в разделяемой библиотеке нужно сделать malloc импортируемым. Далее слева приведен исходный текст для архива, а справа - переработанный текст для разделяемой библиотеки.



/* см. ниже pointers.c */

extern char *malloc(); extern char *(*_libc_malloc)();

export () export () { { . . . . . . p=malloc (n); p=(*_libc_malloc) (n); . . . . . . } }

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

Чтобы указать препроцессору C-компилятора, в каком каталоге искать файл с макросами, можно использовать опцию -I команды cpp(1). Далее снова приведены два варианта исходного текста включаемого файла import.h, причем справа записан вариант для разделяемой библиотеки:

/* пустой файл */ /* Макросы для импортируемых имен: по одному на имя. */ . . . #define malloc (*_libc_malloc) . . .

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

Общий исходный текст:

#include "import.h"

extern char *malloc ();

export() { . . . p=malloc (n); . . . }

В случае использования разделяемого варианта import.h описание malloc() в действительности есть описание указателя _libc_malloc.

Другой вариант: поместить #include внутрь #ifdef:

#ifdef SHLIB # include "import.h" #endif

extern char *malloc ();

export() { . . . p=malloc (n); . . . }

сборки, она модифицирует файл pmalloc.o, добавив туда перемещаемые команды, соответствующие оператору присваивания:

_libc_malloc = &malloc;

Эти команды редактор внешних связей извлечет из разделяемой библиотеки сборки вместе с файлом pmalloc.o и поместит в выполняемый файл. Затем редактор разрешит внешние ссылки и соберет все фрагменты инициализации вместе. При запуске выполняемого файла стартовая процедура crt1 выполнит эти фрагменты, установив таким образом правильные значения библиотечных указателей.



Избирательное использование импортируемых имен

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

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

Рассмотрим несколько примеров. В первом приближении можно определить все указатели в одном исходном файле pointers.c:

. . . int (*_libc_ptr1)() = 0; char *(*_libc_malloc)() = 0; int (*_libc_ptr2)() = 0; . . .

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

Файл Содержимое
ptr1.c   int (*_libc_ptr1)() = 0; 
 pmalloc.c   char *(*_libc_malloc)() = 0; 
 ptr2.c   int (*_libc_ptr2)() = 0; 
Ранее все указатели определялись в единственном объектном файле, pointers.o. Его извлечение из библиотеки в ходе редактирования внешних связей потребовало бы определения ptr1, malloc и ptr2. Разделение этого файла на три позволяет использовать каждый указатель по отдельности, избегая таким образом возникновения неразрешенных внешних ссылок на ненужные функции.




Использование msgctl


В статье msgct(2) Справочника программиста синтаксис данного системного вызова описан так:

#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h>

int msgctl (msqid, cmd, buf) int msqid, cmd; struct msqid_ds *buf;

При успешном завершении результат равен нулю; в случае неудачи возвращается -1.

В качестве аргумента msqid должен выступать идентификатор очереди сообщений, предварительно полученный при помощи системного вызова msgget(2).

Управляющее действие определяется значением аргумента cmd. Допустимых значений три:

IPC_STAT 
  Поместить информацию о состоянии очереди, содержащуюся в структуре данных, ассоциированной с идентификатором msqid, в пользовательскую структуру, на которую указывает аргумент buf.
    IPC_SET 
  В структуре данных, ассоциированной с идентификатором msqid, переустановить значения действующих идентификаторов пользователя и группы, прав на операции, максимально допустимого числа байт в очереди.
    IPC_RMID 
  Удалить из системы идентификатор msqid, ликвидировать очередь сообщений и ассоциированную с ней структуру данных.

Чтобы выполнить управляющее действие IPC_SET или IPC_RMID, про- цесс должен иметь действующий идентификатор пользователя, рав- ный либо идентификаторам создателя или владельца очереди, либо идентификатору суперпользователя. Чтобы выполнить действие IPC_STAT, требуется право на чтение.



Использование msgget


В статье msgget(2) Справочника программиста синтаксис данного системного вызова описан так:

#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h>

int msgget (key, msgflg) key_t key; int msgflg;

Тип key_t описан во включаемом файле <sys/types.h> при помощи typedef как целый тип.

Целочисленное значение, возвращаемое в случае успешного завершения системного вызова, есть идентификатор очереди сообщений (msqid). В случае неудачи результат равен -1.

Новый идентификатор msqid, очередь сообщений и ассоциированная с ней структура данных выделяются в каждом из двух случаев:

Значение ключа key равно IPC_PRIVATE.

Ключ key еще не имеет ассоциированного с ним идентификатора очереди сообщений и выражение (msgflg & IPC_CREAT) истинно.

Целое число, передаваемое в качестве аргумента msgflg, удобно рассматривать как восьмеричное. Оно задает

Права на выполнение операций. Флаги.

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

Права на операции   Восьмеричное значение 
 Чтение для владельца  0400
 Запись для владельца  0200
 Чтение для группы  0040
 Запись для группы  0020
 Чтение для остальных  0004
 Запись для остальных  0002

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

Флаги определены во включаемом файле <sys/ipc.h>. В следующей таблице сведены мнемонические имена флагов и соответствующие им восьмеричные значения:

 Флаг   Восьмеричное значение 
 IPC_CREAT   0001000 
 IPC_EXCL   0002000 
<
Значение аргумента msgflg в целом является, следовательно, результатом побитного ИЛИ (операция | в языке C) прав на выполнение операций и флагов, например:

msqid = msgget (key, (IPC_CREAT | 0644)); msqid = msgget (key, (IPC_CREAT | IPC_EXCL | 0400));

Как уже указывалось, системный вызов вида

msqid = msgget (IPC_PRIVATE, msgflg);

приведет к попытке выделения нового идентификатора очереди сообщений и ассоциированной информации независимо от значения аргумента msgflg. Попытка может быть неудачной только из-за превышения системного лимита на общее число очередей сообщений, задаваемого настраиваемым параметром MSGMNI [см. master(4)].

При использовании флага IPC_EXCL в сочетании с IPC_CREAT системный вызов msgget(2) завершается неудачей в том и только в том случае, когда с указанным ключом key уже ассоциирован идентификатор. Флаг IPC_EXCL необходим, чтобы предотвратить ситуацию, когда процесс полагает, что получил новый (уникальный) идентификатор очереди сообщений, в то время как это не так. Иными словами, когда используются и IPC_CREAT и IPC_EXCL, при успешном завершении системного вызова обязательно возвращается новый идентификатор msqid.

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




Использование очередей сообщений


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

Говоря об очереди сообщений следует иметь в виду, что реально в ней хранятся не сами сообщения, а их описатели, имеющие следующую структуру:

struct msg { struct msg *msg_next; /* Указатель на следующее сообщение */ long msg_type; /* Тип сообщения */ short msg_ts; /* Размер текста сообщения */ short msg_spot; /* Адрес текста сообщения */ };

Приведенное определение находится во включаемом файле <sys/msg.h>.

С каждым уникальным идентификатором очереди сообщений ассоциирована одна структура данных, которая содержит следующую информацию:

struct msqid_ds { struct ipc_perm msg_perm; /* Структура прав на выполнение операций */ struct msg *msg_first; /* Указатель на первое сообщение в очереди */ struct msg *msg_last; /* Указатель на последнее сообщение в очереди */ ushort msg_cbytes; /* Текущее число байт в очереди */ ushort msg_qnum; /* Число сообщений в очереди */ ushort msg_qbytes; /* Макс. допустимое число байт в очереди */ ushort msg_lspid; /* Ид-р последнего отправителя */ ushort msg_lrpid; /* Ид-р последнего получателя */ time_t msg_stime; /* Время последнего отправления */ time_t msg_rtime; /* Время последнего получения */ time_t msg_ctime; /* Время последнего изменения */ };

Это определение также находится во включаемом файле <sys/msg.h>. Поле msg_perm данной структуры использует в качестве шаблона структуру ipc_perm, которая задает права на операции с сообщениями и определяется так:

struct ipc_perm { ushort uid; /* Идентификатор пользователя */ ushort gid; /* Идентификатор группы */ ushort cuid; /* Идентификатор создателя очереди */ ushort cgid; /* Ид-р группы создателя очереди */ ushort mode; /* Права на чтение/запись */ ushort seq; /* Последовательность номеров используемых слотов */ key_t key; /* Ключ */ };


Последнее определение находится во включаемом файле <sys/ipc.h>, общем для всех средств межпроцессной связи.

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

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

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

Кроме того, можно специфицировать ключ key со значением IPC_P-RIVATE (0). Если указан такой "личный" ключ, для него обязательно выделяется новый уникальный идентификатор и создаются ассоциированные с ним очередь сообщений и структура данных (при условии, что это не приведет к превышению системного лимита). При выполнении утилиты ipcs поле KEY для подобного идентификатора msqid из соображений секретности содержит нули.

Если идентификатор msqid со специфицированным значением ключа key уже существует, выполняется второе действие, то есть возвращается ассоциированный идентификатор. Если Вы желаете трактовать возвращение существующего идентификатора как ошибку, в передаваемом системному вызову аргументе msgflg нужно установить флаг IPC_EXCL.

Более детально использование данного системного вызова обсуждается в пункте Использование msgget.

При выполнении первого действия процесс, вызвавший msgget(2), становится владельцем/создателем очереди сообщений; соответственно этому инициализируется ассоциированная структура данных. Напомним, что владелец очереди может быть изменен, однако процесс-создатель всегда остается создателем; см. пункт Управление очередями сообщений. При создании очереди сообщений определяются также начальные права на выполнение операций над ней.



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

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

Для управления очередями сообщений используется системный вызов msgct(2). Он позволяет выполнять следующие управляющие действия:

Опросить содержимое структуры данных, ассоциированной с идентификатором очереди сообщений msqid.

Изменить права на выполнение операций над очередью сообщений.

Изменить максимально допустимое число байт (msg_qbytes) в очереди сообщений, определяемой идентификатором msqid.

Удалить из системы идентификатор msqid, ликвидировать очередь сообщений и ассоциированную с ней структуру данных.

Более детально системный вызов msgct(2) описан в пункте Управление очередями сообщений.




Использование операций


В статье msgop(2) Справочника программиста синтаксис упомянутых системных вызовов описан так:

#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h>

int msgsnd (msqid, msgp, msgsz, msgflg) int msqid; struct msgbuf *msgp; int msgsz, msgflg;

int msgrcv (msqid, msgp, msgsz, msgtyp, msgflg) int msqid; struct msgbuf *msgp; long msgtyp; int msgsz, msgflg;

2.4.1.1. Посылка сообщений

При успешном завершении системного вызова msgsnd() результат равен нулю; в случае неудачи возвращается -1.

В качестве аргумента msqid должен выступать идентификатор очереди сообщений, предварительно полученный при помощи системного вызова msgget(2).

Аргумент msgp является указателем на структуру в области памяти пользователя, содержащую тип посылаемого сообщения и его текст.

Аргумент msgsz специфицирует длину массива символов в структуре данных, указываемой аргументом msgp, то есть длину сообщения. Максимально допустимый размер данного массива определяется системным параметром MSGMAX.

Отметим, что значение поля msg_qbytes ассоциированной структуры данных может быть уменьшено с предполагаемой по умолчанию величины MSGMNB при помощи управляющего действия IPC_SET системного вызова msgct(2), однако впоследствии увеличить его может толь- ко суперпользователь.

Аргумент msgflg позволяет специфицировать выполнение над сообщением "операции с блокировкой"; для этого флаг IPC_NOWAIT должен быть сброшен (msgflg & IPC_NOWAIT = 0). Блокировка имеет место, если либо текущее число байт в очереди уже равно максимально допустимому значению для указанной очереди (то есть значению поля msg_qbytes или MSGMNB), либо общее число сообщений во всех очередях равно максимально допустимому системой (системный параметр MSGTQL). Если в такой ситуации флаг IPC_NOWAIT

установлен, системный вызов msgsnd() завершается неудачей и возвращает -1.

2.4.1.2. Прием сообщений

При успешном завершении системного вызова msgrcv() результат равен числу принятых байт; в случае неудачи возвращается -1.


В качестве аргумента msqid должен выступать идентификатор очереди сообщений, предварительно полученный при помощи системного вызова msgget(2).

Аргумент msgp является указателем на структуру в области памяти пользователя, содержащую тип принимаемого сообщения и его текст.

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

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

Аргумент msgflg позволяет специфицировать выполнение над сообщением "операции с блокировкой"; для этого должен быть сброшен флаг IPC_NOWAIT (msgflg & IPC_NOWAIT = 0). Блокировка имеет место, если в очереди сообщений нет сообщения с запрашиваемым типом (msgtyp). Если флаг IPC_NOWAIT установлен и в очереди нет сообщения требуемого типа, системный вызов немедленно заверша- ется неудачей. Аргумент msgflg может также специфицировать, что системный вызов должен заканчиваться неудачей, если размер со- общения в очереди больше значения msgsz; для этого в данном ар- гументе должен быть сброшен флаг MSG_NOERROR (msgflg & MSG_NOERROR = 0). Если флаг MSG_NOERROR установлен, сообщение обрезается до длины, указанной аргументом msgsz.




ИСПОЛЬЗОВАНИЕ ПОДПРОГРАММ ПАКЕТА CURSES


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

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

Управляют вводом и выводом данных - например, подсвечивают выводимые данные или подавляют отображение вводимых символов на экране ("эхо").

Работают с несколькими образами экрана (окнами).

Выполняют простые графические функции.

Управляют программируемыми метками на экране терминала.

Осуществляют ввод/вывод, работая одновременно с несколькими терминалами.

По мере описания подпрограмм мы приводим простые программы, иллюстрирующие их применение. Кроме того, мы ссылаемся на группу более значительных примеров, находящихся в разделе Примеры программ, работающих с curses. Эти последние программы более глубоки и зачастую используют подпрограммы, которые здесь не обсуждаются, поэтому держите под рукой справочник curses(3X).



ИСПОЛЬЗОВАНИЕ ПОДПРОГРАММ ПАКЕТА TERMINFO


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

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

Примечание

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



Использование программируемых меток


Большинство терминалов поддерживает программируемые метки в нижней части своего экрана. На клавиатуре этим меткам обычно соответствует функциональные клавиши. Меток обычно восемь, каждая длиной в восемь знаков и высотой в одну-две строки.

Библиотека curses содержит подпрограммы, поддерживающие единую модель из восьми программируемых меток. Если терминал их не имеет, нижняя строка его экрана превращается в область программируемых меток. Для использования в curses-программах таких меток, нет необходимости иметь на клавиатуре соответствующие им функциональные клавиши.

Далее кратко описываются большинство подпрограмм curses, применяющихся при работе с программируемыми метками: slk_init(), slk_set(), slk_refresh(), slk_noutrefresh(), slk_clear() и slk_restore().

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

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

slk_init() имеет один аргумент, определяющий способ размещения меток на экране в случае использования для этого строки из stdscr. Выбор возможен между размещением группами 3-2-3, как это принято на терминалах AT&T, либо 4-4, как на терминалах Хьюлетт-Паккард. Подпрограммы curses определяют положение и размер меток в соответствии с указанным способом, причем максимально возможная длина метки - восемь символов.

Подпрограмма slk_set() имеет три аргумента: номер метки (1-8), текст, помещаемый в метку (не более восьми символов), выравнивание этого текста в поле метки (0 - выравнивание влево, 1 - центрирование, 2 - выравнивание вправо).


Подпрограмма slk_noutrefresh() подобна wnoutrefresh() тем, что она обновляет содержимое внутреннего образа экрана, но не выводит данные на реальный экран. Поскольку после ее вызова обычно вызывают wrefresh(), именно slk_noutrefresh(), как правило, используется для вывода меток на экран терминала.

Подобно тому, как wrefresh() эквивалентна последовательным вызовам wnoutrefresh() и doupdate(), подпрограмма slk_refresh()

эквивалентна последовательным вызовам slk_noutrefresh() и doupdate().

Чтобы программируемые метки не мешали работе в среде shell, перед обращением к endwin() можно вызвать slk_clear(), которая удаляет программируемые метки с экрана и вызывает doupdate(). Программируемые метки можно восстановить на экране функцией slk_restore(). Подробнее о подпрограммах для работы с программируемыми метками см. curses(3X).




ИСПОЛЬЗОВАНИЕ РАЗДЕЛЯЕМЫХ БИБЛИОТЕК


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



Использование разделяемых сегментов памяти


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

struct shmid_ds { struct ipc_perm shm_perm; /* Структура прав на выполнение операций */ int shm_segsz; /* Размер сегмента */ struct region *shm_reg; /* Указатель на структуру области памяти */ char pad [4]; /* Информация для подкачки */

После того, как создан уникальный идентификатор разделяемого сегмента памяти и ассоциированная с ним структура данных, можно использовать системные вызовы семейства shmop(2) (операции над разделяемыми сегментами) и shmctl(2) (управление разделяемыми сегментами).



Использование семафоров


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

С точки зрения реализации множество семафоров представляет собой массив структур. Каждая структура соответствует семафору и определяется следующим образом:

struct sem { ushort semval; /* Значение семафора */ short sempid; /* Идентификатор процесса, выпол- нявшего последнюю операцию */ ushort semncnt; /* Число процессов, ожидающих увеличения значения семафора */ ushort semzcnt; /* Число процессов, ожидающих обнуления значения семафора */ };

Определение находится во включаемом файле <sys/sem.h>.

С каждым идентификатором множества семафоров ассоциирована структура данных, содержащая следующую информацию:

struct semid_ds { struct ipc_perm sem_perm; /* Структура прав на выполнение операций */ struct sem *sem_base; /* Указатель на первый семафор в множестве */ ushort sem_nsems; /* Количество семафоров в множестве */ time_t sem_otime; /* Время последней операции */ time_t sem_ctime; /* Время последнего изменения */ };

Это определение также находится во включаемом файле <sys/sem.h>. Отметим, что поле sem_perm

данной структуры использует в качестве шаблона структуру ipc_perm, общую для всех средств межпроцессной связи (см. выше раздел ОЧЕРЕДИ СООБЩЕНИЙ).

Системный вызов semget(2) аналогичен вызову msgget(2) (разумеется, с заменой слов "очередь сообщений" на "множество семафоров"). Он также предназначен для получения нового или опроса существующего идентификатора, а нужное действие определяется значением аргумента key. В аналогичных ситуациях semget(2) терпит неудачу. Единственное отличие состоит в том, что при создании требуется посредством аргумента nsems указывать число семафоров в множестве.

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



Использование semctl


В статье semctl(2) Справочника программиста синтаксис данного системного вызова описан так:

#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>

int semctl (semid, semnum, cmd, arg) int semid, cmd; int semnum; union semun { int val; struct semid_ds *buf; ushort *array; } arg;

Результат системного вызова semctl(2) в случае успешного завершения зависит от выполняемого управляющего действия. Как правило он равен 0, но четыре действия (GETVAL, GETPID, GETNCNT и GETZCNT) являются исключениями. При возникновении ошибки всегда возвращается -1.

Аргументы semid и semnum определяют множество или отдельный семафор, над которым выполняется управляющее действие. В качестве аргумента semid должен выступать идентификатор множества семафоров, предварительно полученный при помощи системного вызова semget(2). Аргумент semnum задает номер семафора в множестве. Семафоры нумеруются с нуля.

Назначение аргумента arg зависит от управляющего действия, которое определяется значением аргумента cmd. Допустимы следующие действия:

GETVAL 
  Получить значение семафора и выдать его в качестве результата.
    SETVAL 
  Установить значение семафора равным arg.val.
    GETPID 
  Получить идентификатор процесса, последним выполнявшего операцию над семафором, и выдать его в качестве результата.
    GETNCNT 
  Получить число процессов, ожидающих увеличения значения семафора, и выдать его в качестве результата.
    GETZCNT 
  Получить число процессов, ожидающих обнуления значения семафора, и выдать его в качестве результата.
    GETALL 
  Прочитать значения всех семафоров множества и поместить их в массив, на который указывает arg.array.
    SETALL 
  Установить значения всех семафоров множества равными значениям элементов массива, на который указывает arg.array.
    IPC_STAT 
  Поместить информацию о состоянии множества семафоров, содержащуюся в структуре данных, ассоциированной с идентификатором semid, в пользовательскую структуру, на которую указывает arg.buf.
    IPC_SET 
  В структуре данных, ассоциированной с идентификатором semid, переустановить значения действующих идентификаторов пользователя и группы, а также прав на операции. Нужные значения извлекаются из структуры данных, на которую указывает arg.buf.
    IPC_RMID 
  Удалить из системы идентификатор semid, ликвидировать множество семафоров и ассоциированную с ним структуру данных.

Чтобы выполнить управляющее действие IPC_SET или IPC_RMID, процесс должен иметь действующий идентификатор пользователя, равный либо идентификаторам создателя или владельца очереди, либо идентификатору суперпользователя. Для выполнения управляющих действий SETVAL и SETALL требуется право на изменение, а для выполнения остальных действий - право на чтение.



Использование semop


В статье semop(2) Справочника программиста синтаксис данного системного вызова описан так:

#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>

int semop (semid, sops, nsops) int semid; struct sembuf *sops; unsigned nsops;

При успешном завершении результат системного вызова равен нулю; в случае неудачи возвращается -1.

В качестве аргумента semid должен выступать идентификатор множества семафоров, предварительно полученный при помощи системного вызова semget(2).

Аргумент sops (массив структур) определяет, над какими семафорами будут выполняться операции и какие именно. Структура, описывающая операцию над одним семафором, определяется следующим образом:

struct sembuf { short sem_num; /* Номер семафора */ short sem_op; /* Операция над семафором */ short sem_flg; /* Флаги операции */ };

(см. включаемый файл <sys/sem.h>).

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

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

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

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

Нулевое значение поля sem_op предписывает сравнить значение семафора с нулем. Операция не может быть успешно выполнена, если значение семафора отлично от нуля.

Допустимые значения флагов операций (поле sem_flg):

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

Аргумент nsops специфицирует число структур в массиве. Максимально допустимый размер массива определяется системным параметром SEMOPM, то есть в каждом системном вызове semop(2) можно выполнить не более SEMOPM операций.



Использование shmctl


В статье shmctl(2) Справочника программиста синтаксис данного системного вызова описан так:

#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h>

int shmctl (shmid, cmd, buf) int shmid, cmd; struct shmid_ds *buf;

При успешном завершении результат равен нулю; в случае неудачи возвращается -1.

В качестве аргумента shmid должен выступать идентификатор разделяемого сегмента памяти, предварительно полученный при помощи системного вызова shmget(2).

Управляющее действие определяется значением аргумента cmd. Допустимы следующие значения:

IPC_STAT 
  Поместить информацию о состоянии разделяемого сегмента, содержащуюся в структуре данных, ассоциированной с идентификатором shmid, в пользовательскую структуру, на которую указывает аргумент buf.
    IPC_SET 
  В структуре данных, ассоциированной с идентификатором shmid, переустановить значения действующих идентификаторов пользователя и группы, а также прав на операции. Нужные значения извлекаются из структуры данных, на которую указывает аргумент buf.
    IPC_RMID 
  Удалить из системы идентификатор shmid, ликвидировать разделяемый сегмент памяти и ассоциированную с ним структуру данных.
    SHM_LOCK 
  Удерживать в памяти разделяемый сегмент, заданный идентификатором shmid.
    SHM_UNLOCK 
  Освободить (перестать удерживать в памяти) разделяемый сегмент, заданный идентификатором shmid.

Чтобы выполнить управляющее действие IPC_SET или IPC_RMID, про- цесс должен иметь действующий идентификатор пользователя, равный либо идентификаторам создателя или владельца очереди, либо идентификатору суперпользователя. Управляющие действия SHM_LOCK

и SHM_UNLOCK может выполнить только суперпользователь. Для выполнения управляющего действия IPC_STAT процессу требуется право на чтение.



Использование системы SCCS программистами-одиночками


SCCS (Source Code Control System) - это набор утилит, предназначенных для управления версиями исходных текстов программ. Если версии программы отслеживаются с помощью SCCS, то в любой момент времени возможно редактирование только одной версии. Когда измененный текст программы возвращается SCCS, записываются только сделанные изменения. Каждая версия имеет свой с_идентификатор. Указывая с_идентификатор, можно выделить из SCCS-файла более раннюю версию текста программы. Если отредактировать и возвратить SCCS для сохранения такую раннюю версию, в дереве версий будет начата новая ветвь. В состав SCCS входят утилиты admin(1), cdc(1), comb(1), delta(1), get(1), prs(1), rmdel(1), sccsdiff(1), val(1), what(1). Все они описаны в Справочнике пользователя.

Как правило, считается, что SCCS применяется для управления большими программными проектами. Тем не менее любой пользователь вполне может организовать SCCS-систему для личных нужд.



Использование значений произвольных типов


По умолчанию предполагается, что значения, возвращаемые действиями и лексическим анализатором - целые. yacc поддерживает значения и других типов, в том числе структуры. Кроме того, yacc отслеживает типы и вставляет соответствующие имена элементов объединений, в результате чего получается строго типизированная процедура разбора. При описании стека значений yacc'у указывается объединение (union) всех ожидаемых типов значений. Пользователь задает это объединение и связывает имена элементов объединения с каждой лексемой и нетерминальным символом, у которых может быть значение. Когда на значение ссылаются при помощи конструкций $$ или $n, yacc автоматически вставляет имя соответствующего элемента объединения, так что лишних преобразований не происходит, и, кроме того, утилиты проверки типов, такие как lint(1), ведут себя лояльнее.

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

Чтобы описать объединение, пользователь включает конструкцию

%union { тело объединения

}

в секцию определений. Она означает, что элементы стека значений и внешние переменные yylval и yyval имеют тип, совпадающий с этим объединением. Если yacc вызывается с опцией -d, описание объединения копируется в файл y.tab.h в качестве YYSTYPE.

После того, как тип YYSTYPE определен, надо сопоставить имена элементов объединения именам лексем и нетерминальных символов. Для указания имени элемента объединения используется конструкция

<имя>

Если эта конструкция следует за одним из ключевых слов %token, %right, %left или %nonassoc, то имя элемента объединения сопоставляется лексемам из последующего списка. Так, запись


%left <optype> '+' '-'

означает, что любое обращение к значениям, возвращаемым этими двумя лексемами, снабжается тегом - именем элемента объединения optype. Еще одно ключевое слово %type используется для сопоставления имени элемента объединения нетерминальному символу. Чтобы сопоставить элемент объединения nodetype нетерминальным символам expr и stat, можно записать

%type <nodetype> expr stat

Есть несколько случаев, в которых описанных механизмов недостаточно. Если действие находится внутри правила, возвращаемое этим действием значение не имеет типа, заданного a priori. Аналогично, yacc'у не хватает информации для определения типа при ссылках на значения из левого контекста (таких как $0). В подобных ситуациях тип может быть задан явно ссылкой на имя элемента объединения, которое заключается в угловые скобки < и > и вставляется за первым знаком $. Пример

rule : aaa { $<intval>$ = 3; } bbb { fun ($<intval>2, $<other>0); } ;

иллюстрирует данную возможность. Синтаксис не назовешь "сахарным", но и требуется такая конструкция нечасто.

Средства, описанные в данном разделе, проиллюстрированы в более сложном из приводимых ниже примеров. Отметим, что контроль типов включается только при явном использовании типизации (например, если встречается конструкция %type) и контроль этот является весьма жестким. Так, считается ошибкой вхождение $n или $$

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




Использовать ли разделяемую библиотеку?


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

Если есть как разделяемая, так и неразделяемая библиотеки, Вы можете для сравнения создать две версии Вашей программы, с разделяемой библиотекой и без нее. Напомним, что это возможно, так как для обоих видов библиотек годится один и тот же исходный текст (см. выше раздел Исходный текст программ). Сделайте две версии выполняемого файла и сравните их с точки зрения размера и эффективности. Пример:

$ cat hello.c main () { printf ("hello\n"); } $ cc -o unshared hello.c -lc $ cc -o shared hello.c $ size unshared shared unshared: 10384 + 1292 + 2336 = 14012 shared: 472 + 944 + 2232 = 3648

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

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



Явные преобразования типов


Возможность явного преобразования типов в языке C предназначена в значительной степени для того, чтобы повысить мобильность программ. Рассмотрим присваивание

p = 1;

в котором p - это указатель на char. lint, обнаружив такую конструкцию, будет выдавать диагностическое сообщение. В присваивании

p = (char *) 1;

использована операция явного преобразования целого значения в указатель на символ. У программиста наверняка были веские причины написать именно такую конструкцию и явно об этом сигнализировать. Тем не менее, если указана опция -p, lint, как и прежде, будет выдавать сообщение о несоответствии типов.



Явные преобразования указателей


Некоторые преобразования, затрагивающие указатели, допустимы, но имеют особенности, зависящие от реализации. Имеются в виду операции явного преобразования типа (см. пункт Унарные операции в разделе ВЫРАЖЕНИЯ И ОПЕРАЦИИ и пункт Имена типов в разделе ОПИСАНИЯ).

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

Объект целочисленного типа может быть явно преобразован в указатель. Отображение всегда переводит целое, полученное преобразованием из указателя, в тот же указатель; в остальном оно машинно-зависимо.

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

Например, процедура выделения памяти получает размер (в байтах) объекта, который требуется разместить, и возвращает результат типа указатель на char; ее можно использовать так:

extern char *alloc (); double *dp; dp = (double *) alloc (sizeof (double)); *dp = 22.0 / 7.0;

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



Язык C


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

Набор типов данных: символьный, целый, длинный целый, вещественный, и вещественный двойной точности. Наличие низкоуровневых возможностей (напомним, что большая часть ядра ОС UNIX написана на C). Возможность определения производных типов данных, таких как массивы, функции, указатели, структуры и объединения. Наличие многомерных массивов. Возможность определения указателей на данные конкретного типа и выполнения арифметических действий над указателями с учетом типа адресуемых ими данных. Побитные операции. Множество управляющих конструкций: if, if-else, switch, while, do-while и for. Высокая степень мобильности программ.

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

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



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


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

Генерирует отчеты.

Сопоставляет шаблоны.

Оценивает корректность данных.

Фильтрует передаваемые данные.

В первой части главы в общих чертах излагается синтаксис awk'а. Затем, начиная с раздела ПРИМЕНЕНИЕ AWK'А, приводится несколько примеров, демонстрирующих использование предоставляемых языком возможностей.



Элементы таблицы имен


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

Байты Описание Имя Смысл
0-7 см. текст ниже _n Эти восемь байт содержат либо текст имени, либо его смещение от начала таблицы цепочек
8-11 long int n_value Значение имени, зависящее от класса памяти
12-13 short n_scnum Номер секции, содержащей имя
14-15 unsigned short n_type Базовый и производные типы имени
16 char n_sclass Класс памяти
17 char n_numaux Число вспомогательных элементов



Как аргументы передаются в программу


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

Обычно параметры функции main() имеют имена argc и argv, хотя это и не обязательно. argc - целое число, равное количеству передаваемых аргументов. Это число всегда больше или равно 1, поскольку сама команда считается первым аргументом, и argv[0]

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

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

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

Переменное имя файла.

Приведем примеры фрагментов программ, иллюстрирующих эти два способа использования аргументов программы.

#include <stdio.h> #define FALSE 0 #define TRUE 1

main (argc, argv) int argc; char *argv []; { void exit (); int oflag = FALSE; int pflag = FALSE; /* Функциональные флаги */ int rflag = FALSE; int ch;

while ((ch = getopt (argc, argv, "opr")) != EOF) { /* Если имеются опции, присвоить флагам TRUE. Если опций нет, вывести сообщение об ошибке */ switch (ch) { case 'o': oflag = TRUE; break; case 'p': pflag = TRUE; break; case 'r': rflag = TRUE; break; default: (void) fprintf (stderr, "Использование: %s [-opr]\n", argv [0]); exit (2); } } . . . }


#include <stdio.h>

main (argc, argv) int argc; char *argv []; { FILE *fopen (), *fin; void perror (), exit ();

if (argc > 1) { if ((fin = fopen (argv [1], "r")) == NULL) { /* Первое %s - для вывода имени программы. Второе %s - для вывода имени файла, который не удалось открыть */ (void) fprintf (stderr, "%s: неудача при попытке открыть файл %s: ", argv [0], argv [1]); perror (""); exit (2); } } . . . }

При разборе shell'ом командной строки аргументами считаются любые последовательности непробельных символов, разделенные пробелами или знаками табуляции. Последовательность символов, заключенных в двойные кавычки (например, "abc def"), считается одним аргументом, даже если в ней встречаются пробелы или табуляции. Разумеется, проверка корректности передаваемых аргументов полностью возлагается на программиста.

Кроме argc и argv, у функции main() есть еще один параметр, обычно обозначаемый как envp. Это массив указателей на цепочки символов, образующих окружение. Более подробная информация о envp имеется в Справочнике программиста в статьях exec(2) и environ(5).




Как ОС UNIX работает с разделяемыми библиотеками?


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

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

Разделяемая библиотека сборки - это фактически то же самое, что и архивная библиотека. Каждый из ее элементов (обычно это законченный объектный файл) содержит имена некоторых функций и данных в своей таблице имен. Этот файл просматривается редактором внешних связей, когда разделяемая библиотека сборки указывается при компиляции или редактировании связей программы. Просмотр производится с целью разрешения внешних ссылок. Однако, как уже отмечалось, редактор связей не копирует элементы разделяемой библиотеки, разрешающие внешние ссылки, в объектный файл программы. Вместо этого редактор, найдя эти определения, помещает в объектный файл информацию, идентифицирующию их местонахождение в библиотеке. В результате создается специальная секция выполняемого файла, которая обозначается .lib. См. также раздел Что такое разделяемая библиотека? выше.

Разделяемая библиотека выполнения похожа на файл a.out(4). ОС

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


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

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

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

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

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



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

На следующем рисунке показано, как два выполняемых файла обращаются к printf(3S). Процесс слева был создан с архивной библиотекой, поэтому в него включена копия функции printf(3S). Процесс справа создавался с разделяемой библиотекой. Этот файл ссылается на абсолютный адрес (10) в таблице переходов разделяемой библиотеки выполнения. По этому адресу находится команда безусловного перехода на нужный адрес в библиотеке.






Как применение разделяемых библиотек может привести к увеличению расхода памяти?


Использование разделяемой библиотеки сборки при редактировании связей может привести к увеличению размера выполняемого файла. Напомним, что в ОС UNIX V версии 3.1 редактирование связей является статическим, что означает необходимость разрешения всех внешних ссылок программы к моменту ее выполнения. Напомним также, что разделяемая библиотека может обращаться к внешним (импортируемым) именам, которые в ней не определяются. Эти имена порождают при редактировании связей внешние ссылки, для разрешения которых редактор связей включает в выполняемый файл соответствующие секции .text и .data, что увеличивает размер файла.

Разделяемая библиотека выполнения может увеличить размер процесса. Напомним, что в разделе Как ОС UNIX работает с разделяемыми библиотеками? указывалось, что к процессу присоединяются секции команд и данных разделяемой библиотеки выполнения. Секция команд разделяется между всеми процессами, которым она нужна, однако это не касается секции данных. Каждый процесс, использующий библиотеку, получает свою собственную копию всей секции данных библиотеки. Это, разумеется, увеличивает объем памяти, нужной процессу. В результате, если процессу в действительности нужна лишь малая часть функций и данных разделяемой библиотеки, он может занять больше памяти, чем если бы он создавался с архивной библиотекой. Так, неразумно было бы использовать разделяемую библиотеку языка C, если Вам нужна только функция strcmp(3S). Хотя разделяемое использование самой strcmp(3S) и экономит память, в этом случае перевешивают потери, связанные с необходимостью копирования в процесс всей секции данных разделяемой библиотеки языка C, поэтому лучше использовать архивную версию библиотеки.

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



Как разделяемые библиотеки экономят память?


Чтобы лучше понять это, сравним разделяемую и архивную библиотеки.

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

Примечание

Редактор внешних связей ОС UNIX является средством статического редактирования. Это означает, что все внешние ссылки программы должны быть разрешены к моменту начала ее выполнения. Как с архивной, так и с разделяемой библиотеками редактирование внешних связей является статическим.

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

Рассмотрим работу ОС UNIX с библиотекой каждого из типов в процессе редактирования связей. Работая с архивной библиотекой, редактор связей копирует ее модули, в которых определяются внешние имена редактируемой программы, в секции .text и .data

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


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

main () { . . . printf ("Как Вам нравится эта глава?\n"); . . . result = strcmp ("Весьма!", answer); . . . }



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

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






Как системные вызовы и библиотечные функции используются в C-программах


Как правильно пользоваться системными вызовами и библиотечными функциями, объясняется в соответствующих статьях Справочника программиста. Чтобы чтение Справочника было наиболее эффективным, необходимо знать типичную структуру его статей. Рассмотрим, например, статью gets(3S).

GETS(3S) GETS(3S)
НАЗВАНИЕ
gets, fgets - чтение цепочки символов из потока
СИНТАКСИС
  #include <stdio.h>

char *gets (s) char *s;

char *fgets (s, n, stream) char *s; int n; FILE *stream;

ОПИСАНИЕ
 

Функция gets читает символы из стандартного потока ввода stdin в область памяти, на которую указывает аргумент s. Чтение производится до тех пор, пока не встретится перевод строки или конец файла. Символ перевода строки отбрасывается, а прочитанная цепочка ограничивается нулевым байтом.

Функция fgets считывает (n-1) символов из потока ввода stream в область памяти, на которую указывает аргумент s. Чтение производится до тех пор, пока не встретится перевод строки (в отличие от gets он не отбрасывается) или конец файла. Прочитанная цепочка символов ограничивается нулевым байтом.

СМ. ТАКЖЕ   ferror(3S), fopen(3S), fread(3S), getc(3S), scanf(3S). ДИАГНОСТИКА   Если первым прочитанным символом окажется признак конца файла, то есть фактически ни одного символа не будет считано, то обе функции возвращают пустой указатель NULL. Если обнаружена ошибка чтения, например, при попытке использовать эти функции для файлов, не открытых на чтение, то также возвращается NULL. В остальных случаях возвращается значение указателя s.

В этом примере в одной статье описываются две связанные функции: gets() и fgets(). Обе функции считывают цепочку символов из потока, но делают это несколько по-разному. В разделе ОПИСАНИЕ объясняется, как действует каждая из них.

Раздел СИНТАКСИС содержит информацию о том, как нужно обращаться к описываемым функциям из программы. Заметим, что в первой строке этого раздела записано:

#include <stdio.h>

Это означает, что программе, использующей функции gets() или fgets(), необходима информация из включаемого файла стандартного ввода/вывода. Обычно оператор #include помещается в начале исходного текста. Ниже будет приведена версия файла <stdio.h>, которую можно просмотреть, чтобы понять, что используется функциями gets() и fgets().


Далее в разделе СИНТАКСИС приводится формальное описание функций. Из формального описания можно узнать:

Тип объекта, возвращаемого функцией.

В нашем примере, обе функции gets() и fgets(), возвращают указатель на символ.

Какой объект или объекты следует передавать функции при вызове.

Передаваемые объекты указываются в скобках после имени функции. Например, в нашем примере из формального описания следует, что функции gets() нужно передавать указатель на символ. Более подробная информация о передаваемых объектах приводится в разделе ОПИСАНИЕ.

Как эти объекты будут интерпретироваться функцией.

Описание

char *s;

в функции gets() означает, что s будет рассматриваться как указатель на символ. Следует иметь в виду, что в языке C при передаче аргументов имя массива преобразуется в указатель на начало этого массива.

Мы рассмотрели простой пример описания функции gets(). Если Вы хотите проверить себя на более сложном примере, попытайтесь понять значение различных элементов описания функции fgets().

Рассматривая функцию fgets(), мы сталкиваемся еще с одной особенностью языка C. Третий аргумент этой функции - stream - есть файл с ассоциированными с ним буферами, описанный как указатель на производный тип FILE. Где определяется этот тип? Правильно! В файле <stdio.h>.

Завершая обсуждение способов вызова функций, описанных в Справочнике программиста, приведем пример фрагмента программы, в котором вызывается функция gets():

#include <stdio.h>

main () { char sarray [80];

for (;;) { if (gets (sarray) != NULL) { . . . /* Выполнить что-либо с цепочкой символов */ . . . } } }

Можно задать вопрос: "Откуда функция gets() считывает символы?". Ответ: "Со стандартного ввода". Под стандартным вводом обычно понимается то, что вводится с клавиатуры терминала, на котором была набрана команда, запустившая выполнение программы, или вывод другой программы, направленный функции gets(). То, что функция gets() считывает информацию со стандартного ввода, можно узнать из раздела ОПИСАНИЕ в статье Справочника. Действительно, там написано: "Функция gets читает символы из стандартного потока ввода...". Стандартный поток ввода определен в файле <stdio.h>.



Ниже приводится содержимое файла <stdio.h>:

#ifndef _NFILE #define _NFILE 20

#define BUFSIZ 1024

/* Размер буфера при выводе в небуферизованный файл */ #define _SBFSIZ 8

typedef struct { int _cnt; unsigned char *_ptr; unsigned char *_base; char _flag; char _file; } FILE;

/* Флаг _IOLBF означает, что файловый вывод будет буфе- ризоваться построчно. _IONBF, _IOLBF и _IOFBF могут использоваться не только как флаги, но и как значения "типа" для передачи функции setvbuf */ #define _IOFBF 0000 #define _IOREAD 0001 #define _IOWRT 0002 #define _IONBF 0004 #define _IOMYBUF 0010 #define _IOEOF 0020 #define _IOERR 0040 #define _IOLBF 0100 #define _IORW 0200

#ifndef NULL #define NULL 0 #endif #ifndef EOF #define EOF(-1) #endif

#define stdin (&_iob[0]) #define stdout (&_iob[1]) #define stderr (&_iob[2])

#define _bufend(p) _bufendtab[(p)->_file] #define _bufsiz(p) (_bufend(p) - (p)->_base)

#ifndef lint #define getc(p) (--(p)->_cnt < 0 ? _filbuf(p) : \ (int) *(p)->_ptr++) #define putc(x, p) (--(p)->_cnt < 0 ? \ _flsbuf((unsigned char) (x), (p)) : \ (int) (*(p)->_ptr++ = \ (unsigned char) (x))) #define getchar() getc(stdin) #define putchar(x) putc((x), stdout) #define clearerr(p) ((void) ((p)->_flag &= \ ~(_IOERR | _IOEOF))) #define feof(p) ((p)->_flag & _IOEOF) #define ferror(p) ((p)->_flag & _IOERR) #define fileno(p) (p)->_file #endif

extern FILE _iob[_NFILE]; extern FILE *fopen(), *fdopen(), *freopen(), *popen(), *tmpfile(); extern long ftell(); extern void rewind(), setbuf(); extern char *ctermid(), *cuserid(), *fgets(), *gets(), *tempnam(), *tmpnam(); extern int fclose(), fflush(), fread(), fwrite(), fseek(), fgetc(), getw(), pclose(), printf(), fprintf(), sprintf(), vprintf(), vfprintf(), vsprintf(), fputc(), putw(), puts(), fputs(), scanf(), fscanf(), sscanf(), setvbuf(), system(), ungetc(); extern unsigned char *_bufendtab[];

#define L_ctermid 9 #define L_cuserid 9 #define P_tmpdir "/usr/tmp/" #define L_tmpnam (sizeof (P_tmpdir) + 15) #endif




Как узнать, нужны ли выполняемому файлу разделяемые библиотеки?


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

dump -hv файл

Если в файле есть секция .lib, он работает с некоторой разделяемой библиотекой. Ненамного труднее узнать, какие именно разделяемые библиотеки требуются, просмотрев содержимое секции .lib:

dump -L файл