Создание очередей сообщений
В данном пункте детально описывается системный вызов msgget(2) и приводится программа-пример, иллюстрирующая его использование.
Создание описания терминала
База данных terminfo содержит описания многих наиболее часто применяющихся терминалов. Может, однако, оказаться, что Вы пожелаете написать curses-программу, предназначенную для выполнения на терминале, который еще не описан. В этом случае Вам придется создать его описание.
В общем случае для генерации описания терминала необходимо:
Указать известные Вам названия терминала.
Выяснить и перечислить характеристики терминала.
Компилировать описание терминала.
Тестировать это описание.
При необходимости вернуться ко второму шагу, уточнить характеристики и повторить последующие шаги.
Иногда генерировать и тестировать описание терминала легче по частям. Такие последовательные тесты помогут выявить Ваши слабые стороны в работе по описанию терминалов. Кроме того, можно облегчить генерацию, корректируя уже существующее описание похожего терминала (не следует забывать девиз системы UNIX: опираться на работу других).
Далее мы опишем каждый шаг построения описания некоторого условного терминала myterm.
Создание пустот в выходных секциях
Специальный символ точка, ., может появляться только в предложениях определения секций и в операторах присваивания. Его появление в левой части оператора присваивания предписывает ld(1) увеличить или переустановить счетчик размещения, что приводит к образованию пустого места в выходной секции. Отметим, что под такого рода пустоты физически отводится место в выходном файле; они инициализируются с помощью заполнителя - либо подразумеваемого (0x00), либо явно указанного (см. описание опции -f в разделе Прочие возможности редактора связей и раздел Инициализация пустот и секций .bss).
Рассмотрим следующее определение секции:
outsec: { . += 0x1000; f1.o (.text) . += 0x100; f2.o (.text) . = align (4); f3.o (.text) }
Результат выполнения этого предложения будет таков:
В начале секции окажется "дыра" длиной 0x1000 байт, инизиализированная с помощью подразумеваемого заполнителя. Затем будет следовать входная секция .text из файла f1.o.
Через 0x100 байт после окончания f1.o (.text)
окажется входная секция .text из файла f2.o.
Секция .text из файла f3.o будет размещена с ближайшей границы слова вслед за секцией .text из файла f2.o (выравнивание производится относительно начала секции outsec).
Размещение и выравнивание в пределах одной выходной секции производится относительно ее начала, то есть так, как если бы секция начиналась с нулевого адреса. Поэтому, если в рассмотренном выше примере адрес выходной секции outsec окажется нечетным, то нечетным будет и адрес той части outsec, куда размещается входная секция f3.o (.text), даже если f3.o (.text) и была ранее выравнена на границу слова. Этого можно избежать, потребовав выравнивания выходной секции в целом:
outsec ALIGN (4): {
Заметим, что ассемблер as(1) всегда производит выравнивание ге- нерируемых им секций на границу слова, так что можно не указывать это требование явно. То же верно и в отношении C-компилятора.
Операторы, уменьшающие счетчик размещения, являются некорректными, поскольку повторное размещение по какому-либо адресу не допускается. Как правило, для изменения счетчика размещения используются операции += и align.
СОЗДАНИЕ РАЗДЕЛЯЕМОЙ БИБЛИОТЕКИ
В этой части описывается процесс построения разделяемой библиотеки. Рассматриваются основные шаги этого процесса и использование утилиты mkshlib(1) для создания разделяемых библиотек сборки и выполнения. Приводятся также некоторые пояснения по поводу разработки программ для включения в разделяемую библиотеку.
Предполагается, что читатель - квалифицированный программист, пишущий на языке C, которому нужно создать разделяемую библиотеку. Предполагается также знакомство с процессом построения архивной библиотеки. Вам нет необходимости читать эту часть, если Вы собираетесь ограничиться лишь использованием ранее созданных разделяемых библиотек.
Создание разделяемых сегментов памяти
Для создания разделяемого сегмента памяти служит системный вызов shmget(2). В статье shmget(2) Справочника программиста синтаксис данного системного вызова описан так:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h>
int shmget (key, size, shmflg) key_t key; int size, shmflg;
Целочисленное значение, возвращаемое в случае успешного завершения, есть идентификатор разделяемого сегмента (shmid). В случае неудачи результат равен -1.
Смысл аргументов key и shmflg тот же, что и у соответствующих аргументов системного вызова semget(2). Аргумент size задает размер разделяемого сегмента в байтах.
Системный параметр SHMMNI определяет максимально допустимое число уникальных идентификаторов разделяемых сегментов памяти (shmid) в системе. Попытка его превышения ведет к неудачному завершению системного вызова.
Системный вызов завершится неудачей и тогда, когда значение аргумента size меньше, чем SHMMIN, либо больше, чем SHMMAX. Данные системные параметры специфицируют, соответственно, минимальный и максимальный размеры разделяемого сегмента памяти.
В статье shmget(2) Справочника программиста описывается начальное значение ассоциированной структуры данных, формируемое в случае успешного завершения системного вызова.
Создание с помощью mkshlib(1) разделяемых библиотек сборки и выполнения
Как разделяемая библиотека сборки, так и разделяемая библиотека выполнения создаются утилитой ОС UNIX mkshlib(1), которая вызывает другие программы, например, as(1) или ld(1). Это делается при помощи системного вызова execvp [см. exec(2)], который ищет нужные программы в директориях, определяемых значением переменной окружения $PATH. Утилита mkshlib(1) обрабатывает префиксы подобно команде cc(1), то есть вызываемые программы получают тот же префикс. Например, 3b2mkshlib будет обращаться к 3b2ld. Относительно всех перечисленных утилит см. Справочник пользователя.
Чтобы использовать mkshlib(1), необходимо подготовить файл спецификаций (это мы только что обсудили) и указать опции командной строки. Эти последние мы сейчас и рассмотрим. Синтаксис вызова mkshlib(1) таков:
mkshlib -s файл_спецификаций [-t библ_выполнения] [-h библ_сборки] [-n] [-L каталог] [-q]
Рассмотрим допустимые опции по очереди.
-s файл_спецификаций | |
Задает файл_спецификаций разделяемой библиотеки, содержащий всю информацию, необходимую для описания этой библиотеки (о его структуре см. предыдущий раздел). В него входят спецификации таблицы переходов, маршрутное имя файла, в котором будет находиться разделяемая библиотека выполнения, начальные адреса секций команд и данных разделяемой библиотеки выполнения, спецификации инициализации для разделяемой библиотеки сборки, и, наконец, перечень объектных файлов, которые должны быть включены в разделяемую библиотеку. | |
-t библ_выполнения | |
Задает файл, в который будет помещена создаваемая разделяемая библиотека выполнения. На стадии выполнения разделяемая библиотека должна находиться в том месте, которое было указано в ее файле спецификаций (см. описание директивы #target в предыдущем разделе). Если указана опция -n, генерация разделяемой библиотеки выполнения не производится. | |
-h библ_сборки | |
Задает файл, куда будет помещена разделяемая библиотека сборки. Если эта опция не указана, разделяемая библиотека сборки не генерируется. | |
-n | |
Указывает, что не нужно генерировать разделяемую библиотеку выполнения. Эта опция может быть полезна при создании новой разделяемой библиотеки. При указании опции -n, необходимо тем не менее указывать и опцию -t, так как для построения разделяемой библиотеки сборки требуется версия разделяемой библиотеки выполнения. | |
-q | |
Не выдавать предупреждающих сообщений. |
Создание выполняемых файлов
Чтобы редактор связей просматривал разделяемую библиотеку, ее, как и обычную архивную, нужно задать с помощью опции -l команды cc(1):
cc файл.c -oфайл ... -lбибл
Например, чтобы обеспечить просмотр сетевой библиотеки, укажите такую командную строку:
cc файл.c -oфайл ... -lnsl_s
Чтобы откомпилировать все C-программы в текущем каталоге и отредактировать внешние связи со стандартной разделяемой библиотекой языка C, можно воспользоваться следующей командной строкой:
cc *.c -lc_s
Как правило, опция -lc_s включается в командную строку последней из всех опций -l. В этом случае разделяемая библиотека языка C используется подобно архивной, то есть с ее помощью разрешаются внешние ссылки, оставшиеся неразрешенными после просмотра всех остальных файлов и библиотек, указанных в командной строке.
Специализированные языки программирования
Кроме описанных языков программирования, в ОС UNIX имеются спе- циализированные языки, перечисленные ниже.
Специальные имена
В таблицу имен помещаются некоторые специальные имена, генерируемые ассемблером as(1) и другими инструментальными средствами. Эти имена перечислены в следующей таблице:
Специальное имя | Смысл |
.file | Имя файла |
.text | Адрес секции команд |
.data | Адрес секции инициализированных данных |
.bss | Адрес секции неинициализированных данных |
.bb | Адрес начала внутреннего блока |
.eb | Адрес конца внутреннего блока |
.bf | Адрес начала функции |
.ef | Адрес конца функции |
.target | Адрес структуры или объединения, возвращаемых функцией |
.xfake | Фиктивное имя структуры, объединения или перечисления |
.eos | Конец структуры, объединения или перечисления |
etext | Ближайший доступный адрес после окончания выходной секции команд |
edata | Ближайший доступный адрес после окончания выходной секции инициализированных данных |
end | Ближайший доступный адрес после окончания выходной секции неинициализированных данных |
Из перечисленных специальных имен шесть появляются парами. Имена .bb и .eb отмечают границы внутренних блоков; пара .bf и .ef
ограничивает каждую функцию. Пара .xfake и .eos именует и определяет границы непоименованных структур, объединений и перечислений. Имя .eos встречается также в конце поименованных структур, объединений и перечислений.
Для непоименованных структур, объединений и перечислений компилятор генерирует служебные имена вида .xfake, где x есть целое число. Так, если в исходном тексте встречаются три непоименованные структуры, объединения или перечисления, то они получат имена .0fake, .1fake и .2fake. В основном элементе таблицы хра- нится различная информация для различных видов имен. За основным элементом могут следовать вспомогательные.
Специальные переменные
Некоторые переменные в awk имеют специальное назначение. В этом разделе приводится полный список таких переменных и описание их использования:
NR | |
Порядковый номер текущей записи. | |
NF | |
Число полей в текущей записи. | |
FS | |
Входной разделитель полей, по умолчанию равен пробелу/табуляции. | |
RS | |
Входной разделитель записей, по умолчанию равен символу перевода строки. | |
$i | |
i-е поле текущей записи. | |
$0 | |
Текущая входная запись целиком. | |
OFS | |
Выходной разделитель полей, по умолчанию равен пробелу. | |
ORS | |
Выходной разделитель записей, по умолчанию равен символу перевода строки. | |
OFMT | |
Формат для вывода на печать чисел, используется оператором print; по умолчанию равен %.6g. | |
FILENAME | |
Имя файла, из которого в данный момент производится ввод. Это удобно, поскольку обычно awk-программы имеют вид |
awk -f программа файл1 файл2 файл3 ...
СПЕЦИФИЧЕСКИЕ ВОЗМОЖНОСТИ
В этом, заключительном разделе описывается использование некоторых специфических возможностей awk'а.
Спецификации
Шаблоны специфицируются с помощью нотации, называемой регулярными выражениями. Регулярное выражение задается цепочкой символов со знаками операций или без них. Простейшие регулярные выражения - это цепочки символов без знаков операций. Пример:
apple orange pluto
Эти три регулярные выражения сопоставляются со всеми вхождениями указанных цепочек символов. Если Вы хотите, чтобы порожденный лексический анализатор удалял из входного текста цепочки orange, специфицируйте правило
orange ;
Так как действие справа от шаблона (перед точкой с запятой) не задано, lex не делает ничего, кроме вывода первоначального текста с удалением каждого вхождения данного регулярного выражения, то есть вообще без вхождений цепочки orange.
В отличие от orange, большинство распознаваемых выражений нельзя специфицировать так просто. Например, само выражение может быть слишком длинным. В более общем случае класс распознаваемых выражений оказывается слишком большим; на самом деле, он может быть бесконечным. Благодаря использованию операций можно формировать регулярные выражения, обозначающие любое выражение из определенного класса. Операция +, например, обозначает одно или более вхождений предшествующего выражения, ? обозначает 0 или 1 вхождение предшествующего выражения (что, конечно, эквивалентно необязательности предшествующего выражения), * обозначает 0 или более вхождений предшествующего выражения. (На первый взгляд кажется странным говорить о 0 вхождениях выражения и иметь операцию , чтобы выразить данную мысль, однако часто это весьма полезно. Ниже мы увидим соответствующий пример). Так, m+ - это регулярное выражение, которое сопоставляется с любой цепочкой из символов m, такой, как каждая из следующих:
mmm m mmmmm mm
Далее, 7* - это регулярное выражение, которое сопоставляется с любой цепочкой из нуля или большего числа семерок:
77 77777
777
Цепочка пробелов в третьей строке сопоставляется просто потому, что в ней вообще нет семерок.
Квадратные скобки, [ ], обозначают произвольный символ из цепочки символов, указанной в скобках. Так, [dgka] сопоставляется с одиночными символами d, g, k и a. Отметим, что символы в квадратных скобках не нужно разделять запятыми. Любая запятая в данном случае рассматривается как символ, который должен распознаваться во входном тексте. Диапазоны букв или цифр (в стандартном алфавитном или числовом порядке) обозначаются при помощи дефиса, -. Так, запись [a-z] обозначает произвольную строчную латинскую букву. Приведем пример более интересного регуляр- ного выражения:
[A-Za-z0-9*&#]
Такой шаблон сопоставляется с любой латинской буквой (строчной или прописной), любой цифрой, символами *, & или #. Если входной текст имеет вид
$$$$?? ????!!!*$$ $$$$$$&+====r~~# ((
то лексический анализатор, одно из правил которого содержит приведенный выше шаблон, распознает *, &, r и #, выполняя каждый раз действие, специфицированное в правиле (в примере действие опущено), и выводит остаток текста без изменений.
Иногда проще указать не само множество нужных символов, а его дополнение, поместив сразу за открывающей скобкой [ символ ^.
Чрезвычайно мощным средством является комбинирование операций. Пример - регулярное выражение, обозначающее идентификатор во многих языках программирования:
[a-zA-Z][0-9a-zA-Z]*
Идентификатор в этих языках определяется как буква, за которой следует нуль или больше букв или цифр, и именно это задается в регулярном выражении. Первая пара скобок сопоставляется с произвольной буквой. Вторая, если бы за ней не следовал знак операции *, сопоставлялась с произвольной буквой или цифрой. Но за счет использования операции * регулярное выражение сопоставляется с буквой, за которой следует произвольное число букв или цифр. В частности, были бы распознаны как идентификаторы следующие цепочки символов:
e pay distance pH EngineNo99 R2D2
Отметим, что не будут распознаваться как идентификаторы
not_idenTIFIER 5times $hello
так как not_idenTIFIER содержит подчеркивание, 5times начинается с цифры, но не с буквы, а $hello начинается с символа $. Конечно, в качестве упражнения Вы можете написать спецификации для этих трех примеров.
Очевидно, в тех случаях, когда знаки операций нужно использовать как обычные символы, входящие в шаблон, требуется специальная нотация. Так, в последнем из приведенных примеров звездочка не является частью идентификатора. В lex'е указанную проблему можно решать двумя способами. Символ, заключенный в кавычки, либо символ, перед которым указан знак \, рассматривается буквально, то есть как часть искомого текста. Чтобы приме- нить второй способ для распознавания, скажем, символа *, за которым следует произвольное число цифр, можно использовать шаблон
\*[0-9]*
Чтобы распознать сам символ \, требуется шаблон \\.
Спецификации файлов
При определении выходной секции, файлы и секции файлов, которые должны быть включены в нее, нужно перечислять в том порядке, в котором они должны следовать в выходной секции. Секции входного файла указываются одним из следующих способов:
имя_файла (имя_секции)
имя_файла (имя_секции1 имя_секции2 ...)
Имена секций входного файла, как и сами спецификации файлов, должны отделяться друг от друга пробелами, символами табуляции, переводами строк или запятыми.
Чтобы указать все неинициализированные и неразмещаемые глобальные объекты из данного файла, можно использовать запись
имя_файла [COMMON]
Если имя файла указывается без сопровождающего его списка секций, то все секции этого файла (кроме неинициализированных и неразмещаемых глобальных объектов) вставляются в определяемую выходную секцию. Пример:
SECTIONS { outsec1: { file1.o (sec1) file2.o file3.o (sec1, sec2) } }
В результате выполнения этого предложения порядок размещения входных секций в выходной секции outsec1 будет таким:
Секция sec1 из файла file1.o.
Все секции файла file2.o, в том же порядке, в котором они следуют в файле file2.o.
Секция sec1 из файла file3.o, а затем секция sec2 того же файла file3.o.
Если в других входных файлах окажутся секции с именем outsec1, то они будут следовать после секций, перечисленных в приведенном выше определении выходной секции outsec1. Если в файле file3.o или в файле file1.o окажутся, кроме перечисленных выше, еще какие-нибудь входные секции, то они будут помещены в одноименные выходные, - в том, разумеется, случае, если пользователь не включит их в другие спецификации файлов.
Для спецификации всех не размещенных ранее входных секций с определенным именем (независимо от имени входного файла) используется запись вида
* (имя_секции)
Спецификаторы класса памяти
Спецификаторы класса памяти имеют вид:
спецификатор_класса:
auto static extern register typedef
Спецификатор typedef причислен к спецификаторам_класса только для удобства описания синтаксиса; память он не резервирует (см. дополнительную информацию в пункте Определяемые типы). Смысл различных классов памяти обсуждается в пункте Имена типов.
Описания auto, static и register служат также и определениями, поскольку они вызывают выделение соответствующего объема памяти. Если в некоторой функции при описании каких-либо идентификаторов используется спецификатор extern (см. ВНЕШНИЕ ОПРЕДЕЛЕНИЯ), где-то вне этой функции должно быть указано их внешнее определение.
Описание register лучше всего понимать как описание auto вместе с указанием компилятору, что описанные переменные будут интенсивно использоваться. В каждой функции эффективными будут лишь несколько первых из подобных описаний. Более того, в регистрах можно разместить переменные только некоторых типов. Еще одно ограничение, касающееся переменных, описанных с использованием класса памяти register, состоит в том, что к ним нельзя применять операцию вычисления адреса &. Можно надеяться, что программы при разумном использовании описаний register будут несколько меньше и быстрее.
В описании может быть задано не более одного спецификатора_класса памяти. Если спецификатор_класса опущен, он считается равным auto внутри функции и extern - вне. Исключение: функции не могут быть автоматическими.
Спецификаторы типа
Спецификаторы типа имеют вид:
спецификатор_типа: спецификатор_базового_типа спецификатор_структуры_или_объединения имя_определяемого_типа спецификатор_перечислимого_типа
спецификатор_базового_типа: базовый_тип базовый_тип спецификатор_базового_типа
базовый_тип:
char short int long unsigned float double void
Вместе с ключевым словом int можно указать long или unsigned long; смысл такой записи - тот же, что и в случае отсутствия int. Вместе с float можно указывать ключевое слово long; эта запись имеет тот же смысл, что и double. Ключевое слово unsigned может быть указано само по себе, либо вместе с int или с любым из его вариантов (long или unsigned long), а также с char.
В остальных случаях в описании может быть задано не более одного спецификатора_типа. В частности, не допускается использование дополнений long, unsigned long и unsigned вместе с именами_определяемых_типов. Если в описании спецификатор_типа опущен, он считается равным int.
Спецификаторы для структур, объединений и перечислимых типов обсуждаются в пунктах Описания структур и объединений и Описания перечислимых типов. Определяемые типы обсуждаются в пункте с соответствующим названием.
СРЕДСТВА ОРГАНИЗАЦИИ РАЗРАБОТКИ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ
Следующие три утилиты предназначены для организации процесса разработки программного обеспечения.
Ссылки
Ссылки на статьи из Справочников пользователя, программиста и администратора ОС UNIX имеют вид название_статьи(номер_раздела). Не следует путать такие ссылки с примерами вызовов функций. Пронумерованные разделы расположены в следующих Справочниках:
Раздел 1 - в Справочнике пользователя.
Разделы 2, 3, 4, 5 - в Справочнике программиста.
Разделы 1M, 7, 8 - в Справочнике администратора.
Стандартные открытые файлы
Программы могут иметь несколько открытых файлов одновременно. Максимально допустимое число открытых в программе файлов может быть различным для разных систем; как правило, оно равно 20. Число файлов, открытых для выполнения операций стандартного ввода/вывода, задается константой _NFILE во включаемом файле <stdio.h>.
Обычно имеется три стандартных открытых файла. В файле <stdio.h>, примерно в середине его текста, имеются три директивы #define, устанавливающие stdin, stdout и stderr равными адресам _iob[0], _iob[1] и _iob[2] соответственно. Массив _iob содержит информацию о потоках, открытых программой. Номер элемента массива _iob называется также дескриптором файла. По умолчанию в ОС UNIX все три стандартных открытых файла ассоциированы с терминалом.
Так как эти файлы постоянно открыты, то при использовании функций и макросов для выполнения операций ввода/вывода со стандартными потоками ввода и вывода stdin и stdout нет необходимости открывать и закрывать файлы. Например, упоминавшаяся выше функция gets() читает символы из потока стандартного ввода stdin; функция puts() записывает завершающуюся нулевым байтом цепочку символов в поток стандартного вывода stdout. Имеются и другие функции для работы с этими потоками, например, для организации посимвольного или форматного ввода/вывода и другие. Кроме упомянутых функций, имеются также функции, выполняющие операции ввода/вывода с потоками, отличными от stdin и stdout. К таким функциям относится, например, fprintf(), которая выполняет те же действия, что и функция printf(), но вывод при этом направляется в указанный поток, например, stderr. Для того, чтобы при выполнении программы операции чтения и записи выполнялись с нужными файлами, можно также воспользоваться средствами shell'а. В заключении приведем пример указания потоков ввода и вывода. Пусть требуется отделить сообщения об ошибках и обычный вывод программы, направляемый в stdout. Это может потребоваться, например, когда вывод программы будет использоваться в качестве ввода для другой программы. В таком случае для организации обычного вывода и выдачи сообщений можно использовать различные модификации одной и той же функции из пакета стандартного ввода/вывода, одна из которых направляет вывод в stdout, а другая - в указанный в качестве ее аргумента поток.
СТАРШИНСТВО ОПЕРАЦИЙ
Есть ситуация, в которой приведенные выше методы разрешения конфликтов не подходят. Имеется в виду разбор арифметических выражений. Большинство конструкций, обычно используемых в арифметических выражениях, можно естественным образом описать при помощи понятия уровней старшинства (приоритетов) операций и информации о левой или правой ассоциативности. Применяя данный механизм, можно использовать неоднозначные грамматики с соответствующими правилами разрешения неоднозначностей для порождения алгоритмов разбора, которые быстрее и легче задавать, чем алгоритмы, построенные из однозначных грамматик. Основная идея заключается в том, чтобы записать грамматические правила для всех требуемых бинарных и унарных операций в виде
expr : expr OP expr
или
expr : UNARY expr
В результате получается неоднозначная грамматика с очень большим числом конфликтов. В качестве метода разрешения неоднозначностей пользователь специфицирует приоритеты (степень связывания) всех операций и ассоциативность бинарных операций. Этой информациии достаточно, чтобы дать возможность yacc'у разрешить конфликты и построить алгоритм разбора, реализующий заданные приоритеты и ассоциативности.
Приоритеты и ассоциативности связываются с лексемами в секции определений. Это делается в группе строк, начинающихся с ключевых слов yacc'а %left, %right или %nonassoc, за которыми следуют списки лексем. Считается, что все лексемы в одной строке имеют одни и те же приоритет и ассоциативность; строки указываются в порядке возрастания приоритета. Так, строки
%left '+' '-' %left '*' '/'
задают приоритет и ассоциативность четырех арифметических операций. '+' и '-' левоассоциативны и имеют меньший приоритет, чем также левоассоциативные '*' и '/'. Для описания правоассоциативных операций используется ключевое слово %right, а для описания операций, подобных операции .LT. в Фортране, которые не являются ассоциативными, - ключевое слово %nonassoc. Например, выражение
A .LT. B .LT. C
некорректно в Фортране; поэтому такую операцию следует описать в yacc при помощи ключевого слова %nonassoc. Действие названных ключевых слов иллюстрирует спецификация
%right '=' %left '+' '-' %left '*' '/'
%%
expr : expr '=' expr | expr '+' expr | expr '-' expr | expr '*' expr | expr '/' expr | NAME ;
Ее можно использовать, чтобы входную цепочку
a = b = c * d - e - f * g
структурировать следующим образом:
a = (b = (((c * d) - e) - (f * g)))
что соответствует истинным приоритетам операций. При использовании данного механизма должен быть, вообще говоря, задан приоритет унарных операций. Иногда унарная и бинарная операции имеют одно и то же внешнее представление, но различные приоритеты. Примером является унарный и бинарный минус, -.
Унарному минусу можно придать тот же приоритет, что и умножению, или даже выше, в то время как у бинарного минуса приоритет меньше, чем у умножения. Ключевое слово %prec изменяет приоритет операции в пределах одного грамматического правила. Ключевое слово %prec указывается сразу за телом правила, перед действием или завершающей точкой с запятой; за ключевым словом следует имя лексемы или литерал. В результате приоритет правила становится таким же, как и у данной лексемы или литерала. Например, правила
%left '+' '-' %left '*' '/'
%%
expr : expr '+' expr | expr '-' expr | expr '*' expr | expr '/' expr | '-' expr %prec '*' | NAME ;
можно использовать, чтобы установить унарному минусу тот же приоритет, что и у умножения.
Лексему, описанную при помощи конструкций %left, %right или %nonassoc, можно (но не обязательно) описать и при помощи %token.
Приоритеты и ассоциативность yacc использует для того, чтобы разрешить конфликты разбора. Они задают следующие методы разрешения неоднозначностей:
Приоритеты и ассоциативность приписываются тем лексемам и литералам, для которых они заданы явно.
Грамматическому правилу сопоставляются приоритет и ассоциативность последней лексемы или литерала в теле правила. В случае использования конструкции %prec это сопоставление переопределяется. Некоторым правилам приоритет и ассоциативность могут не сопоставляться.
Если есть конфликт свертка-свертка или конфликт свертка-перенос, и входной символ или грамматическое правило не имеют приоритета и ассоциативности, то используется общий метод разрешения неоднозначностей, приведенный в начале раздела, и выдается информация о конфликтах.
Если есть конфликт свертка-перенос и как грамматическому правилу, так и входному символу сопоставлен приоритет и ассоциативность, то конфликт разрешается в пользу той операции перенос или свертка, которой соответствует больший приоритет. Если приоритеты равны, используется информация об ассоциативности. Левая ассоциативность обозначает свертку, правая - перенос, неассоциативность - ошибку.
Конфликты, разрешенные при помощи приведенного метода, не учитываются при подсчете сообщаемого yacc'ом числа конфликтов перенос-свертка и свертка-свертка. Это означает, что ошибки в спецификации приоритетов могут замаскировать ошибки во входной грамматике. Хорошо было бы воздерживаться от задания приоритетов или использовать их весьма осторожно, пока не приобретен определенный опыт. Чтобы убедиться, что получен именно тот алгоритм разбора, который требуется, следует изучить содержимое файла y.output.
Стиль
Трудно написать правила, которые выполняют серьезные действия и к тому же имеют удобочитаемый вид. Ниже приводится несколько рекомендаций, касающихся стиля спецификаций.
Пишите имена лексем прописными буквами, а имена нетерминальных символов - строчными. Так удобнее отлаживать yacc-спецификацию.
Помещайте грамматические правила и действия на отдельных строках. Это облегчает чтение.
Все правила с одинаковой левой частью помещайте вместе. Левую часть пишите только раз, а альтернативы разделяйте вертикальной чертой.
Точку с запятой ставьте на отдельной строке и только после последнего правила в группе правил с одинаковой левой частью. Это позволяет легко добавлять новые правила.
Используйте отступы для выделения действий и их тел.
Сложные действия выделяйте в подпрограммы, определенные в других файлах.
Основная проблема заключается в том, чтобы не утопить грамматические правила в трясине действий.
Странные конструкции
lint выявляет ряд конструкций, являющихся вполне корректными, но несколько странными. Предполагается, что сообщения, выдаваемые при этом, должны поощрять лучшее качество программ, более ясный стиль, а иногда и способствовать обнаружению ошибок. (Чтобы отменить эти проверки, используется опция -h). Например, в операторе
*p++;
* ничего не дает. Такая конструкция вызывает сообщение lint'а:
null effect
В следующем фрагменте программы
unsigned x; . . . if (x < 0) . . .
условие никогда не будет выполнено. В таком случае lint выдает сообщение
degenerate unsigned comparison
Аналогично, для того же x, проверка
if (x > 0) . . .
эквивалентна
if (x != 0) . . .
Поэтому lint спрашивает:
unsigned comparison with 0?
Если программа содержит нечто, подобное фрагменту
if (1 != 0) . . .
lint выдает сообщение
constant in conditional context
поскольку сравнение 1 с 0 дает неизменный результат.
В некоторых конструкциях lint выявляет свойства, связанные со старшинством операций. Ошибки, вызванные неправильным пониманием старшинства, могут быть замаскированы использованием пробелов и другими чисто внешними особенностями расположения операндов, что делает обнаружение подобных ошибок исключительно сложным. Например, операторы
if (x&077 == 0) . . .
и
x<<2 + 40
имеют, скорее всего, совсем не тот смысл, который имелся в виду. Лучшее решение - использовать в таких выражениях скобки; к этому и призывает соответствующее сообщение lint'а.
Strip(1)
Команда strip(1) удаляет из объектного файла обычного формата таблицу имен и информацию о номерах строк. После применения команды strip количество символов в файле, выдаваемое командой ls -l, приближается к значению, получаемому по команде size. Но файл все еще содержит некоторые заголовки, которые не входят в секции .text, .data или .bss. После выполнения команды strip
данный файл уже не допускает символьной отладки.
Строки продолжения
Слишком длинную строку, не являющуюся комментарием, можно продолжить, используя знак \. Если последним символом строки является символ \, то он, а также перевод строки и все следующие за ним пробелы и табуляции заменяются на одиночный пробел.
Структура программы
awk-программа - это последовательность операторов следующего вида:
шаблон { действие } шаблон { действие } . . .
awk обрабатывает набор входных файлов. Основной операцией awk'а является просмотр входных строк в порядке их поступления. Каждую строку awk пытается сопоставить с описанным в awk-программе шаблоном. Если сопоставление оказалось успешным, выполняется соответствующее этому шаблону действие. Таким же образом к данной входной строке применяются и все остальные содержащиеся в awk-программе операторы. После того, как проверены все шаблоны, awk переходит к следующей входной строке, причем awk-программа снова выполняется с начала.
В awk-операторах могут отсутствовать как шаблоны, так и действия, но не обе части одновременно. Отсутствие действия эквивалентно предписанию распечатать строку. Если указано только действие, оно выполняется для каждой входной строки. Пустая awk-программа не делает ничего. Поскольку оба компонента оператора являются необязательными, действия должны заключаться в фигурные скобки, чтобы отличить их от шаблонов.
Например, awk-программа
/x/ { print }
выводит все входные строки, содержащие символ x.
awk-программа имеет следующую структуру:
секция BEGIN
основная секция (секция записей)
секция END
Секция BEGIN выполняется перед обработкой входных строк, секция END - после того, как все исходные данные обработаны. Основная секция выполняется для каждой входной строки. Ключевые слова BEGIN и END являются на самом деле шаблонами специального вида, которые распознаются awk'ом.
Структуры и объединения
Структуры и объединения можно присваивать, передавать в качестве параметров функциям и возвращать как результаты функций. Другие вполне осмысленные операции, такие как проверка равенства и преобразование типа структуры, не реализованы.
При ссылке на элемент структуры или объединения имя справа от -> или . должно специфицировать компонент составного объекта, поименованного или указанного выражением, стоящим слева. Вообще говоря, с элементами объединения нельзя работать до тех пор, пока объединению не присвоено значение с использованием именно этого элемента. Однако, чтобы упростить работу с объединениями, язык все же предоставляет следующую гарантию: если в объединение входит несколько структур, имеющих общую начальную последовательность элементов, и если в данный момент объединение содержит одну из них, можно обрабатывать общую начальную часть любой из этих структур. Например, корректен следующий фрагмент:
union { struct { int type; } n; struct { int type; int intnode; } ni; struct { int type; float floatnode; } nf; } u; . . . u.nf.type = FLOAT; u.nf.floatnode = 3.14; . . . if (u.n.type == FLOAT) ... sin (u.nf.floatnode) ...
Суффиксы и правила трансформации
Утилита make использует таблицу встроенных правил, определяющих, как преобразовать файл с одним суффиксом в файл с другим суффиксом. Если в командной строке make'а указан флаг -r, встроенная таблица не используется.
В действительности список суффиксов совпадает со списком зависимостей для имени .SUFFIXES. make пытается найти файл с одним из суффиксов из списка. Если такой файл найден, make трансформирует его в файл с другим суффиксом. Имена правил трансформации - это просто конкатенации суффиксов файлов до и после трансформации. Так, правило трансформации .c-файла в .o-файл называется .c.o. Если данное правило существует, а в пользовательских файлах описаний не задано явной последовательности команд, используется последовательность команд, соответствующая правилу .c.o. Если команда порождается при помощи одного из таких правил, посредством макроса $* можно получить основу (все, кроме суффикса) имени файла; макрос $< обозначает полное имя файла из строки зависимостей, вызвавшего выполнение действия.
Порядок суффиксов в списке существенен, поскольку список просматривается слева направо. Используется первое сформированное имя - то, для которого имеется и файл, и соответствующее ему правило. Если надо добавить новые имена, пользователь может добавить запись в правило .SUFFIXES в файле описаний. Зависимости добавляются к стандартному списку. Строка .SUFFIXES, не содержащая зависимостей, делает текущий список пустым. Если порядок имен надо изменить, текущий список должен быть очищен.
Связь между типом и классом памяти
Ниже перечислены типы, допустимые для каждого класса памяти.
Класс памяти | d-поля | Базовый тип | ||
Функция? | Массив? | Указатель? | ||
C_AUTO | нет | да | да | Любой, кроме T_MOE |
C_EXT | да | да | да | Любой, кроме T_MOE |
C_STAT | да | да | да | Любой, кроме T_MOE |
C_REG | нет | нет | да | Любой, кроме T_MOE |
C_LABEL | нет | нет | нет | T_NULL |
C_MOS | нет | да | да | Любой, кроме T_MOE |
C_ARG | да | нет | да | Любой, кроме T_MOE |
C_STRTAG | нет | нет | нет | T_STRUCT |
C_MOU | нет | да | да | Любой, кроме T_MOE |
C_UNTAG | нет | нет | нет | T_UNION |
C_TPDEF | нет | да | да | Любой, кроме T_MOE |
C_ENTAG | нет | нет | нет | T_ENUM |
C_MOE | нет | нет | нет | T_MOE |
C_REGPARM | нет | нет | да | Любой, кроме T_MOE |
C_FIELD | нет | нет | нет | T_ENUM, T_UCHAR, T_USHORT, T_UINT, T_ULONG |
C_BLOCK | нет | нет | нет | T_NULL |
C_FCN | нет | нет | нет | T_NULL |
C_EOS | нет | нет | нет | T_NULL |
C_FILE | нет | нет | нет | T_NULL |
C_ALIAS | нет | нет | нет | T_STRUCT, T_UNION, T_ENUM |
Условия относительно d-полей касаются всех полей d1..d6 с тем исключением, что не может быть двух последовательных производных типов функция.
Хотя аргументы функции и могут быть объявлены как массивы, они все равно трактуются как указатели. Поэтому аргумент функции не может иметь массив в качестве своего первого производного типа.
Связывание
Часто бывает необходимо, чтобы начало секции приходилось на определенный, заранее известный адрес. Установление соответствия между объектом и начальным адресом называется связыванием. в таком случае говорят, что объект связан с определенным адресом. Обычно связыванию подвергаются выходные секции, однако имеется возможность связывать с адресами и некоторые абсолютные глобальные имена, для чего используется оператор присваивания управляющего языка ld(1).
СВОДКА СИНТАКСИСА
Данная сводка синтаксиса языка C предназначена скорее для лучшего понимания, чем для строгого изложения языка.
System(3S)
Формальное описание функции system() выглядит так:
#include <stdio.h>
int system (string) char *string;
Здесь аргумент string трактуется shell'ом как командная строка. Таким образом, string может содержать имя и аргументы любой выполняемой программы или стандартной команды ОС UNIX. Если аргументы передаваемой команды заранее не известны, то для формирования нужного значения string можно воспользоваться функцией sprintf(). Возвращаемое функцией system() значение является кодом завершения shell'а. При обращении к system() вызвавшая программа ожидает завершения выполнения переданной команды, а затем продолжает выполнение со следующего выполняемого оператора.
ТАБЛИЦА ЦЕПОЧЕК
Тексты имен, состоящие более чем из восьми символов, размещаются последовательно в таблице цепочек, причем каждый текст завершается нулевым байтом. Первые четыре байта таблицы цепочек содержат размер этой таблицы в байтах; таким образом, смещения всех имен из этой таблицы больше или равны 4.
ТАБЛИЦА ИМЕН
Порядок имен в этой таблице очень важен для символьной отладки. Имена размещаются в порядке, показанном в следующей таблице:
Имя файла 1 |
Функция 1 |
Имена, локальные для функции 1 |
Функция 2 |
Имена, локальные для функции 2 |
. . . |
Статические имена |
Имя файла 2 |
Функция 1 |
Имена, локальные для функции 1 |
. . . |
Статические имена |
. . . |
Определенные глобальные имена |
Неопределенные глобальные имена |
Статическими в приведенной таблице называются имена, определенные (в языке C) с классом памяти static вне всех функций. Таблица содержит по меньшей мере один элемент фиксированной длины для каждого имени, однако для некоторых имен за этим элементом может следовать несколько вспомогательных элементов той же длины. Элемент таблицы хранит значение имени, тип и другую информацию.
Текстовые константы
Текстовая константа - это цепочка из нуля или большего числа символов, заключенная в двойные кавычки; например: ",", "a", "ab", "12". Чтобы включить в цепочку сам символ двойной кавычки, перед ним надо поставить знак \: "He said, \"Sit!\"". Чтобы включить в цепочку символ перевода строки, в соответствующем месте надо указать \n. Никаких других управляющих последовательностей не требуется. Текстовые константы могут иметь (практически) любую длину.
Формат текстовых констант описан ранее, в разделе Лексемы. Числовое значение текстовой константы равно 0, если только цепочка, заключенная в двойные кавычки, не является записью числовой константы. В этом случае числовое значение определяется естественным образом. Предпочтительным является текстовое значение, которое всегда совпадает с самой константой. Следующая таблица содержит примеры значений текстовых констант:
Текстовая константа | Числовое значение | Текстовое значение |
"" | 0 | пусто |
"a" | 0 | a |
"XYZ" | 0 | XYZ |
"o" | 0 | 0 |
"1" | 1 | 1 |
".5" | 0.5 | .5 |
".5e2" | 50 | .5e2 |
Текстовая константа - это последовательность символов, ограниченная двойными кавычками: "...". Текстовая константа имеет тип "массив символов" и класс памяти static (см. КЛАСС ПАМЯТИ И ТИП) и инициализируется указанными символами. В конце каждой текстовой константы компилятор помещает нулевой байт (\0); благодаря этому программы, просматривающие текстовую константу, могут обнаружить ее конец. Если требуется включить в текстовую константу символ двойной кавычки ("), перед ним надо поставить знак \; кроме того, могут использоваться те же управляющие символы, что и в символьных константах.
Знак \ и следующий непосредственно за ним символ перевода строки игнорируются. Все текстовые константы, даже внешне идентичные, располагаются в отдельных фрагментах памяти.
Тексты имен
Первые 8 байт элемента таблицы имен представляют собой объединение массива символов и двух четырехбайтных целых чисел. Если длина текста имени не превышает восьми символов, то здесь хранится сам текст, дополненный нулевыми байтами. Если же его длина больше восьми символов, то текст хранится в таблице цепочек. В этом случае в первых восьми байтах элемента таблицы имен находятся два целых числа, первое из которых равно нулю, а второе есть смещение соответствующего элемента таблицы цепочек относительно начала этой таблицы. Нули в первых четырех байтах позволяют отличить текст от ссылки на него, поскольку никакое имя не может начинаться с нулевых байт:
Байты | Описание | Имя | Смысл |
0-7 | char | n_name | 8-символьный текст имени, дополненный нулевыми байтами |
0-3 | long | n_zeroes | Нули в этом поле указывают, что текст имени находится в таблице цепочек |
4-7 | long | n_offset | Смещение текста имени относительно начала таблицы цепочек |
Специальные имена, генерируемые C-компилятором, описаны выше в разделе Специальные имена.
ТЕРМИНОЛОГИЯ
Прежде чем перейти к описанию средств блокировки сегментов, определим некоторые термины.
Сегмент | |
Последовательный участок файла. Операционная система UNIX не поддерживает структуру сегментов файла. Если это нужно, Вы должны делать это сами в программах, работающих с файлами. | |
Взаимодействующие процессы | |
Процессы, которые работают совместно для достижения некоторой заранее определенной общей цели. Процессы, совместно использующие файлы, должны запрашивать разрешение на доступ к файлу перед его использованием. Права доступа должны быть правильно установлены, чтобы не дать возможности доступа к этим файлам из других процессов. Термин процесс далее неизменно будет применяться в отношении процессов такого рода. | |
Блокировка на чтение (разделяемая) | |
Используется для того, чтобы установить ограниченный доступ к сегментам файла. Когда сегмент заблокирован на чтение, другие процессы также могут заблокировать на чтение весь сегмент или его часть. Никакие другие процессы, однако, не должны (к моменту блокировки на чтение) блокировать на запись пересекающийся с сегментом участок файла, и не смогут сделать это позже. Если процесс заблокировал сегмент на чтение, то можно считать, что никакие процессы не будут записывать или изменять этот сегмент в то же самое время. Тем не менее данный способ блокировки позволяет многим процессам читать данный сегмент одновременно. Такая блокировка необходима, например, когда организуется поиск в файле, а запросы на блокировку на запись (монопольную) могут привести к нежелательным осложнениям. | |
Блокировка на запись (монопольная) | |
Используется для получения полного контроля над доступом к сегменту файла. Когда сегмент блокируется монопольно, никакие другие процессы не имеют возможности заблокировать на чтение или запись весь сегмент или его часть. Если процесс заблокировал сегмент на запись, можно считать, что никакие процессы не будут записывать или читать данный сегмент в это же самое время. | |
Слабая блокировка | |
Данная форма блокировки сегментов не взаимодействует с подсистемой ввода-вывода [то есть с системными вызовами creat(2), open(2), read(2) и write(2)]. Контроль над сегментом устанавливается запросом на соответствующую блокировку, выдаваемым до операций ввода-вывода. Если соответствующие запросы делают все процессы, обрабатывающие файл, доступ к файлу будет управляться взаимодействием этих запросов. Слабая блокировка обеспечивает выполнение протокола блокировки сегментов для всех использующих ее процессов; она не означает дополнительных проверок доступности во время любого запроса на ввод-вывод. | |
Сильная блокировка | |
Данная форма блокировки сегментов взаимодействует с подсистемой ввода-вывода. Контроль блокировки сегментов осуществляется системными вызовами creat(2), open(2), read(2) и write(2). Если сегмент заблокирован, то доступ к нему со стороны других процессов ограничивается в соответствии с типом блокировки. Управление блокировкой по-прежнему должно осуществляться путем явной выдачи соответствующего запроса на нее перед операциями ввода-вывода, однако перед каждой операцией ввода-вывода системой будут делаться дополнительные проверки, чтобы обеспечить выполнение протокола блокировки сегментов. Сильная блокировка обеспечивает дополнительный контроль очередности обращений к данным за счет некоторой потери эффективности. |
Термы
Различные арифметические операции, применяемые к первичным выражениям, образуют более крупные синтаксические элементы, называемые термами. Все арифметические вычисления выполняются над вещественными числами. Терм имеет одну из следующих форм:
первичное_выражение
терм биноп терм
уноп терм
переменная_с_приращением
( терм )
Тестирование описания
Рассмотрим три способа тестирования описания терминала. Во-первых, можно установить значение переменной окружения $TERMINFO
равным имени каталога, содержащего это описание. Если программы выполняются на новом терминале так же, как они делали это на других терминалах, новое описание пригодно для использования.
Во-вторых, можно проверить правильность заполнения экрана при вставке строк, для чего закомментировать характеристику xon в описании терминала и редактировать при помощи vi(1) большой файл (не менее ста строк) при скорости передачи 9600 бод, если это возможно. Удалите около 15 строк в середине экрана и быстро нажмите несколько раз u (отменить операцию). Если содержимое экрана портится, то, вероятно, нужно увеличить время задержки. Примерно такой же тест можно использовать для проверки вставки символа.
В-третьих, можно воспользоваться командой tput(1). Эта команда выводит значение характеристики - числовое или текстовое, в зависимости от ее типа. Если характеристика булева, tput ничего не выводит, но возвращает код завершения (0, если истина, 1, если ложь). Общий формат команды tput таков:
tput [-T тип] характеристика
Опция -Tтип задает тип терминала, о котором вы запрашиваете информацию. Обычно эту опцию указывать не требуется, так как по умолчанию в качестве имени терминала берется значение переменной окружения $TERM. Поле характеристика указывает, значение какой характеристики надо извлечь из базы данных terminfo.
Следующая командная строка показывает, как выводится на экран последовательность для очистки экрана:
tput clear # экран терминала очищается
Следующая командная строка показывает, как отобразить на терминале количество колонок, имеющихся на его экране:
tput cols # здесь появится количество колонок
Более подробную информацию о команде tput и выводимых ей сообщениях см. в Справочнике пользователя, tput(1).
Тип
Язык C поддерживает несколько базовых типов объектов. Объекты, описанные как символы (char), занимают место достаточное, чтобы поместить в него любое значение из используемой кодировки символов. Если в переменную типа char заносится настоящий символ из данной кодировки, ее значение равно численному значению символа. В символьные переменные можно помещать и другие величины, но реализация этого машинно-зависима. В частности, char по умолчанию может быть знаковым или беззнаковым. В данной реализации символы по умолчанию знаковые.
Доступно до трех возможных размеров целых чисел, которые описываются при помощи ключевых слов short int, int и long int. Более длинные целые предоставляют не меньше места, чем более короткие, однако в некоторых реализациях короткие или длинные целые, либо и те и другие, эквивалентны обычным целым. Обычные целые имеют размер, наиболее естественный для архитектуры конкретного компьютера. Другие размеры предоставляются, чтобы удовлетворить специфические потребности. Размеры базовых типов для процессоров MC68020/30 показаны в таблице:
68020/30 | |
char | 8 бит |
int | 32 |
short | 16 |
long | 32 |
float | 32 |
double | 64 |
Свойства типов enum (см. Описания перечислимых типов в разделе ОПИСАНИЯ) тождественны свойствам некоторых целых типов. Реализация компилятора может использовать информацию о диапазоне значений, чтобы определить, сколько выделять памяти.
Беззнаковые целые, описанные как unsigned, подчиняются законам арифметики по модулю (2n), где n - число бит в их представлении.
Для вещественных чисел обычная (float) и двойная (double) точности могут в некоторых реализациях совпасть.
Поскольку объекты перечисленных выше типов можно с успехом интерпретировать как числа, будем далее относить их к арифметическим типам. char, int всех размеров (со знаком или без), а также enum будут собирательно называться целочисленными типами. Типы float и double будут собирательно называться вещественными типами.
Тип void задает пустое множество значений. Он используется для обозначения типа функций, не возвращающих значения.
Кроме базовых арифметических типов имеется потенциально бесконечный класс производных типов, построенных из базовых типов с помощью следующих конструкторов:
Массивы объектов большинства типов. Функции, возвращающие объекты данного типа. Указатели на объекты данного типа. Структуры, которые содержат в себе последовательность объектов различных типов. Объединения, которые могут содержать в себе один из нескольких объектов различных типов.
В общем случае перечисленные методы конструирования типов могут применяться рекурсивно.
Типы данных
Переменные (и поля) принимают числовые или текстовые значения в зависимости от контекста. Например, в присваивании
pop += $3
pop полагается числом, в то время как в присваивании
country = $1
country - это цепочка символов. В выражении
maxpop < $3
тип maxpop зависит от данных, которые содержатся в $3, что определяется во время выполнения программы.
В общем случае, каждая переменная (поле) является потенциально цепочкой символов или числом, либо одновременно и тем и другим. Если значение переменной устанавливается присваиванием
пер = выражение
то ее тип становится равным типу выражения. ("Присваивание" - это также и +=, ++, -= и т.д.) Арифметическое выражение имеет тип число; конкатенация цепочек имеет тип цепочка_символов.
В сравнениях, если оба операнда являются числами, они и сравниваются как числа. В противном случае, операнды, если требуется, преобразуются в цепочки символов и выполняется сравнение этих цепочек.
Следующие трюки позволяют преобразовать тип любого выражения:
выражение + 0
трактуется как число, а
выражение ""
- как цепочка символов. (Последнее выражение - это конкатенация с пустой цепочкой.)
ТИПЫ СООБЩЕНИЙ LINT'А
В последующих пунктах описываются основные группы сообщений, выдаваемых lint'ом.
ТРИ ВИДА ПРОГРАММИСТОВ
Как уже говорилось, мы будем различать три категории UNIX-программистов, используя в качестве критерия вид разрабатываемых ими программ. Заметим, что эта классификация не основана на квалификации или опыте программистов. Действительно, высококвалифицированные программисты, имеющие многолетний опыт, могут быть иногда отнесены к категории программистов-одиночек. С другой стороны, программисты с относительно небольшим опытом работы могут участвовать в коллективах, создающих прикладное или системное программное обеспечение.
Указание адреса загрузки секции
Связывание выходной секции с определенным адресом виртуальной памяти достигается посредством следующей разновидности предложения SECTIONS:
SECTIONS { имя_выходной_секции_1 адрес : { . . . } . . . }
Адрес, с которым выполняется связывание, записывается в виде константы языка C. Редактор связей выдает соответствующее сообщение об ошибке, если указанные выходная_секция и адрес не могут быть связаны (например, из-за конфигурации памяти или из-за перекрытия с другими секциями). В качестве адреса можно также использовать слово BIND, за которым должно следовать выражение в скобках. В этом выражении могут встречаться псевдофункции SIZEOF, ADDR и NEXT. Аргументом SIZEOF и ADDR должна быть ранее определенная секция, а аргументом NEXT - константа. Псевдофункция NEXT возвращает минимальный адрес конфигурируемой памяти, по которому еще ничего не размещено, кратный аргументу.
Выходные секции можно разместить в любом месте конфигурируемой памяти, где они помещаются, не перекрываясь с другими секциями.
Предложения SECTIONS могут подаваться на вход редактора связей в произвольном порядке, если только не используются псевдофункции SIZEOF и ADDR.
Редактор связей не обеспечивает автоматически ни четного размера секции, ни четного адреса ее начала, в отличие от ассемблера, который гарантирует, что длина секции в байтах будет делиться на четыре. Используя предложения управляющего языка, можно добиться того, что секция будет начинаться с нечетного адреса, однако делать это не рекомендуется. Если секция начинается с нечетного байта, то либо она будет неправильно выполняться, либо неправильно будет осуществляться доступ к данным, находящимся в этой секции. Если пользователь все же указал нечетную границу, ld(1) выдаст предупреждение.
Указание характеристик терминала
После того, как Вы уяснили характеристики терминала, их необходимо указать в его описании. Описание имеет вид последовательности разделенных запятыми полей, каждое из которых содержит принятое в terminfo сокращение названия характеристики и, в некоторых случаях, ее значение для данного терминала. Например, для указания способности терминала подавать звуковой сигнал используется сокращение bel. На большинстве терминалов инструкцией, передаваемой для подачи звукового сигнала, является CTRL+G. Таким образом, характеристика возможности подачи звукового сигнала изображается в описании терминала в виде bel=^G,.
Описание характеристики может занимать несколько строк. В отличие от первой строки, строки продолжения должны начинаться с символа табуляции или пробела. В описание можно включать комментарии, которые выделяются символом # в начале строки.
Полный список характеристик, которые можно указывать в описании терминала см. в terminfo(4). В этом списке приводятся название характеристики, сокращенное название, которое используется в базе данных, двухбуквенный код, соответствующий этой характеристике в устаревшей базе данных termcap, а также краткое описание характеристики. Сокращение, которое необходимо указать в описании терминала см. в колонке "Сокращение".
Примечание
Чтобы curses-программа могла работать с терминалом, его описание в базе данных terminfo должно включать, как минимум, характеристики, касающиеся перемещения курсора во всех четырех направлениях и очистки экрана.
Значение характеристики может задаваться клавишей (например, CTRL+G), числом, или цепочкой символов, определяющей последовательность операций, которые нужно выполнить, чтобы добиться необходимого эффекта. Для указания типа значения после названия характеристики в описании терминала могут использоваться некоторые специальные символы, а именно:
# | Указывает, что далее следует числовое значение характеристики. Например, количество столбцов может определяться в виде cols#80. |
= | Указывает, что значение характеристики является цепочкой символов. Эта цепочка в действительности может быть последовательностью команд, требующих от терминала выполнения определенных действий. В таких цепочках могут использоваться некоторые специальные символы, а именно: |
^ | Обозначает управляющий символ. Например, звуковой сигнал подается передачей CTRL+G, что записывается в виде ^G. |
\e или \E | В сопровождении еще каких-либо символов обозначают escape-последовательности. Например, \EC передается на терминал в виде ESC C. |
\n | Обозначает перевод строки. |
\l | Обозначает переход к новой строке. |
\r | Обозначает возврат каретки. |
\t | Обозначает горизонтальную табуляцию. |
\b | Обозначает возврат на символ назад. |
\f | Обозначает переход к новой странице. |
\s | Обозначает пробел. |
\nnn | Обозначает символ с восьмеричным кодом nnn, где nnn может иметь от одной до трех цифр. |
$< > | Эти символы используются для обозначения задержки в миллисекундах. Длительность задержки заключается в угловые скобки (<>). Она может быть целым или десятичным числом с одной цифрой после точки. После числа может следовать звездочка (*). Звездочка указывает, что длительность задержки пропорциональна количеству вовлеченных в операцию строк. Например, задержка в 20 миллисекунд на строку указывается в виде $<20*>. Более подробную информацию о задержке и символах-заполнителях см. в terminfo(4). |
.bel=^G,
Теперь, когда мы узнали, как описываются характеристики терминала, опишем их для myterm. Мы рассмотрим характеристики экрана, клавиатуры, основные и параметрические характеристики.
4.1.3.1. Основные характеристики
Следующие характеристики являются общими для большинства терминалов: звуковой сигнал, колонки и строки на экране, возможность надпечатывания символов. Пусть наш гипотетический терминал имеет эти и некоторые другие перечисленные ниже возможности. Заметьте, что в скобках после каждой характеристики следует ее сокращение для terminfo:
Автоматический переход в начало следующей строки, когда курсор достигает правой границы (am).
Способность подавать звуковой сигнал. Командой подачи сигнала является CTRL+G (bel).
Ширина экрана - 80 колонок. (cols).
Высота экрана - 30 строк. (lines).
Используется протокол xon/xoff (xon).
Объединив последовательность имен (см. раздел Название терминала) и описание упомянутых характеристик, мы получим следующее описание для базы данных terminfo:
mytermmytmminefancyterminalMy FANCY Terminal, am, bel=^G, cols#80, lines#30, xon,
4.1.3.2. Характеристики экрана
Эти характеристики управляют содержимым экрана. Пусть терминал myterm имеет перечисленные ниже характеристики. Опять-таки, в скобках после описания характеристики дается ее сокращение.
Возврату каретки соответствует CTRL+M (cr). Перемещению курсора на одну строку вверх соответствует CTRL+K (cuu1). Перемещению курсора на одну строку вниз соответствует CTRL+J (cud1). Перемещению курсора на одну строку влево соответствует CTRL+H (cub1). Перемещению курсора на одну строку вправо соответствует CTRL+L (cuf1). Переходу к инвертированному отображению соответствует ESC D (smso). Отмене инвертированного отображения соответствует ESC Z
(rmso). Последовательностью, очищающей до конца строки, является ESC K, и этот процесс должен сопровождаться задержкой в 3 мс. (el). Производится роллирование экрана терминала при получе- нии перевода строки в конце экрана (ind).
Описание терминала myterm, включающее эти характеристики, будет выглядеть так:
mytermmytmminefancyterminalMy FANCY Terminal, am, bel=^G, cols#80, lines#30, xon, cr=^M, cuu1=^K, cud1=^G, cub1=^H, cuf1=^L, smso=\ED, rmso=\EZ, el=\EK$<3>, ind=\n,
4.1.3.3. Характеристики клавиатуры
Эти характеристики представляют собой последовательности симво- лов, генерируемые при нажатии клавиш на клавиатуре терминала. Большинство терминалов имеет ряд специальных клавиш, по меньшей мере, стрелки и клавиши забоя. Пусть наш гипотетический терми- нал имеет несколько таких клавиш, которые генерируют следующие последовательности:
Забой - CTRL+H (kbs).
Стрелка вверх - ESC [A (kcuu1).
Стрелка вниз - ESC [B (kcud1).
Стрелка вправо - ESC [C (kcuf1).
Стрелка влево - ESC [D (kcub1).
Клавиша HOME - ESC [H (khome).
Добавим эту информацию к нашему описанию:
mytermmytmminefancyterminalMy FANCY Terminal, am, bel=^G, cols#80, lines#30, xon, cr=^M, cuu1=^K, cud1=^G, cub1=^H, cuf1=^L, smso=\ED, rmso=\EZ, el=\EK$<3>, ind=\n, kbs=^H, kcuu1=\E[A, kcud1=\E[B, kcuf1=\E[C, kcub1=\E[D, khome=\E[H,
4.1.3.4. Параметризованные цепочки
Характеристики, значения которых задаются параметризованными цепочками, могут иметь параметры. Например, таковы характеристики, описывающие позиционирование курсора на экране, или включение комбинации режимов отображения. Для позиционирования курсора используется характеристика cup, при этом передаются два параметра: номер строки и номер столбца. В случае таких характеристик, как cup или sgr (установка атрибутов), параметры передаются через входящую в terminfo подпрограмму tparm().
Аргументы параметрических характеристик обрабатываются согласно включенным в описания этих характеристик специальным последова- тельностям символов, которые начинаются с %. Эти последовательности похожи на те, которые используются в printf(3S). Кроме того, данные можно записать в стек и производить над ними операции, которые записываются в виде, близком к обратной польской записи. Как мы упомянули, cup имеет два аргумента: номер строки и номер столбца. sgr имеет девять аргументов, по одному на каждый из атрибутов отображения. Список аргументов sgr и порядок их следования, а также примеры см. в terminfo(4).
Пусть на нашем терминале курсор позиционируется последовательностью, состоящей из символов ESC [, за которыми следуют номера строки и столбца, разделенные точкой с запятой, а в конце идет H. Координаты задаются относительно 1, а не нуля. Так, чтобы передвинуть курсор в позицию (5, 18) относительно (0, 0), необ- ходимо передать последовательность \E[6;19H.
Целочисленные аргументы записываются в стек последовательностью %p, за которой следует номер аргумента, например, %p2 для записи в стек второго аргумента. %i - это краткая запись для увеличения первых двух аргументов. Для вывода числа из вершины стека в десятичном виде используется %d, как и в printf. Последовательность cup нашего терминала строится следующим образом:
cup= | Пояснения | \E[ | вывести ESC [ | %i | увеличить оба аргумента | %p1 | записать первый аргумент (номер строки) в стек | %d | вывести номер строки в десятичном виде | ; | вывести точку с запятой | %p2 | записать второй аргумент (номер столбца в стек) | %d | вывести номер столбца в десятичном виде | H | вывести завершающий цепочку символ H |
cup=\E[%i%p1%d;%p2%dH,
Добавив это к нашему описанию, получим:
mytermmytmminefancyterminalMy FANCY Terminal, am, bel=^G, cols#80, lines#30, xon, cr=^M, cuu1=^K, cud1=^G, cub1=^H, cuf1=^L, smso=\ED, rmso=\EZ, el=\EK$<3>, ind=\n, kbs=^H, kcuu1=\E[A, kcud1=\E[B, kcuf1=\E[C, kcub1=\E[D, khome=\E[H, cup=\E[%i%p1%d;%p2%dH,
Подробнее о параметризованных цепочках см. terminfo(4).
Указатели и целые
Выражение целочисленного типа можно сложить с указателем или вычесть из него; в таком случае оно преобразуется по правилам, которые будут изложены при обсуждении операции сложения. Два указателя на объекты одного типа можно вычесть; в этом случае результат преобразуется к целому типу по правилам, которые будут изложены при обсуждении операции вычитания.
Уловки анализа лексики
Некоторые решения, принимаемые лексическим анализатором, зависят от контекста. Например, лексический анализатор может игнорировать пробелы всюду, но не в текстах, взятых в кавычки, или имена могут включаться в таблицу в описаниях, но не в выражениях. Один из способов обработки таких ситуаций - использование глобального признака, устанавливаемого действиями и опрашиваемого лексическим анализатором. Например, спецификация
%{ int dflag; %} ... другие определения ... %% prog : decls stats ; decls : /* пусто */ { dflag = 1; } | decls declaration ; stats : /* пусто */ { dflag = 0; } | stats statement ; ... другие правила ...
описывает программу, состоящую из нуля или большего числа описаний, за которыми следует нуль или более операторов. Признак dflag равен 0, когда читаются операторы, и 1, когда читаются описания, за исключением первой лексемы в первом операторе. Эта лексема требуется алгоритму разбора для того, чтобы выяснить, что описания закончились и начались операторы.
Можно расценить такие попытки пройти с черного хода как вредные. Однако они позволяют сделать вещи, которые другими способами сделать было бы сложно, если не невозможно.
Унарные операции
Выражения с унарными операциями группируются справа налево.
унарное_выражение: * выражение & л_значение - выражение ! выражение ~ выражение ++ л_значение -- л_значение л_значение ++
л_значение --
( имя_типа ) выражение sizeof выражение sizeof ( имя_типа )
Унарная операция * вызывает "переход к значению"; операнд должен быть указателем, результатом является л_значение, обозначающее объект, на который указывает операнд. Если тип операнда - "указатель на ...", то тип результата - "...".
Результатом унарной операции & является указатель на объект, который обозначен л_значением. Если тип л_значения - "...", то тип результата - "указатель на ...".
Результат унарной операции - равен значению операнда, взятому с противоположным знаком. Выполняются обычные арифметические преобразования. Число, противоположное беззнаковому, вычисляется вычитанием операнда из (2n), где n - число бит в соответствующем знаковом типе.
Унарная операция + отсутствует.
Результат операции логического отрицания ! равен единице, если значение операнда равно нулю, и нулю, если значение операнда ненулевое. Тип результата - int. Операция применима к любому арифметическому типу и к указателям.
Операция ~ дает побитное отрицание своего операнда. Выполняются обычные арифметические преобразования. Тип операнда должен быть целочисленным.
При выполнении префиксной операции ++ объект, который обозначается операндом-л_значением, увеличивается на единицу. Результат равен новому значению операнда, однако он не является л_значением. Выражение ++x эквивалентно x += 1. (См. информацию о преобразованиях в пунктах Аддитивные операции и Операции присваивания).
Операнд-л_значение префиксной операции -- уменьшается на единицу по аналогии с префиксной операцией ++.
Если к л_значению применяется постфиксная операция ++, результатом является объект, обозначенный данным л_значением. После того как выдан результат, объект увеличивается в точности аналогично префиксной операции ++. Тип результата совпадает с типом л_значения.
Если к л_значению применяется постфиксная операция --, результатом является объект, обозначенный данным л_значением. После того, как выдан результат, объект уменьшается аналогично префиксной операции --. Тип результата совпадает с типом л_значения.
Выражение, которому предшествует заключенное в скобки имя типа, преобразуется к названному типу. Такая конструкция называется явным преобразованием. Имена типов обсуждаются в пункте Имена типов раздела ОПИСАНИЯ.
Операция sizeof выдает размер своего операнда в байтах. (Понятие байта в языке фигурирует только как единица измерения sizeof, однако во всех существующих реализациях байт - это фрагмент памяти, необходимый для хранения символа.) Если операция применяется к массиву, результат равен общему числу байт в массиве. Размер вычисляется по описаниям объектов. Семантически данное выражение является константой типа unsigned и может использоваться повсюду, где требуется константа. В основном описанная конструкция используется при взаимодействии с процедурами, подобными процедурам динамического выделения памяти и ввода/выдода.
Операция sizeof может также применяться к заключенному в скобки имени типа. В этом случае она дает размер в байтах объектов указанного типа.
Конструкция sizeof(тип) считается неделимой, поэтому выражение sizeof(тип)-2 - это то же самое, что и (sizeof(тип))-2.
Унарные термы
В терме вида
уноп терм
уноп может быть унарным + или -. Унарная операция применяется к числовому значению операнда терм, результат имеет соответствующее числовое значение, которое является предпочтительным, однако его можно интерпретировать и как текстовое. Унарные операции + и - имеют более высокий приоритет, чем *, / и %.
Управление очередями сообщений
В данном пункте детально описывается использование системного вызова msgct(2) и приводится программа-пример, позволяющая поупражняться со всеми его возможностями.
Управление окружением и получение информации о его состоянии
В некоторых обстоятельствах может потребоваться информация об окружении на компьютере, а также возможность управления окружением. Для этого имеется набор системных вызовов. Некоторые из них приведены в следующей таблице:
Имена функций | Назначение |
chdir | Изменение текущего каталога. |
chmod | Изменение режима доступа к файлу. |
chown | Изменение владельца и группы файла. |
getpid getpgrp getppid | Получение идентификаторов процесса. |
getuid geteuid getgid | Получение идентификаторов пользователя. |
ioctl | Управление устройствами. |
link unlink | Создание или удаление ссылки на файл. |
mount umount | Монтирование/размонтирование файловой системы. |
nice | Изменение приоритета процесса. |
stat fstat | Получение статуса файла. |
time | Получение системного времени. |
ulimit | Получение или изменение ограничений процесса. |
uname | Получение имени текущей UNIX-системы. |
Как можно заметить, многие из приведенных в таблице функций эквивалентны соответствующим командам shell'а. Действительно, необходимые действия по управлению окружением можно выполнить с помощью shell'а. Тем не менее, упомянутые функции могут использоваться в C-программах как часть интерфейса между ОС UNIX и языком C. Описание этих функций содержится в разделе 2 Справочника программиста.
Управление разделяемыми сегментами памяти
В данном пункте детально описывается использование системного вызова shmctl(2) и приводится программа-пример, позволяющая по- упражняться со всеми его возможностями.
Управление семафорами
В данном пункте детально описывается использование системного вызова semctl(2) и приводится программа-пример, позволяющая поупражняться со всеми его возможностями.
Управление строками
Командная строка вида
#line константа "имя_файла"
формирует управляющую информацию для других препроцессоров, участвующих в генерации C-программы. Компилятор при диагностике ошибок будет сообщать, что номер следующей строки во входном файле равен константе, и что текущий входной файл имеет указанное имя. Если имя_файла не указано, оно остается прежним.
Управление тупиковыми ситуациями
Система блокировки сегментов позволяет в некоторой степени выявлять и обходить тупиковые ситуации. Эти средства обеспечивают тот же уровень защиты, что и соответствующая стандарту /usr/group функция lockf(3C). Средства обнаружения тупиковых ситуаций работают лишь для процессов, блокирующих сегменты и файлы на одном компьютере. Тупиковые ситуации потенциально могут встречаться только тогда, когда операционная система собирается приостановить процесс, обратившийся к системе блокировки. Система осуществляет поиск циклов в графе процессов, зависимых по блокировке. Если появится цикл, это приведет к бесконечному ожиданию возможности заблокировать сегмент. При выявлении подобной ситуации попытка блокировки заканчивается неудачей и значение errno будет равно соответствующему номеру ошибки, связанному с тупиковой ситуацией. Если нужно, чтобы процесс не использовал системный способ обнаружения тупиковых ситуаций, следует при блокировке вместо F_GETLKW использовать F_GETLK.
Управление версиями
Данная возможность, известная как S_списки, используется для формирования информации, необходимой для управления версиями. Командная строка вида
#ident "версия"
помещает версию, представленную произвольной цепочкой символов, в секцию .comment объектного файла. Стоит помнить, что секция комментариев при выполнении в память не загружается. (Возможность в настоящий момент не реализована.)
Управляющие конструкции
awk позволяет использовать в действиях следующие управляющие конструкции:
if-else
while
for
и составной оператор, такой же, как в языке C.
Оператор if имеет следующий вид:
if ( условие ) оператор1 else оператор2
Условие вычисляется; если оно истинно, выполняется оператор1; в противном случае выполняется оператор2. Часть else является необязательной. Несколько операторов, заключенных в фигурные скобки, трактуются как единый оператор. В примере, приведенном в разделе Инициализация переменных, вычисление максимума населения можно перенести из шаблона в действие, если воспользоваться оператором if:
{ if (maxpop < $3) { maxpop = $3 country = $1 } } END { print country, maxpop }
Оператор while имеет вид:
while ( условие ) оператор
Условие вычисляется; если оно истинно, выполняется оператор. Условие вычисляется снова, и если оно истинно, опять выполняется оператор. Цикл повторяется до тех пор, пока условие истинно. Например, следующая программа распечатывает все входные поля, по одному на каждой строке:
{ i = 1 while (i <= NF) { print $i ++i } }
Другой пример - алгоритм Евклида нахождения наибольшего общего делителя $1 и $2:
{ printf "the greatest common divisor of " $1 "and ", $2, "is" while ($1 != $2) { if ($1 > $2) $1 -= $2 else $2 -= $1 } printf $1 "\n" }
Оператор for, аналогичный соответствующей конструкции языка C, имеет вид
for ( выражение1 ; условие ; выражение2 ) оператор
Так,
{ for (i = 1 ; i <= NF; i++) print $i }
- это еще одна awk-программа, распечатывающая все входные поля, по одному на каждой строке.
Имеется альтернативная форма оператора for, удобная для доступа к элементам ассоциативного массива в awk:
for ( i in массив ) оператор
Такая конструкция задает выполнение оператора для i, принимающего последовательно каждое значение индекса в массиве. Перебираются все индексы, однако порядок перебора не определен. Хаос гарантируется, если в теле цикла изменяется переменная i или создаются новые элементы массива. Цикл for в такой форме можно использовать, например, чтобы после завершения основной части программы напечатать все входные записи, предварив их порядковыми номерами:
{ x [NR] = $0 } END { for ( i in x) print i, x [i] }
Более содержательным является следующий пример - индексы-цепочки используются для вычисления суммарного населения стран по континентам:
BEGIN { FS="\t" } { population [$4] += $3 } END { for (i in population) print i, population [i] }
В данной программе тело цикла for выполняется для i, равного по очереди различным названиям континентов, до тех пор, пока все возможные значения i не будут исчерпаны (то есть пока все цепочки-названия не будут использованы). Отметим, однако, что порядок вычислений не определен. Например, такая программа может напечатать:
Africa 37 South America 142 Asia 1765 North America 243 Australia 14
Отметим, что условие в операторах if, while и for может включать:
Операции сравнения <, <=, >, >=, ==, !=.
Регулярные выражения, используемые вместе с операциями сопоставления ~ и !~.
Логические операции , && и !.
Скобки для группировки.
Оператор break (если он встречается внутри циклов while или for) приводит к немедленному выходу из цикла.
Оператор continue (если он встречается внутри циклов while или for) приводит к началу следующей итерации цикла.
Встретившийся в awk-программе оператор next заставляет awk немедленно перейти к следующей записи и возобновить просмотр шаблонов с начала программы. (Отметим различие между getline и next: getline не ведет к переходу к началу awk-программы.)
Если оператор exit встречается в секции BEGIN awk-программы, программа прекращает свое выполнение и выполняется секция END
(если она есть).
Если оператор exit встречается в основной секции awk-программы, прекращается выполнение основной секции. Последующие записи не читаются, выполняется секция END.
Оператор exit в секции END приводит к завершению выполнения программы.
Условная компиляция
Командная строка вида
#if ограниченное_константное_выражение
проверяет, отлично ли от нуля значение ограниченного_константного_выражения. (Константные выражения обсуждаются в соответствующем разделе; здесь же накладываются следующие дополнительные ограничения: константное выражение не может содержать sizeof, операций явного преобразования типа и констант перечислимых типов.)
В ограниченное_константное_выражение может также входить дополнительная унарная операция
defined идентификатор
или
defined (идентификатор)
результат которой равен единице, если идентификатор в данный момент определен препроцессором, и нулю - если не определен.
В ограниченных_константных_выражениях все определенные в данный момент идентификаторы заменяются на соответствующие им цепочки лексем (за исключением идентификаторов - операндов defined) в точности так же, как и в обычном тексте. Ограниченное_константное_выражение будет обрабатываться только после того, как выполнятся все подстановки. Во время этой обработки все неопределенные к текущему моменту идентификаторы заменяются нулями.
Командная строка вида
#ifdef идентификатор
проверяет, определен ли в данный момент препроцессором указанный идентификатор, то есть был ли он задан при помощи директивы #define. Эта командная строка эквивалентна строке
#if defined(идентификатор)
Командная строка вида
#ifndef идентификатор
проверяет, что указанный идентификатор в данный момент не определен. Эта командная строка эквивалентна строке
#if !defined(идентификатор)
За командной строкой, имеющей любую из трех указанных форм, следует произвольное число строк, среди которых может быть командная строка
#else
Затем должна идти командная строка
#endif
Если проверяемое условие истинно, все строки, стоящие между #else и #endif, игнорируются. Если проверяемое условие ложно, игнорируются строки, стоящие между строкой, содержащей условие, и строкой с #else, а если ее нет - строкой с #endif.
Еще одна форма командной строки препроцессора:
#elif ограниченное_константное_выражение
Между командными строками #if (#ifdef, #ifndef) и #else
(#endif) может быть указано любое число строк #elif. Эти конструкции могут быть вложены.
Условная операция
условное_выражение: выражение ? выражение : выражение
Условные выражения группируются справа налево. Первое выражение вычисляется; если его значение отлично от 0, результат будет равен значению второго выражения, в противном случае - значению третьего выражения. Если возможно, выполняются обычные арифметические преобразования, чтобы привести второй и третий операнды к одному типу. Если они являются структурами или объединениями одного типа, результат будет структурой или объединением. Если они являются указателями одного типа, результат будет иметь тот же тип; в противном случае один из операндов должен быть указателем, а другой - константой 0; результат же имеет тип указателя. Вычисляется только одно из выражений - второе или третье.
Условный оператор if
Имеется два варианта условного оператора:
if ( выражение ) оператор if ( выражение ) оператор else оператор
В обоих вариантах сначала вычисляется выражение; если результат оказывается ненулевым, выполняется первый оператор. Во втором варианте условного оператора, если значение выражения равно 0, выполняется второй оператор. Синтаксическая неоднозначность else разрешается путем присоединения else к самому внутреннему условному оператору, которому часть else еще не сопоставлена.