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

         

Начало структуры, объединения или перечисления


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



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



Настройка разделяемой библиотеки


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

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

Постройте временной профиль программы [см. profil(2)]

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

Определите содержимое библиотеки

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

Группируйте взаимосвязанные функции

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


Выравнивайте на границы страниц

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

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

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

#objects #objects . . . . . . printf.o strcmp.o fopen.o malloc.o malloc.o printf.o strcmp.o fopen.o . . . . . .

Учитывайте особенности оборудования

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




Название терминала


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

Приведем название из описания терминала AT&T Teletype 5420:

5420|att5420|AT&T Teletype 5420,

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

Приведем название для нашего условного терминала myterm:

myterm|mytm|mine|fancy|terminal|My FANCY Terminal,

Имена терминалов должны удовлетворять обычным соглашениям об именах. Эти соглашения касаются, в частности, начального имени, например, 5425 или myterm. Это имя не должно содержать странных символов (например, минусов), которые могут не быть распознаны как часть синонима имени терминала. Возможные режимы оборудования и предпочтения пользователя указываются путем добавления имени и "индикатора режима" в конце имени. Например, для нашего фиктивного терминала режим с большим числом символов в строке (что обозначается через -w) будет записан в виде myterm-w. Другие индикаторы подробно обсуждаются в term(5).



Неиспользуемые переменные и функции


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

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

или -x, подавляющие подобные сообщения.

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

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

/* ARGSUSED */

Комментарий действует так же, как и опция -v, но только в пределах одной функции. Другой комментарий

/* VARARGS */

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

/* VARARGS2 */

вызывает проверку двух первых аргументов.

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



Неявные описания


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

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



Некоторые специфические свойства


Кроме сохранения распознанной цепочки в yytext[], lex автоматически подсчитывает число сопоставленных символов и сохраняет его в переменной yyleng. Можно использовать эту переменную, чтобы обратиться к любому отдельному символу, уже помещенному в массив yytext[]. Напомним, что в языке C элементы массива нумеруются, начиная с 0, поэтому, чтобы распечатать третью цифру в распознанном целом числе (если таковая имеется), следует написать:

[0-9]+ { if (yyleng > 2) printf("%c", yytext[2]); }

Чтобы разрешить неоднозначности, к которым может приводить совокупность заданных пользователем спецификаций, lex следует нескольким соглашениям. Подобные неоднозначности возникают, например, в ситуациях, когда цепочка символов (скажем, зарезервированное слово), может сопоставляться с шаблонами из двух правил. В примере лексического анализатора, описанном ниже в разделе Совместное использование lex и yacc, зарезервированное слово end может сопоставляться как с шаблоном из второго правила, так и с шаблоном из правила (седьмого) для идентификаторов.

Примечание

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

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

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

Примечание

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

В данном случае распознается операция >= и выполняется ассоциированное с ней действие. Другой пример: данное правило позволяет различить в C-программах операции + и ++.


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

в Фортране. При анализе оператора

DO 50 k = 1, 20, 1

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

DO50k = 1

есть просто оператор присваивания. (Напомним, что в Фортране пробелы игнорируются.) Для обработки ситуаций, требующих опере- жающего просмотра, используется символ / (не путать с символом \), который обозначает, что следующая за ним последовательность является завершающим контекстом и ее не следует сохранять в yytext[], поскольку она не входит в саму лексему. Правило для распознавания DO в Фортране можно записать так:

DO/[0-9 ]*[a-zA-Z0-9 ]+=[a-zA-Z0-9 ]+, printf("нашли DO");

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

Для обозначения специального завершающего контекста - конца строки - lex использует знак операции $. (Это эквивалентно \n). Ниже приведено правило, которое игнорирует все пробелы и табуляции в конце строки:

[ \t]+$ ;

С другой стороны, если нужно сопоставить шаблон непременно с начала строки, lex предлагает операцию ^ (это пример начального контекста). Например, в утилите форматирования nroff требуется, чтобы строка никогда не начиналась с пробела, поэтому для проверки входных данных nroff можно использовать такое правило:

^[ ] printf("ошибка: удалите начальные пробелы");

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



\" while (input() != '"');

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

Для выполнения специальных операций ввода/вывода можно переписать функции input(), unput(), и output(), используя стандартные для языка C средства вода/вывода. Эти и другие определенные программистом функции следует поместить в секцию подпрограмм. В таком случае новые подпрограммы заменят стандартные. Стандартная функция input(), в действительности, эквивалентна getchar(), а стандартная функция output() - putchar().

В lex'е имеется ряд подпрограмм, которые предоставляют возможность многократной обработки цепочек символов. Это функции yymore() и yyless(n) и действие REJECT. Повторим, что текст, сопоставленный с некоторой спецификацией, помещается в массив yytext[]. В общем случае, после того как выполнено действие для данной спецификации, символы в yytext[] замещаются последующими символами входного потока, образующими следующую сопоставляемую цепочку. Функция yymore(), напротив, гарантирует, что последующие распознаваемые символы будут добавляться после тех, которые уже содержатся в yytext[]. Тем самым в случае, когда значимыми оказываются как одна цепочка символов, так и другая, более длинная, включающая первую, появляется возможность выполнить сначала одно действие, а потом другое, ассоциированное с длинной цепочкой.

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

,[^,]* { if (flag == 0) { flag = 1; yymore(); } else { flag = 0; yytext [yyleng] = (char) 0; printf ("%s\n", &yytext[1]); } }

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



Функция yyless(n) позволяет переустановить указатель конца обработанных символов на символ, оказавшийся n-м в массиве yytext[]. Предположим, что мы занимаемся расшифровкой кода и уловка заключается в том, чтобы обрабатывать только половину символов в последовательности, заканчивающейся определенным символом, например, прописной или строчной буквой z. Достаточно воспользоваться правилом

[a-yA-Y]+[Zz] { yyless(yyleng/2); ...обработка первой половины цепочки...}

Наконец, действие REJECT упрощает обработку цепочек символов в случаях, когда они перекрываются или одна является частью другой. Выполнение REJECT заключается в немедленном переходе к следующему правилу без изменения содержимого yytext[]. Например, для подсчета числа вхождений в тексте как цепочки символов snapdragon, так и ее подцепочки dragon, можно использовать правило

snapdragon { countflowers++; REJECT; } dragon countmonsters++;

В следующем примере, иллюстрирующем частичное перекрытие двух шаблонов, подсчитывается число вхождений цепочек comedian и diana, причем правильно обрабатываются даже такие входные цепочки, как comediana...:

comedian { comiccount++; REJECT; } diana princesscount++;

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




Ненастраиваемые входные файлы


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

Если во входном файле ld(1) отсутствует таблица имен или информация о настройке ссылок (вследствие применения команды strip(1) либо редактирования без опции -r или с опцией -s) ld(1) продолжает работу, используя в качестве исходных данных ненастраиваемый файл.

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

Ни в одном входном файле не должно быть неразрешенных внешних ссылок.

Каждый входной файл должен быть связан с тем же адресом, что и при первом вызове ld(1).

Примечание

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



НЕОДНОЗНАЧНОСТИ И КОНФЛИКТЫ


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

expr : expr '-' expr ;

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

expr - expr - expr

правило позволяет представить ее как в виде

( expr - expr ) - expr

так и в виде

expr - ( expr - expr )

(В первом случае говорят о левой ассоциативности, во втором - о правой).

yacc выявляет подобные неоднозначности при построении алгоритма разбора. На примере входной цепочки

expr - expr - expr

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

expr - expr

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

- expr

и снова выполняет свертку. Такая последовательность действий соответствует левой ассоциативности.

Если алгоритм прочитал

expr - expr

он, напротив, может отложить немедленное применение правила и продолжить чтение до тех пор, пока не будет считана цепочка

expr - expr - expr

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

expr - expr

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

expr - expr

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


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

yacc по умолчанию использует два метода разрешения неоднозначностей:

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

При конфликте свертка-свертка по умолчанию выбирается правило, встретившееся в yacc-спецификации первым.

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

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

всегда сообщает число конфликтов перенос-свертка и свертка-свертка, разрешенных при помощи метода 1 и метода 2.

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

Рассмотрим методы разрешения неоднозначностей на следующем примере.



stat : IF '(' cond ')' stat | IF '(' cond ')' stat ELSE stat ;

Это - фрагмент спецификации языка программирования, включающего оператор if-then-else. В приведенном правиле IF и ELSE являются лексемами, cond - нетерминальный символ, описывающий условные (логические) выражения, а stat - нетерминальный символ, описывающий операторы. Первую альтернативу будем называть простым if-правилом, вторую - if-else-правилом.

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

IF ( C1 ) IF ( C2 ) S1 ELSE S2

может в соответствии с ними структурироваться двумя способами:

IF ( C1 ) { IF ( C2 ) S1 } ELSE S2

или

IF ( C1 ) { IF ( C2 ) S1 ELSE S2 }

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

IF ( C1 ) IF ( C2 ) S1

и встречает слово ELSE. Он может немедленно выполнить свертку по простому if-правилу и получить

IF ( C1 ) stat

а затем прочитать остаток входной цепочки

ELSE S2

и выполнить свертку

IF ( C1 ) stat ELSE S2

по if-else-правилу. Это соответствует первой интерпретации входной цепочки.

С другой стороны, алгоритм может сначала сделать перенос ELSE, прочитать S2, а затем выполнить свертку правой части цепочки

IF ( C1 ) IF ( C2 ) S1 ELSE S2

по if-else-правилу и получить

IF ( C1 ) stat

что в свою очередь свертывается по простому if-правилу. Это соответствует второй интерпретации, которая обычно считается более предпочтительной.

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

IF ( C1 ) IF ( C2 ) S1

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



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

23: shift-reduce conflict (shift 45, reduce 18) on ELSE

state 23

stat : IF ( cond ) stat_ (18) stat : IF ( cond ) stat_ELSE stat

ELSE shift 45 . reduce 18

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

IF ( cond ) stat

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

stat : IF ( cond ) stat ELSE_stat

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

stat : IF ( cond ) stat

по грамматическому правилу 18.

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




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


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



Номера секций и классы памяти


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

Класс памяти Возможный номер секции
C_AUTO N_ABS
C_EXT N_ABS, N_UNDEF, N_SCNUM
C_STAT N_SCNUM
C_REG N_ABS
C_LABEL N_UNDEF, N_SCNUM
C_MOS N_ABS
C_ARG N_ABS
C_STRTAG N_DEBUG
C_MOU N_ABS
C_UNTAG N_DEBUG
C_TPDEF N_DEBUG
C_ENTAG N_DEBUG
C_MOE N_ABS
C_REGPARM N_ABS
C_FIELD N_ABS
C_BLOCK N_SCNUM
C_FCN N_SCNUM
C_EOS N_ABS
C_FILE N_DEBUG
C_ALIAS N_DEBUG



Новые окна


Далее следуют описания подпрограмм newwin() и subwin(), которые применяются для создания новых окон. В curses(3X) описаны подп- рограммы newpad() и subpad() для создания новых спецокон.

newwin( )

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

WINDOW *newwin (nlines, ncols, begin_y, begin_x) int nlines, ncols, begin_y, begin_x;

ОПИСАНИЕ
  newwin() возвращает указатель на новое окно с новой областью данных.

Аргументы nlines и ncols задают размер нового окна.

Аргументы begin_y и begin_x задают координаты точки на экране [относительно (0, 0)], на которую отображается левый верхний угол окна.

ПРИМЕР
  См. пример программы, работающей с двумя окнами и рисунок выше. См. также программу window в разделе Примеры программ, работающих с curses.

subwin( )

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

WINDOW *subwin (orig, nlines, ncols, begin_y, begin_x) WINDOW *orig; int nlines, ncols, begin_y, begin_x;

ОПИСАНИЕ
  subwin() возвращает указатель на новое окно, которое является частью ранее определенного окна, orig.

Аргументы nlines и ncols задают размер нового окна.

Аргументы begin_y и begin_x задают координаты левого верхнего угла окна на экране терминала.

Как подокна, так и сами окна могут перекрываться.

Предостережение

На момент выпуска данного руководства не работало определение подокон у подокон.

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

main() { WINDOW *sub;

initscr (); box (stdscr, 'w', 'w'); /* О box() см. curses(3X) */ mvaddstr (stdscr, 7, 10, "------- это 10,10"); mvaddch (stdscr, 8, 10, ''); mvaddch (stdscr, 9, 10, 'v'); sub = subwin (stdscr, 10, 20, 10, 10); box (sub, 's', 's'); wnoutrefresh (stdscr); wrefresh (sub); endwin (); }

Эта программа заполняет границу stdscr (то есть края экрана терминала) символами w, а границу подокна sub - символами s. Другой пример см. в программе window раздела Примеры программ, работающих с curses.



Объектный файл


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

Файлы, которые создаются компиляторами, могут среди прочих содержать секции с именами .text и .data. Секция .text содержит выполняемые команды, а секция .data - инициализированные данные. Пусть, например, программа на языке C содержит глобальное, то есть не входящее в функцию, описание и оператор присваивания:

int i=100; . . . i=0;

Команды, сгенерированные для оператора присваивания, попадут в секцию .text, а место для переменной i будет отведено в секции .data.



Объекты и л_значения


Объект - это обрабатываемая область памяти. Л_значение - это выражение, обозначающее объект. Очевидным примером л_значения

является идентификатор. Есть операции, чей результат - л_значение. Например, если E - выражение типа указатель, то *E - л_значение, обозначающее объект, на который указывает E. Термин "л_значение" возник потому, что в присваивании E1 = E2 левый операнд должен обозначать объект, то есть быть л_значением. Ниже при обсуждении каждой операции указывается, требуются ли ей в качестве операндов л_значения и является ли л_значением результат.



Обеспечение совместимости с архивной библиотекой


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

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

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

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



Обеспечение совместимости с будущими версиями


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

Проверка версий библиотеки на совместимость

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

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

Пусть нам нужно сравнить две разделяемые библиотеки выполнения: new.libx_s и old.libx_s. Мы используем команду nm(1) для получения списка имен и sed(1) для исключения всех имен, кроме внешних. Работу может облегчить небольшой sed-сценарий, который мы поместим в файл cmplib.sed:

/|extern|.*/!d s/// /^.bt/d /^etext /d /^edata /d /^end /d

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

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

nm old.libx_s | sed -f cmplib.sed > old.nm nm new.libx_s | sed -f cmplib.sed > new.nm

Сравним теперь соответствующие адреса:

diff old.nm new.nm

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


Как работать с несовместимыми библиотеками

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

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

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

Примечание

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




Обход неконфигурируемых областей памяти


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

MEMORY { mem1: о=0x00000 l=0x02000 mem2: о=0x40000 l=0x05000 mem3: о=0x20000 l=0x10000 }

Пусть далее в каждом из файлов f1.o, f2.o, ..., fn.o содержатся три секции: .text, .data и .bss. Предположим также, что размер объединенной секции .text оказался бы равным 0x12000 байт. Легко видеть, что не существует конфигурируемой области памяти достаточной длины. Чтобы ld(1) мог выполнить размещение, необходимо эту секцию разделить посредством соответствующих предложений, например:

SECTIONS { txt1: { f1.o (.text) f2.o (.text) f3.o (.text) } txt2: { f4.o (.text) f5.o (.text) f6.o (.text) } . . . }



Обработка ошибок


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

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

#include <errno.h>

При успешном завершении системного вызова значение переменной errno не изменяется, поэтому оно имеет смысл только в случае, когда какой-либо системный вызов вернул -1. Список кодов ошибок приведен в Справочнике программиста в статье intro(2).

Для того, чтобы по коду ошибки, помещенному в errno, вывести в стандартный протокол сообщение об ошибке, можно воспользоваться функцией perror(3C).


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

Такие правила, как предыдущее, универсальны, но ими трудно управлять. Правила, подобные

stat : error ';'

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

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

input : error '\n' { printf ("Перенаберите последнюю строку: "); } input { $$ = $4; } ;

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

yyerrok ;

возвращает алгоритм в нормальное состояние. Последний пример можно переписать в виде

input : error '\n' { yyerrok; printf ("Перенаберите последнюю строку: "); } input { $$ = $4; } ;

Как упоминалось выше, сразу после символа error предварительно просмотренная лексема равна входной лексеме, на которой была обнаружена ошибка. Иногда это неуместно; задачу выбора корректного места для возобновления разбора может взять на себя действие, соответствующее правилу, содержащему error. В этом случае предыдущая предварительно просмотренная лексема должна быть очищена. Оператор

yyclearin ;

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

stat : error { resynch (); yyerrok; yyclearin; } ;

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




Общий вид файла описаний


Общий вид точки входа в файле описаний таков:

цель1 [цель2 ...] :[:] [зависимость1 ...] [; команды] [# ...] [\t команды ] [# ...]

Указанные в скобках компоненты могут быть опущены, цели и зависимости являются цепочками из букв, цифр, символов . и /. При обработке строки интерпретируются метасимволы shell'а, такие как * и ?. Команды могут быть указаны после точки с запятой в строке зависимостей, или в строках, начинающихся с табуляции, которые следуют сразу за строкой зависимостей. Команда - это произвольная цепочка символов, не содержащая знак #, за исключением тех случаев, когда # заключен в кавычки.



ОБЗОР ИСПОЛЬЗОВАНИЯ LEX'А


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

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

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


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



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

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

Транслировать lex-спецификации.

Компилировать, редактировать связи и запускать лексический анализатор на C.

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

После этого Вы сможете самостоятельно оценить мощность средств, предоставляемых lex'ом.




ОЧЕРЕДИ СООБЩЕНИЙ


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

Послать сообщение.

Принять сообщение.

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

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

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

Операция завершилась успешно. Процесс получил сигнал. Очередь сообщений ликвидирована.

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



Одинарная и двойная точность


Все вещественные операции в языке C выполняются с двойной точностью. Где бы в выражении не встретилось значение типа float, оно преобразуется в double. Если при преобразовании значения типа double в float (например, в присваивании) результат не может быть представлен как float, он не определен.



ОКРУЖЕНИЕ YACC'А


В результате обработки пользовательской спецификации утилитой yacc создается содержащий C-подпрограммы файл y.tab.c. Функция, которую порождает yacc, называется yyparse(); это функция с целочисленным результатом. Когда она выполняется, то периодически запрашивает у yylex(), функции лексического анализа, заданной пользователем (см. раздел ЛЕКСИЧЕСКИЙ АНАЛИЗ), входные лексемы. В конечном итоге либо обнаруживается ошибка, yyparse() возвращает значение 1 и восстановление после ошибки невозможно, либо лексический анализатор возвращает маркер конца и разбор успешно завершается. В этом случае yyparse() возвращает значение 0.

Чтобы получить работающую программу, пользователь должен сформировать для процедуры разбора определенное окружение. Например, как в любой программе на языке C, должна быть определена процедура с именем main(), вызывающая yyparse(). Кроме того, чтобы печатать сообщение об обнаруженной синтаксической ошибке, нужна процедура, называемая yyerror().

Эти две процедуры в той или иной форме должны быть заданы пользователем. Чтобы упростить на начальном этапе работу с yacc'ом, предоставляется библиотека с подразумеваемыми версиями функций main() и yyerror(). Библиотека задается при помощи аргумента -ly команды cc(1) или редактора связей. Исходный текст

main () { return (yyparse ()); }

и

#include <stdio.h>

yyerror (s) char *s; { (void) fprintf (stderr, "%s\n", s); }

демонстрирует банальность этих предопределенных программ. Аргумент у функции yyerror() - текст, содержащий сообщение об ошибке, обычно syntax error. В реальных приложениях чаще выполняются более сложные действия. Обычно программа подсчитывает номер входной строки и печатает его вместе с сообщением об обнаружении синтаксической ошибки. Внешняя переменная целого типа yychar содержит номер предварительно просмотренной лексемы на момент обнаружения ошибки. Это может представлять некоторый интерес для формирования лучшей диагностики. Так как функция main()

вероятнее всего задается пользователем (необходимо прочесть аргументы и т.п.), yacc-библиотека полезна только в маленьких проектах или на ранних этапах разработки больших.

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



Опции ввода


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

Отображает вводимые символы на экране терминала (такое отображение мы будем называть эхом). Обрабатывает символы забоя (ERASE, обычно #) и уничтожения (KILL, обычно @). Интерпретирует CTRL+D как конец файла (EOF). Обрабатывает символы прерывания (INTR) и выхода (QUIT). Сбрасывает бит четности символа. Преобразует возврат каретки в перевод строки.

Curses полностью берет на себя управление экраном терминала, поэтому его подпрограммы отключают эхо, производимое системой UNIX, и делают его самостоятельно. Временами бывает необходимо программно отключать стандартную для системы UNIX обработку символов. Для этого предназначены некоторые подпрограммы curses, например, noecho() и cbreak(). Используя такие подпрограммы, можно управлять интерпретацией вводимых символов. В приведенной ниже таблице перечислены основные подпрограммы, предназначенные для управления режимом ввода.

Если программа использует curses и вводит с терминала, она должна установить некоторые опции ввода. Это связано с тем, что при старте программы терминал может оказаться в любом из режимов cbreak(), raw(), nocbreak(), или noraw(). Как показано в таблице, при запуске программы гарантируется только наличие режима echo().

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

Опции ввода Символы
Интерпретируются Не интерпретируются
Нормальное состояние "вне curses" прерывание, выход, сброс бита четности, <CR> в <NL>, забой, уничтожение, конец файла
Нормальное состояние "старт curses-программы" эхо (заменяет эхо системы UNIX) все остальное не определено
cbreak () и echo () прерывание, выход, сброс бита четности эхо забой, уничтожение, конец файла
cbreak () и noecho () прерывание, выход, сброс бита четности забой, уничтожение, конец файла, эхо
nocbreak () и noecho () разрыв, выход, сброс бита четности, забой, уничтожение, конец файла эхо
nocbreak () и echo () См. предостережение ниже
nl () <CR> в <NL>  
nonl ()   <CR> в <NL>
raw () [вместо cbreak ()]   разрыв, выход, сброс бита четности
<
Предостережение

Не применяйте одновременно nocbreak() и noecho(). Если установлены оба режима и Вы вызываете getch(), программа при чтении каждого символа включает, а затем отключает cbreak(). В зависимости от состояния драйвера терминала при вводе символа, это может привести к выводу нежелательных данных на экран.

Помимо подпрограмм, перечисленных в таблице, для управления вводом используются следующие подпрограммы curses: noraw(), halfdelay(), nodelay(). Они обсуждаются в curses(3X).

Далее подробнее описываются подпрограммы noecho(), cbreak() и связанные с ними echo() и nocbreak().

echo( ), noecho( )

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

int echo ( )

int noecho ( )
ОПИСАНИЕ
  echo() устанавливает режим "эхо" - отображение символов на экране по мере их ввода. При старте программы этот режим установлен.
noecho() отключает режим "эхо".

Ни та, ни другая подпрограмма не возвращают полезных значений.

curses-программы могут работать неправильно, если эхо установлено одновременно с nocbreak(). См. таблицу выше и последующее предостережение. После отключения эха вводимые символы можно отображать посредством addch().

ПРИМЕР
  См. программы editor и show в разделе Примеры программ, работающих с curses.
cbreak( ), nocbreak( )

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

int cbreak ( )

int nocbreak ( )
ОПИСАНИЕ
  cbreak() включает режим "прерывание при вводе каждого символа". Программа получает каждый символ сразу после его ввода, но символы забоя, уничтожения и конца файла не обрабатываются.
nocbreak() возвращает к обычному режиму "построчный ввод". Обычно этот режим установлен при запуске прог- раммы.

Ни одна из подпрограмм не возвращает полезных значений.

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

и echo().

См. таблицу выше и последующее предостережение.

ПРИМЕР
  См. программы editor и show в разделе Примеры программ, работающих с curses.



Операции над множествами семафоров


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



Операции над очередями сообщений


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



Операции над разделяемыми сегментами памяти


В данном пункте детально описывается использование системных вызовов shmat(2) и shmdt(2) и приводится программа-пример, позволяющая поупражняться со всеми их возможностями.



Операции отношения


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

выражение_отношения: выражение < выражение выражение > выражение выражение >= выражение выражение <= выражение

Операции < (меньше), > (больше), <= (меньше или равно) и >=

(больше или равно) дают в результате 1, если указанное отношение истинно, и 0, если ложно. Тип результата - int. Выполняются обычные арифметические преобразования. Можно сравнить два указателя; результат зависит от относительного расположения в адресном пространстве указуемых объектов. Программы, использующие сравнение указателей, мобильны только в случае сравнения указателей на объекты из одного массива.



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


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

выражение_присваивания: л_значение = выражение л_значение += выражение л_значение -= выражение л_значение *= выражение л_значение /= выражение л_значение %= выражение л_значение >>= выражение л_значение <<= выражение л_значение &= выражение л_значение ^= выражение л_значение |= выражение

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

Выражение вида E1 оп= E2 почти эквивалентно E1 = E1 оп (E2), однако E1 вычисляется только один раз. В выражениях += и -= левый операнд может быть указателем, в этом случае (целочисленный) правый операнд преобразуется по правилам, описанным в пункте Аддитивные операции. Все правые операнды и все левые операнды-неуказатели должны иметь арифметический тип.



Операции равенства


выражение_равенства: выражение == выражение выражение != выражение

Операции == (равно) и != (не равно) в точности аналогичны операциям отношения, за исключением того, что имеют меньший приоритет. (Так, a<b==c<d равно 1, когда отношения a<b и c<d одновременно истинны либо ложны.)

Указатель можно сравнить с целым, только если целое - это константа 0. Указатель, которому присвоено значение 0, наверняка не указывает ни на какой объект; он и оказывается равным 0. Общепринято считать такой указатель пустым.



Операции сдвига


Выражения с операциями сдвига << и >> группируются слева направо. Обе выполняют обычные арифметические преобразования операндов, каждый из которых должен быть целочисленным. Затем правый операнд преобразуется к типу int; тип результата совпадает с типом левого операнда. Результат не определен, если правый операнд отрицателен либо больше или равен размеру (в битах) левого операнда.

выражение_сдвига: выражение << выражение выражение >> выражение

Значение выражения E1<<E2 есть E1 (рассматриваемое как набор бит), сдвинутое влево на E2 бит. Oсвободившиеся биты заполняются нулями. Значение выражения E1>>E2 есть E1, сдвинутое вправо на E2 бит. Гарантируется, что правый сдвиг будет логическим (с заполнением нулями), если E1 беззнаковое, в ином случае сдвиг может оказаться арифметическим.



Операция запятая


выражение_запятая: выражение , выражение

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

f (a, (t=3, t+2), c)

Функция f вызывается с тремя параметрами, второй из которых суть 5.



Оператор break


Оператор

break ;

приводит к завершению самого внутреннего оператора while, do, for или switch; управление передается на оператор, следующий за завершенным.



Оператор цикла do


Цикл do имеет вид

do оператор while ( выражение );

Выполнение оператора повторяется до тех пор, пока значение выражения не станет равным 0. Проверка делается после каждого выполнения оператора.



Оператор цикла for


Цикл for имеет вид

for ( выр_1 ; выр_2 ; выр_3 ) оператор

Если не учитывать действие оператора continue, данный цикл эквивалентен следующей конструкции:

выр_1 ;

while ( выр_2 ) {

оператор выр_3 ;

}

Таким образом, выр_1 задает инициализацию цикла; выр_2 - условие, проверяемое перед каждой итерацией; выполнение цикла прекращается, когда значение выр_2 становится равным 0. Выр_3 обычно задает приращение, выполняемое после каждой итерации.

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



Оператор цилка while


Цикл while имеет вид

while ( выражение ) оператор

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



Оператор continue


Оператор

continue ;

передает управление на конец текущей итерации самого внутреннего цикла while, do или for и вызывает начало следующей. Более точно, в каждом из операторов

while (...) { do { for (...) { . . . . . . . . . contin: ; contin: ; contin: ; } } while (...); }

действие

continue ;

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

goto contin;

То, что следует за contin:, является пустым оператором (см. пункт Пустой оператор).



Оператор перехода goto


Управление можно передавать безусловно при помощи оператора

goto идентификатор ;

Идентификатор должен быть меткой (см. следующий пункт), расположенной в текущей функции.



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


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

имя = выражение;

имя операция= выражение;

Здесь операция - это один из знаков +, -, * или /. Оператор присваивания должен оканчиваться точкой с запятой.

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

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

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

Функция align используется для выравнивания по границе n байт, где n есть степень двойки. Следующие два выражения равносильны:

ALIGN (n)

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

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

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

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

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

Другие выражения являются комбинациями вышеупомянутых.

Примечание

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



Оператор с меткой


Любому оператору может предшествовать префикс вида:

идентификатор :

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



Оператор возврата return


Функция возвращает управление вызвавшей ее функции при помощи оператора return, который имеет одну из двух форм:

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

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



Оператор выбора switch


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

switch ( выражение ) оператор

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

case константное_выражение :

Типом константного_выражения должен быть int. Никакие два константных_выражения в одном операторе switch не могут иметь одинаковые значения. Точное определение константных_выражений содержится в разделе КОНСТАНТНЫЕ ВЫРАЖЕНИЯ.

Кроме того, можно задать не более одного оператора с префиксом вида

default :

который принято помещать после всех операторов с префиксами case.

При выполнении оператора switch значение выражения вычисляется и сравнивается по очереди со значениями всех константных_выражений из префиксов case. При обнаружении совпадения управление передается на оператор, следующий за сопоставленным префиксом. В противном случае, если указан префикс default, управление передается на оператор с этим префиксом; иначе ни один из операторов в конструкции switch не выполняется.

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

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

switch (c) { case 'r': rflag = TRUE; break; case 'w': wflag = TRUE; break; default: (void) fprintf (stderr, "Неизвестная опция\n"); exit (2); }



Оператор-выражение


Большинство операторов является операторами-выражениями, имеющими вид

выражение ;

Обычно операторы-выражения - это присваивания или вызовы функций.



ОПЕРАТОРЫ


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


составной_оператор: { список_описаний список_операторов }

список_описаний: описание описание список_описаний

список_операторов: оператор оператор список_операторов

оператор: составной_оператор выражение ;

if ( выражение ) оператор if ( выражение ) оператор else оператор while ( выражение ) оператор do оператор while ( выражение );

for ( выражение ; выражение ; выражение ) оператор switch ( выражение ) оператор case константное_выражение : оператор default: оператор break ;

continue ;

return ;

return выражение ;

goto идентификатор ;

идентификатор : оператор ;



Описание элемента таблицы имен


Ниже приведено описание C-структуры, соответствующей элементу таблицы имен. Для включения описания в программу следует использовать файл lt;syms.h>.

#define SYMNMLEN 8 /* Максимальное количество символов в тексте имени */

struct syment { union { /* Все способы описать текст имени */ char _n_name [SYMNMLEN]; /* Текст имени */ struct { long _n_zeroes; /* Если == 0, то в таблице цепочек */ long _n_offset; /* Смещение в табл. цепочек */ } _n_n; char *_n_nptr [2]; } _n; long n_value; /* Значение имени */ short n_scnum; /* Номер секции */ unsigned short n_type; /* Тип и производный тип */ char n_sclass; /* Класс памяти */ char n_numaux; /* Число вспомогательных элементов */ };

#define n_name _n._n_name #define n_zeroes _n._n_n._n_zeroes #define n_offset _n._n_n._n_offset #define n_nptr _n._n_nptr [1]

#define SYMENT struct syment #define SYMESZ 18 /* sizeof(SYMENT) */



Описание элемента таблицы настройки ссылок


Ниже приведено описание C-структуры, соответствующей элементу таблицы настройки ссылок. Для включения описания в программу следует использовать файл <reloc.h>.

struct reloc { long r_vaddr; /* Виртуальный адрес ссылки */ long r_symndx; /* Номер в таблице имен */ unsigned short r_type; /* Тип ссылки */ }; #define RELOC struct reloc #define RELSZ 10 /* sizeof(RELOC) */



Описание элемента таблицы номеров строк


Ниже приведено описание C-структуры, соответствующей элементу таблицы номеров строк. Для включения описания в программу следует использовать файл <linenum.h>.

struct lineno { union { long l_symndx; /* Если l_lnno == 0, то номер элемента табл. имен, описывающего функцию */ long l_paddr; /* Физический адрес команд, соответст- вующих строке */ } l_addr; unsigned short l_lnno; /* Номер строки */ };

#define LINENO struct lineno #define LINESZ 6 /* sizeof(LINENO) */



Описание конфигурации памяти


Предложение MEMORY используется для указания:

Общего размера виртуальной памяти целевого компьютера.

Конфигурируемых и неконфигурируемых областей виртуальной памяти.

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

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

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

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

В настоящее время возможные атрибуты таковы:

R   Память, допускающая чтение.
  W   Память, допускающая запись.
  Х   Память, в которой могут размещаться выполняемые команды.
  I   Инициализируемая память. Например, память, отводимая под стек, обычно не инициализируется.

В дальнейшем, если понадобится, будут добавлены новые атрибуты. Если атрибуты не указаны в предложениях MEMORY, либо этих предложений нет вообще, то по умолчанию области памяти получают атрибуты R, W, Х и I.

Cинтаксис предложения MEMORY таков:

MEMORY { имя1 (атрибуты) : origin = n1, length = n2 имя2 (атрибуты) : origin = n3, length = n4 . . . }

После ключевого слова origin (которое можно сократить до org или o) указывается начальный адрес области памяти. После ключевого слова length (или, короче, len или l) указывается размер области. Операнд слова origin должен быть допустимым виртуальным адресом. Значения начального адреса и длины указываются в виде десятичной, восьмеричной или шестнадцатеричной константы, записанной по правилам языка C. Предложения MEMORY, а также спецификации origin и length внутри них, должны отделяться друг от друга пробелами, символами табуляции, переводами строк или запятыми.

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

MEMORY { valid: org = 0x10000, len = 0xFE0000 }



Описание вспомогательного элемента


Ниже приведено описание C-структуры, соответствующей вспомогательному элементу таблицы имен. Для включения описания в программу следует использовать файл <syms.h>.

#define SYMNMLEN 8 /* Максимальное количество символов в тексте имени */ #define FILNMLEN 14 /* Максимальное количество символов в имени файла */ #define DIMNUM 4 /* Размерность массива во вспомога- тельном элементе */

union auxent { struct { long x_tagndx; /* Номер в таблице имен начала структуры, объединения или перечисления */ union { struct { unsigned short x_lnno; /* Номер строки определения */ unsigned short x_size; /* Размер массива, структуры или объединения */ } x_lnsz; long x_fsize; /* Размер функции */ } x_misc; union { struct { long x_lnnoptr; long x_endndx; } x_fcn; struct { /* Если массив, то его размерности (до 4) */ unsigned short x_dimen [DIMNUM]; } x_ary; } x_fcnary; unsigned short x_tvndx; } x_sym; struct { char x_fname [FILNMLEN]; /* Имя файла (занимает весь элемент) */ } x_file; struct { long x_scnlen; /* Длина секции */ unsigned short x_nreloc; /* Число элем. с инф. о настройке ссылок */ unsigned short x_nlinno; /* Число элем. с инф. о номерах строк */ } x_scn; struct { long x_tvfill; unsigned short x_tvlen; unsigned short x_tvran[2]; } x_tv; };

#define AUXENT union auxent



Описание вспомогательного заголовка


Ниже приведено описание C-структуры, соответствующей заголовку файла a.out. Такая структура вспомогательного заголовка используется в настоящее время системой UNIX. Для включения описания в программу следует использовать файл <aouthdr.h>.

typedef struct aouthdr { short magic; /* Магическое число */ short vstamp; /* Метка версии */ long tsize; /* Размер сегмента команд в байтах */ long dsize; /* Размер секции .data (инициали- зированные данные) */ long bsize; /* Размер секции .bss (неинициали- зированные данные) */ long entry; /* Точка входа */ long text_start; /* Адрес, с которого размещается сегмент команд */ long data_start; /* Адрес, с которого размещается сегмент данных */ } AOUTHDR;



Описание заголовка файла


Ниже приведено описание C-структуры, соответствующей заголовку файла. Для включения описания в программу следует использовать файл <filehdr.h>.

struct filehdr { unsigned short f_magic; /* Магическое число */ unsigned short f_nscns; /* Количество секций */ long f_timdat; /* Время и дата создания */ long f_symptr; /* Указатель в файле на таблицу имен */ long f_nsyms; /* Число элементов в таблице имен */ unsigned short f_opthdr; /* Размер вспомогательного заголовка */ unsigned short f_flags; /* Флаги */ };

#define FILHDR struct filehdr #define FILHSZ sizeof (FILHDR)



Описание заголовка секции


Ниже приведено описание C-структуры, соответствующей заголовку секции. Для включения описания в программу следует использовать файл <scnhdr.h>.

struct scnhdr { char s_name[8]; /* Имя секции */ long s_paddr; /* Физический адрес */ long s_vaddr; /* Виртуальный адрес */ long s_size; /* Размер секции */ long s_scnptr; /* Указатель в файле на содержимое секции */ long s_relptr; /* Указатель в файле на инф. о настр. ссылок */ long s_lnnoptr; /* Указатель в файле на инф. о номерах строк */ unsigned short s_nreloc; /* Число ссылок, требующих настройки */ unsigned short s_nlnno; /* Число элементов в таблице номеров строк */ как секция комментариев)



C при помощи описаний. Описания


Способ интерпретации идентификаторов задается в C при помощи описаний. Описания имеют вид:
описание: спецификаторы список_описателей_данных ;
Описатели в списке_описателей_данных содержат описываемые идентификаторы. Спецификаторы задают тип и класс памяти идентификаторов.
спецификаторы: спецификатор_типа спецификаторы спецификатор_класса спецификаторы
Набор спецификаторов должен быть непротиворечивым (смысл этого замечания раскрывается ниже).



описатель_данных: описатель инициализатор
описатель: идентификатор ( описатель )
* описатель описатель ( )
описатель [ константное_выражение ]
спецификатор_структуры_или_объединения: struct { список_структ_описаний }
struct идентификатор { список_структ_описаний }
struct идентификатор union { список_структ_описаний }
union идентификатор { список_структ_описаний }
union идентификатор
список_структ_описаний: структ_описание структ_описание список_структ_описаний
структ_описание: спецификатор_типа список_структ_описателей ;
список_структ_описателей: структ_описатель структ_описатель , список_структ_описателей
структ_описатель: описатель описатель : константное_выражение : константное_выражение
инициализатор: = выражение = { список_инициализаторов }
= { список_инициализаторов , }
список_инициализаторов: выражение список_инициализаторов , список_инициализаторов { список_инициализаторов }
{ список_инициализаторов , }
имя_типа: спецификатор_типа абстрактный_описатель
абстрактный_описатель: пусто ( абстрактный_описатель )
* абстрактный_описатель абстрактный_описатель ( )
абстрактный_описатель [ константное_выражение ]
имя_определяемого_типа: идентификатор


Описания перечислимых типов


Переменные и константы перечислимых типов имеют целочисленный тип.

спецификатор_перечислимого_типа: enum { список_перечисляемых }

enum идентификатор { список_перечисляемых }

enum идентификатор

список_перечисляемых: перечисляемое список_перечисляемых , перечисляемое

перечисляемое: идентификатор идентификатор = константное_выражение

Идентификаторы в списке_перечисляемых описываются как константы и могут указываться повсюду, где ожидается константа. Если не указано ни одного перечисляемого с =, идентификаторы в списке_перечисляемых принимают последовательные целые значения, начиная с 0; значения идентификаторов растут при просмотре описания слева направо. Перечисляемое с = придает идентификатору указанное значение; идущие за ним идентификаторы принимают последовательные целые значения, превосходящие данное.

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

Роль идентификатора в спецификаторе_перечислимого_типа совершенно аналогична роли тега структуры в спецификаторе структуры; он именует конкретный перечислимый тип. Например, фрагмент программы

enum color {chartreuse, burgundy, claret=20, winedark}; . . . enum color *cp, col; . . . col = claret; cp = &col; . . . if (*cp == burgundy) ...

делает color тегом перечислимого типа, описывающего различные цвета, а затем объявляет cp как указатель на объект данного типа и col как объект данного типа. Множество возможных значений типа - {0, 1, 20, 21}.