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

         

Какие функции целесообразно включать в библиотеку?


Включайте большие, часто используемые функции

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

Когда мы создавали разделяемую библиотеку языка C...
Функции из семейства printf(3S) используются часто, поэтому мы включили их в разделяемую библиотеку.

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

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

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

Не включайте функции, которым нужно много статических данных

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


Например, getgrent(3C) (см. Справочник программиста) редко применяется в стандартных утилитах ОС UNIX. Некоторые ее версии требуют более 1400 байт данных. Такие функции лучше не включать в разделяемую библиотеку. Можно импортировать глобальные, но не локальные (статические) данные.

Не включайте функции, затрудняющие сопровождение библиотеки

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

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

Лучше, чтобы библиотека была замкнутой. Например, printf(3S) обращается ко многим подпрограммам стандартной библиотеки ввода/вывода. Разделяемая библиотека, содержащая printf(3S), должна содержать и эти подпрограммы.

Примечание

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




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


В Руководстве программиста описываются инструменты, используемые для создания программ в окружении ОС UNIX. Заметим, что в руководстве описываются не все имеющиеся инструменты. Возможно, что неописанные инструменты окажутся даже более важными для Вас, тем не менее у нас нет возможности написать обо всем. Кратко перечислим, что именно будет описано, а что - нет.

Не описанные в данном руководстве инструменты:

Процедура входа в систему. Редакторы ОС UNIX. Организация файловой системы. Программирование на shell'е.

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

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

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



Каналы


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

echo /lib/*.a /usr/lib/*.a | wc -w

Эта команда выводит имена всех файлов из каталогов /lib и /usr/lib, оканчивающиеся на .a, и направляет результат команде wc(1), которая подсчитывает количество файлов.

Особенностью интерфейса между ОС UNIX и языком C является возможность создания каналов между Вашим процессом и выполняемой shell'ом командой, или между двумя взаимодействующими процесссами. В первом случае используется функция popen(3S), входящая в стандартный пакет ввода/вывода, а во втором - системный вызов pipe(2).

Функция popen() напоминает функцию system() тем, что она вызывает выполнение указанной команды shell'а. Отличие заключается в том, что при использовании функции popen() между вызвавшей ее программой и командой создается канал. С помощью функций пакета стандартного ввода/вывода можно выводить символы и цепочки символов в этот канал точно так же, как в stdout или именованные файлы. Канал остается открытым до тех пор, пока не будет вызвана функция pclose(). Типичным применением popen() является организация канала для выдачи информации на устройство печати командой lp(1):

#include <stdio.h>

main () { FILE *pptr; char *outstring;

if ((pptr = popen ("lp", "w")) != NULL) { for (;;) { . . . /* Организация вывода */ . . . (void) fprintf (pptr, "%s\n", outstring); . . . } . . . pclose (pptr); } . . . }



Класс памяти


Имеется четыре объявляемых класса памяти:

Автоматический. Статический. Внешний. Регистровый.

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



КЛАСС ПАМЯТИ И ТИП


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



Классификация системных вызовов и библиотечных функций


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

Доступ к файлам.

Действия с файлами и каталогами.

Управление процессами.

Управление окружением и получение информации о его состоянии.

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

Функции подкласса 3S предоставляют средства эффективного буферизованного ввода/вывода.

Функции подкласса 3C выполняют различные задачи. Их объединяет то, что они хранятся в библиотеке libc.a. Можно разделить эти функции на следующие группы:

Действия с цепочками символов.

Преобразование символов.

Классификация символов.

Управление окружением.

Управление памятью.

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

Имена функций Назначение
fclose fflush Закрыть поток или вытолкнуть его буфера.
ferror feof clearerr fileno  Опрос состояния потока.
fopen freopen fdopen  Открыть поток.
fread fwrite  Двоичный ввод/вывод.
fseek rewind ftell  Установка текущей позиции потока.
getc getchar fgetc getw  Считывание символа или слова из потока.
gets fgets  Считывание цепочки символов из потока.
popen pclose  Создание и ликвидация канала между программой и командой.
printf fprintf sprintf  Вывод с преобразованием по формату.
putc putchar fputc putw  Запись в поток символа или слова.
puts fputs  Запись в поток цепочки символов.
scanf fscanf sscanf  Ввод с преобразованием по формату.
setbuf setvbuf  Назначение буферов для потока.
system  Выполнение команды shell'а.
tmpfile  Создание временного файла.
tmpnam tempnam  Создание имен временных файлов.
ungetc  Вставка символа в поток ввода.
vprintf vfprintf vsprintf  Форматный вывод списка аргументов, заданного по правилам varargs.
<
При использовании всех функций, перечисленных в этой таблице, необходимо включить в программу оператор

#include <stdio.h>

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

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

Имена функций Назначение
strcat (s1, s2) Добавить копию s2 к концу s1.
strncat (s1, s2, n) Добавить n символов из s2 к концу s1.
strcmp (s1, s2) Сравнить две цепочки символов. Возвращает целое число, меньшее, большее или равное нулю в зависимости от того, предшествует ли s1 лексикографически s2, следует за ней или совпадает с ней.
strncmp (s1, s2, n) Сравнить n символов из двух цепочек.
strcpy (s1, s2) Копировать символы из s2 в s1 до тех пор, пока не будет скопирован нулевой байт (\0).
strncpy (s1, s2, n) Скопировать n символов из s2 в s1. Если s2 содержит более n символов, она будет усечена, если меньше n - в s1 будут добавлены нулевые байты.
strdup (s) Возвращает указатель на новую цепочку символов, являющуюся копией s.
strchr (s, c) Возвращает указатель на первое вхождение символа c в цепочку s, или NULL, если s не содержит c.
strrchr (s, c) Возвращает указатель на последнее вхождение символа c в цепочку s, или NULL, если s не содержит c.
strlen (s) Возвращает число символов в s до ближайшего нулевого байта.
strpbrk (s1, s2) Возвращает указатель на первое вхождение в s1 какого-либо символа из s2, либо NULL, если s1 не содержит ни одного символа из s2.
strspn (s1, s2) Возвращает длину начального фрагмента s1, состоящего только из символов, содержащихся в s2.
strcspn (s1, s2) Возвращает длину начального фрагмента s1, состоящего только из символов, не содержащихся в s2.
strtok (s1, s2) Находит включение символов из s2 в s1.
При использовании всех функций, перечисленных в этой таблице, необходимо включить в программу оператор

#include <string.h>

Включаемый файл <string.h> содержит внешние описания функций обработки цепочек символов.



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

Классификация символов

Имена функций Назначение
isalpha (c) c - буква?
isupper (c)  c - большая буква?
islower (c)  c - малая буква?
isdigit (c)   c - цифра: [0-9]?
isxdigit (c)  c - шестнадцатеричная цифра: [0-9], [A-F] или [a-f]?
isalnum (c)   c - алфавитно-цифровой символ (буква или цифра)?
isspace (c)   c - пробел, табуляция, возврат каретки, перевод строки, вертикальная табуляция или символ перехода к новой странице?
ispunct (c)   c - знак пунктуации (то есть не управляющий и не алфавитно-цифровой символ)?
isprint (c)   c - печатный символ? [Коды таких символов располагаются в диапазоне от 040 (пробел) до 0176 (тильда).]
isgraph (c)   c - печатный символ, но не пробел?
iscntrl (c)  c - управляющий символ (код меньше 040) или символ забоя (0177)?
isascii (c)   c является ASCII-символом (код меньше 0200)?
При использовании всех функций, перечисленных в этой таблице, необходимо включить в программу оператор

#include <ctype.h>

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

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

Имена функций Назначение
ecvt fcvt gcvt  Преобразование вещественного числа в цепочку символов.
l3tol ltol3  Преобразование 3-байтного целого числа в длинное целое и обратно.
strtod atof  Преобразование цепочки символов в вещественное число двойной точности.
strtol atol atoi  Преобразование цепочки символов в целое число.
conv(3C): Преобразование символов

toupper   Функция преобразования малой буквы в большую.
_toupper  Макрос преобразования малой буквы в большую.
tolower   Функция преобразования большой буквы в малую.
_tolower  Макрос преобразования большой буквы в малую.
toascii   Обнуляет у аргумента все биты, не являющиеся частью стандартного ASCII-символа; предназначен для достижения совместимости с другими системами.
При использовании макросов из последней таблицы необходимо включить в программу оператор

#include <ctype.h>




Классы памяти


В поле, указывающем класс памяти, хранится одно из перечисленных ниже значений. Именованные константы определены во включаемом файле <storclass.h>.

Обозначение Значение Смысл класса памяти
C_EFCN -1 Физический конец функции
C_NULL 0 -
C_AUTO 1 Автоматическая переменная
C_EXT 2 Внешнее имя
C_STAT 3 Статический
C_REG 4 Регистровая переменная
C_EXTDEF 5 Внешнее определение
C_LABEL 6 Метка
C_ULABEL 7 Неопределенная метка
C_MOS 8 Элемент структуры
C_ARG 9 Аргумент функции
C_STRTAG 10 Начало структуры
C_MOU 11 Элемент объединения
C_UNTAG 12 Начало объединения
C_TPDEF 13 Определение типа
C_USTATIC 14 Неинициализированный статический
C_ENTAG 15 Начало перечисления
C_MOE 16 Элемент перечисления
C_REGPARM 17 Регистровый параметр
C_FIELD 18 Битное поле

Обозначение Значение Смысл класса памяти
C_BLOCK 100 Начало или конец блока
C_FCN 101 Начало или конец функции
C_EOS 102 Конец структуры
C_FILE 103 Имя файла
C_LINE 104 Только для внутреннего пользования
C_ALIAS 105 Синоним
C_HIDDEN 106 Аналог статического; используется, чтобы избежать конфликта имен

Все перечисленные классы памяти, за исключением C_ALIAS и C_HIDDEN, генерируются при выполнении команд cc и as. Класс памяти C_HIDDEN никакими средствами системы UNIX не используется.

Некоторые из перечисленных классов памяти предназначены только для внутреннего использования C-компилятором. Таковы C_EFCN, C_EXTDEF, C_ULABEL, C_USTATIC и C_LINE.



Классы памяти специальных имен


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

 
Специальное имя Класс памяти
.file C_FILE
.bb C_BLOCK
.eb C_BLOCK
.bf C_FCN
.ef C_FCN
.target C_AUTO
.xfake C_STRTAG, C_UNTAG, C_ENTAG
.eos C_EOS
.text C_STAT
.data C_STAT
.bss C_STAT

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

Класс памяти Специальное имя
C_BLOCK .bb, .eb
C_FCN .bf, .ef
C_EOS .eos
C_FILE .file



Ключевые слова


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

BEGIN break log END close next FILENAME continue number FS exit print NF exp printf NR for split OFS getline sprintf ORS if sqrt OFMT in string RS index substr int while length


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

asm default float register switch auto do for return typedef break double goto short union case else if sizeof unsigned char enum int static void continue extern long struct while

Некоторые реализации резервируют также слово fortran.



КОГДА ПРОГРАММА НАПИСАНА


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

Объектный файл обычного формата содержит:

Заголовок файла.

Вспомогательный заголовок системы UNIX.

Таблицу заголовков секций.

Содержимое соответствующих секций.

Информацию о настройке ссылок.

Информацию о номерах строк.

Таблицу имен.

Таблицу цепочек.

Объектный файл состоит из секций. Обычно их по крайней мере две: .text и .data. В некоторых объектных файлах может быть также секция .bss, содержащая неинициализированные данные. Название этой секции происходит от мнемоники ассемблерной псевдокоманды bss (block started by symbol). Используя опции компиляторов, можно добиться включения в объектный файл дополнительной информации. Например, при компиляции с опцией -g добавляются номера строк и другая информация, необходимая для символьной отладки. Даже занимаясь программированием в течение многих лет, можно особенно не задумываться о содержимом и организации объектных файлов обычного формата, поэтому мы не будем сейчас останавливаться на этом вопросе. Обычному формату объектных файлов посвящена отдельная глава.



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


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

make [опции] [макроопределения] [целевые_файлы]

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

-i
  Игнорировать коды ошибок, возвращаемых запускаемыми программами. Данный режим устанавливается и тогда, когда в файле описаний указано фиктивное целевое имя .IGNORE.
  -s
  Молчи, скрывайся и таи... Не выводить командные строки перед их выполнением. Данный режим устанавливается и тогда, когда в файле описаний указано фиктивное целевое имя .SILENT.
  -r
  Не использовать встроенные правила.
  -n
  Пробный режим. Распечатывать команды, но не выполнять их. Выводятся даже команды, начинающиеся со знака @.
  -t
  "Массаж" целевых_файлов: время их создания устанавливается равным текущему времени; команды, предназначенные для получения целевых_файлов, не выполняются.
  -q
  Запрос. Утилита make возвращает нулевой или ненулевой код завершения в зависимости от того, нужно ли обновлять целевые_файлы (0, если не нужно).
  -p
  Вывести все макроопределения, а также все описания целевых_файлов.
  -k
  При ошибке прекращать выполнение команд, связанных с текущей зависимостью, но продолжать обработку других зависимостей, не связанных с текущей.
  -e
  Использовать в первую очередь переменные окружения, а не одноименные макросы make-файлов.
  -f
  Следующий аргумент считается именем файла описаний. Имя файла - обозначает стандартный ввод. Если опция -f не указана, читается файл с именем makefile, Makefile или s.[mM]akefile из текущего каталога. В первую очередь используется содержимое файлов описаний, а не встроенные правила.

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

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

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



КОМАНДНЫЕ СТРОКИ ПРЕПРОЦЕССОРА


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



Комбинации шаблонов


Шаблон может быть составлен из более простых шаблонов, объединенных операциями (ИЛИ), && (И), ! (ОТРИЦАНИЕ) и скобок. Например, шаблон

$2 >= 3000 && $3 >= 100

отбирает строки, относящиеся к странам, как площадь, так и население которых достаточно большие:

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

Шаблон

$4 == "Asia" $4 == "Africa"

отбирает строки, четвертое поле в которых совпадает либо с Asia, либо с Africa. Альтернативный способ спецификации послед- него шаблона:

$4 ~ /^(Asia|Africa)$/

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

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



Признаком комментария, по соглашению, является


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



Комментарий открывается символом # и заканчивается переводом строки. Пример:
# This line is a comment
Комментарий можно добавить в конце любой строки awk-программы.



Комментарий начинается символами /* и заканчивается символами */. Комментарии не могут быть вложенными.


Компиляция C-программ


Файлы с исходными текстами C-программ должны иметь расширение .c, например: mycode.c. Команда вызова компилятора имеет следующий вид:

cc mycode.c

При успешном исходе компиляции после нее будет выполнено редактирование связей и сгенерирован выполняемый файл a.out.

Для управления процессом компиляции и редактирования связей команда cc(1) имеет несколько опций. Перечислим наиболее употребительные из них:

-c
Подавляется фаза редактирования связей. В этом случае генерируется объектный файл (в нашем примере mycode.o), который позже может быть использован для редактирования связей с помощью команды cc без опции -c.
-g
  Генерируется дополнительная информация о переменных и операторах языка для символьной отладки. Если Вы планируете отлаживаться в рамках ИСРП, используйте вместо -g опцию -krot.
  Объектная программа подвергается оптимизации. В результате применения данной опции сокращается размер объектного файла и увеличивается скорость выполнения. Эта опция логически несовместима с опцией -g. Обычно она используется, когда программа уже отлажена.
-p
  Объектная программа допускает использование утилиты prof(1) для получения временного профиля выполнения. Удобно использовать эту опцию для выявления процедур, реализация которых требует совершенствования.
-o вых_файл
  Выполняемый файл, полученный после редактирования связей, будет иметь имя вых_файл, а не a.out.

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

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



Компиляция Фортран-программ


Файлы с исходными текстами Фортран-программ должны иметь расширение .for, для вызова компилятора служит команда svs(1). При необходимости воспользоваться отладчиком следует указывать опцию -krot.



Компиляция и редактирование связей


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

Для C-программ по команде cc(1) выполняется компиляция и редактирование связей.

Для выполнения тех же действий в случае Фортран-программ следует воспользоваться командой svs(1).

Настоятельно рекомендуется выполнять компиляцию и редактирование связей в рамках Интегрированной Среды Разработки Программ (ИСРП). ИСРП, в зависимости от расширения имени файла, вызовет нужный компилятор или редактор связей. Файлы с исходными текстами C-программ должны иметь расширение .c, Фортран-файлы - расширение .for, файлы, управляющие процессом редактирования внешних связей, - .bld. Выгода от использования ИСРП состоит в том, что не нужно помнить опции команд запуска компиляторов или библиотеки, необходимые для редактирования внешних связей.

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



Компиляция и выполнение программ, которые используют terminfo


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



Компиляция описания


Описания терминалов в базе данных terminfo переводятся из исходного в компилированный формат компилятором tic(1M).

Исходный файл с описанием обычно имеет расширение .ti. Например, описание myterm будет называться myterm.ti. Скомпилированное описание myterm обычно помещается в /usr/lib/terminfo/m/ myterm, так как название терминала начинается на m. myterm будет также иметь синонимы, например, /f/fancy. Если перед компиляцией установлено и помещено в окружение значение переменной $TERMINFO, скомпилированное описание будет помещено в каталог $TERMINFO. В этом случае все программы будут искать описание терминала сначала в каталоге $TERMINFO, а затем уже в подразумеваемом каталоге /usr/lib/terminfo. Общий формат командной строки для запуска компилятора tic таков:

tic [-v[число]] [-c] файл

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

tic -v myterm.ti # в ходе компиляции выдается трассировочная информация

Подробную информацию о компиляторе см. в Справочнике администратора, tic(1M).



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


Компилируйте такую программу, как обычную программу на языке C, командой cc(1) (см. Справочник программиста), которая вызывает компилятор языка C.

Подпрограммы обычно хранятся в библиотеке /usr/lib/libcurses.a. Чтобы редактор внешних связей просматривал эту библиотеку, в команде cc необходимо указать опцию -l.

Общий формат командной строки для компиляции программы, работающей с curses, таков:

cc файл.c -lcurses -o файл

Файл.c - это имя файла с исходным текстом программы; файл - имя выполнимого объектного файла.



Конец блока или функции


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

Байты Описание Имя Смысл
0-3 - - Не используются (заполнены нулями)
4-5 unsigned short x_lnno Номер строки исходного текста
6-17 - - Не используются (заполнены нулями)



Конец структуры


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

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



Конфигурация памяти


Для целей размещения программ и данных виртуальная память целевого компьютера подразделяется на конфигурируемую и неконфигурируемую. По умолчанию вся память считается конфигурируемой, то есть допускается ее использование редактором связей. В микропроцессорных приложениях, однако, области памяти, расположенные по разным адресам, зачастую неоднородны. Например, с нулевого адреса может располагаться ППЗУ размером 3K, а с адреса 20K - ПЗУ на 8K. Память в диапазоне от 3K до 20K-1 целесообразно сделать неконфигурируемой, то есть запретоть редактору связей ld(1) использовать ее. Ничто и никогда не может быть связано с неконфигурируемой памятью. Иными словами, указание того, что некоторая область памяти неконфигурируема, делает соответствующие адреса некорректными или несуществующими с точки зрения редактора связей. Конфигурацию памяти, отличную от подразумеваемой, необходимо специфицировать явно.

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



Конкатенация цепочек


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

{ x = "hello" x = x", world" print x }

печатает

hello, world

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

/^A/ { s = s " " $1 } END { print s }

напечатает

Australia Argentina Algeria

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



Конкатенация термов


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

1+2 3+4

имеет текстовое (и числовое) значение 37.



КОНСТАНТНЫЕ ВЫРАЖЕНИЯ


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

+ - * / % & | ^ << >> == != < > <= >= &&

унарными операциями

- ~

или тернарной операцией

? :

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

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



Константы


Имеется несколько видов констант. Каждая константа имеет тип; начальная информация о типах дается в разделе КЛАСС ПАМЯТИ И ТИП.



Контроль типов


lint, по сравнению с C-компилятором, выполняет более строгий контроль типов. Дополнительный контроль затрагивает четыре основные области:

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

Некоторые операции выполняют определенные преобразования, приводящие в соответствие типы операндов. Этим свойством обладают присваивания, условная операция (?:) и операции отношения. Аргумент оператора return и выражения, используемые для инициализации, допускают аналогичные преобразования. В этих конструкциях типы char, short, int, long, unsigned, float и double можно без ограничений смешивать. Указательные типы должны в точности согласовываться, конечно же, за исключением того, что массивы x

можно смешивать с указателями на x.

Правила контроля типов требуют, чтобы при работе со структурами левый операнд операции -> являлся указателем на структуру, ле- вый операнд операции . - структурой, а правый операнд этих операций - элементом, предусмотренным для структурного типа, соответствующего левому операнду. Аналогичные проверки выполняются при работе с объединениями.

Контролируется соответствие типов аргументов функций и возвращаемых значений. Без ограничений сопоставляются типы float и double, так же, как и типы char, short, int и unsigned. Аналогично, указатели могут сопоставляться с соответствующими массивами. Во всех остальных случаях типы фактических аргументов должны согласовываться с описаниями соответствующих им формальных.

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

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

/* NOSTRICT */

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



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


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

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

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



Лексемы


Все awk-программы составляются из лексем следующих восьми видов:

Числовые константы.

Текстовые константы.

Ключевые слова.

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

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

Лексемы для работы с записями и полями.

Комментарии.

Лексемы, используемые для группировки.



Лексемы для работы с записями и полями


$0 - это специальная переменная, чье значение совпадает со значением текущей входной записи. $1, $2 и т.д. - специальные переменные, чьи значения совпадают со значениями соответственно первого, второго и т.д. полей текущей входной записи. Ключевое слово NF (Number of Fields) обозначает специальную переменную, значение которой равно числу полей в текущей входной записи. Таким образом, значение $NF совпадает со значением последнего поля в текущей записи. Отметим, что нумерация полей в записи начинается с 1, а число полей может изменяться от записи к записи. В действиях, относящихся к шаблонам BEGIN и END, использование перечисленных лексем не имеет смысла, поскольку для этих действий не определена текущая входная запись.

Ключевое слово NR (Number of Records) обозначает специальную переменную, значение которой равно числу входных записей, прочитанных к данному моменту. Первая прочитанная входная запись имеет номер 1.



Лексемы, используемые для группировки


Обычно в awk'е лексемы отделяются друг от друга непустыми последовательностями пробелов, табуляций и переводов строк, а также другими пунктуационными символами, такими как запятые и точ- ки с запятой. В фигурные скобки, {...}, заключаются действия, в наклонные черты, /.../, - шаблоны, заданные регулярными выражениями, в двойные кавычки, "...", - текстовые константы.



Лексическая видимость


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

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

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

typedef float distance; . . . { int distance; . . .

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



ЛЕКСИЧЕСКИЕ СОГЛАШЕНИЯ


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

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



Лексический анализ


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

Процедуры синтаксического разбора и лексического анализа должны быть согласованы относительно номеров лексем. Номера может выбрать yacc или пользователь. В обоих случаях, чтобы дать возможность лексическому анализатору использовать символические обозначения номеров лексем, применяется механизм #define языка C. Например, предположим, что имя лексемы DIGIT определено в секции определений файла yacc-спецификаций. Чтобы возвратить требуемый номер лексемы, соответствующий фрагмент лексического анализатора может выглядеть так:

int yylex () { extern int yylval; int c; . . . c = getchar (); . . . switch (c) { . . . case '0': case '1': . . . case '9': yylval = c - '0'; return (DIGIT); . . . } . . . }

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

Такой механизм дает понятные, легко модифицируемые лексические анализаторы. Единственная неприятность, которую следует избегать, - это использование в грамматических правилах в качестве имен лексем слов, зарезервированных в языке C или в yacc'е. Например, использование имен лексем if или while почти наверняка приведет к серьезным трудностям при компиляции лексического анализатора. Имя лексемы error зарезервировано для обработки ошибок, не следует использовать его без нужды.

По умолчанию имена лексем выбирает yacc. В этом случае номер лексемы для литералов равен численному значению соответствующего символа в принятой кодировке символов. Остальные имена получают номера лексем, начиная с 257. Если утилита yacc вызывается с опцией -d, порождается файл с именем y.tab.h, который содержит операторы #define для лексем.


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

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

Весьма полезным инструментом для построения лексических анализаторов является lex(1). Работа лексических анализаторов, порожденных lex'ом, хорошо согласуется с процедурами синтаксического разбора, порожденными yacc'ом. Спецификации лексических анализаторов используют не грамматические правила, а регулярные выражения. lex удобно использовать для порождения довольно хитроумных лексических анализаторов, однако остаются отдельные языки (такие как ФОРТРАН), которые не опираются ни на какое теоретическое обоснование и лексические анализаторы для которых приходится мастерить вручную.




Левая рекурсия


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

name : name rest_of_rule ;

Правила, подобные

list : item | list ',' item ;

и

seq : item | seq item ;

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

Для праворекурсивных правил, таких как

seq : item | item seq ;

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

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

seq : /* пусто */ | item seq ;

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



Lex(1)


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



Линии на экране и прочая графика


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

Для работы с альтернативной кодировкой в curses-программе, необходимо передать подпрограмме waddch() (или подобной) последовательность символов, чьи имена начинаются с ACS_. Например, ACS_ULCORNER - это переменная для символа, изображающего левый верхний угол. Если терминал поддерживает этот символ, то в ACS_ULCORNER будет результат операции ИЛИ (|) соответствующего значения и битной маски A_ALTCHARSET. Если такого графического символа нет, используется символ из стандартного набора ASCII, аппроксимирующий требуемое изображение. Например, для аппроксимации ACS_HLINE, горизонтальной черты, используется - (знак минус). Если близкая аппроксимация невозможна, используется +, знак плюс. Все стандартные имена ACS_ и аппроксимирующие их символы перечислены в curses(3X).

Мы приведем часть текста программы, которая использует графические символы. В примере вызывается подпрограмма box(), входящая в пакет curses, для того, чтобы окружить меню на экране рамкой. По умолчанию box() использует графические символы, но по выбору пользователя может рисовать знаками и - [см. curses(3X)]. Если меню, изображенное в рамке, имеет продолжение вверх или вниз за пределы рамки, то для указания этого выводятся стрелки соответственно вверх или вниз (ACS_UARROW и ACS_DARROW).

box (menuwin, ACS_VLINE, ACS_HLINE); ... /* Вывод стрелок вверх/вниз */ wmove (menuwin, maxy, maxx - 5);

/* вывод стрелки вверх или горизонтальной черты */ if (moreabove) waddch(menuwin, ASC_UARROW); else waddch (menuwin, ACS_HLINE);

/* вывод стрелки вниз или горизонтальной черты */ if (morebelow) waddch (menuwin, ASC_DARROW); else waddch (menuwin, ACS_HLINE);

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

if ( ! (ACS_DARROW & A_ALTCHARSET)) ACS_DARROW = 'V';

Более подробную информацию см. в Справочнике программиста, curses(3X).



Lint(1)


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

Например, в результате выполнения команды

lint restate.c oppty.c pft.c rfe.c

получаем:

restate.c:

restate.c ============== (79) warning: main() returns random value to invocation environment oppty.c: pft.c: rfe.c:

============== function returns value which is always ignored printf

Для облегчения ориентации в тексте программ сообщения об ошибках содержат номера строк.



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


выражение_логического_и: выражение && выражение

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

Операнды не обязаны иметь один и тот же тип, но каждый из них должен иметь базовый тип либо быть указателем. Результат - всегда int.



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


выражение_логического_или: выражение выражение

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

Операнды не обязаны иметь один и тот же тип, но каждый из них должен иметь базовый тип либо быть указателем. Результат - всегда int.



M4(1)


m4(1) - это макропроцессор, который можно использовать как препроцессор для языка ассемблера и C.



Магическое число


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



Make-файлы в рамках SCCS


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

make

и имеются только файлы s.makefile или s.Makefile, make сначала выполнит команду get(1), а затем обработает и удалит файл.



Макроопределения


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

2 = xyz abc = -ll -ly -lm LIBES =

Последнее определение сопоставляет LIBES с пустой цепочкой. Макрос, нигде не определенный явным образом, имеет в качестве значения пустую цепочку. Напомним, однако, что некоторые макросы явно определяются во встроенных правилах утилиты make. (См. раздел ВСТРОЕННЫЕ ПРАВИЛА.)



Машинно-зависимое использование символов


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

char c;

if ((c = getchar ()) < 0) . . .

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

nonportable character comparison

Лучший выход - описать c как целую переменную, поскольку getchar() в действительности возвращает целочисленные значения.

Аналогичные трудности возникают в связи с битными полями. При присваивании константного значения битному полю последнее может оказаться слишком маленьким, чтобы вместить значение. Чаще всего это происходит из-за того, что некоторые машины рассматривают битные поля как знаковые величины. Поскольку естественно считать, что поле из двух бит, описанное как int, не может вместить значение 3, проблема разрешается, если описать битное поле как unsigned.



Массив


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

Байты Описание Имя Смысл
0-3 long int x_tagndx Номер основного элемента для массива
4-5 unsigned short x_lnno Номер строки с описанием массива
6-7 unsigned short x_size Размер массива
8-9 unsigned short x_dimen [0] Первое измерение
10-11 unsigned short x_dimen [1] Второе измерение
12-13 unsigned short x_dimen [2] Третье измерение
14-15 unsigned short x_dimen [3] Четвертое измерение
16-17 - - Не используются (заполнены нулями)



Массивы


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

x [NR] = $0

присваивает текущую входную строку NR-му элементу массива x. В принципе, если выполнить следующую awk-программу:

{ x [NR] = $0 } END { ... программа ... }

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

Программа

{ x [NR] = $1 }

(в случае обработки файла countries) порождает массив со следующим содержимым:

x [1] = "Russia" x [2] = "Canada" x [3] = "China" . . .

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

/Asia/ { pop ["Asia"] += $3 } /Africa/ { pop ["Africa"] += $3 } END { print "Asia=" pop ["Asia"] , "Africa=" pop ["Africa"] }

которая порождает результат

Asia=1765 Africa=37

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

area [$1] = $2

текстовое значение первого поля строки используется как индекс в массиве area.



Массивы, указатели и индексирование


Всякий раз, когда идентификатор типа массив встречается в выражении, он преобразуется в указатель на первый элемент массива. Из-за этого преобразования массивы не являются л_значениями. По определению, операция индексирования [] понимается таким образом, что E1[E2] эквивалентно *((E1)+(E2)). В соответствии с правилами преобразования, выполняющимися для операции +, если E1 - массив, а E2 - целое, то E1[E2] обозначает E2-й элемент массива E1. Поэтому, несмотря на свое несимметричное внешнее представление, индексирование является коммутативной операцией.

Соответствующее правило имеет место и в случае многомерных мас- сивов. Если E - n-мерный массив размера i*j*...*k, то E, встретившееся в выражении, преобразуется в указатель на (n-1)-мерный массив размера j*...*k. Если операцию * явно или неявно, как результат индексирования, применить к этому указателю, результатом будет указываемый им (n-1)-мерный массив, который сам немедленно преобразуется в указатель.

Например, рассмотрим конструкцию int x[3][5]. Здесь x - двумерный массив целых размера 3*5. Когда x встречается в выражении, он преобразуется в указатель на (первый из трех) пятиэлементный одномерный массив целых. В выражении x[i], эквивалентном *(x+i), x сначала преобразуется в указатель, как это описано выше, затем i преобразуется в соответствии с типом x - преобразование заключается в умножении i на размер указываемого объекта, а именно на размер пяти объектов целого типа. Результаты складываются и применяется операция *, что дает одномерный массив (из пяти целых), который в свою очередь преобразуется в указатель на первый из целых элементов. Если есть еще один индекс, выполняются те же действия; на этот раз результат будет целого типа.

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



Многократные использования и побочные эффекты


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

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

lint проверяет важный частный случай - манипуляции со скалярными переменными. Например, оператор

a [i] = b [i++];

вызовет следующее сообщение lint'а:

warning: i evaluation order undefined

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



Многомерные массивы


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

for (i = 1; i <= 10; i++) for (j = 1; j <= 10; j++) mult [i ',' j] = ...

создается массив, индексы в котором имеют вид i,j, то есть 1,1, 1,2 и так далее; тем самым моделируется двумерный массив.



Моделирование действий ошибка и успех


Действия разбора ошибка и успех можно моделировать при помощи макросов YYACCEPT и YYERROR. Макрос YYACCEPT заставляет yyparse() завершиться, возвратив значение 0; макрос YYERROR заставляет процедуру разбора вести себя так, как если бы текущий входной символ был ошибочным; вызывается yyerror() и выполняется нейтрализация ошибки. Эти механизмы можно использовать, чтобы моделировать алгоритмы разбора с многократными маркерами конца и контекстно-зависимым контролем синтаксиса.



Мультипликативные операции


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

мультипликативное_выражение: выражение * выражение выражение / выражение выражение % выражение

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

Бинарная операция % дает остаток от деления первого выражения на второе. Операнды должны быть целочисленными.

По общим правилам результат деления целых значений является целым. При делении положительных целых чисел округление происходит в направлении 0; если хотя бы один из операндов отрицателен, способ округления машинно-зависимо; впрочем, обычно остаток имеет тот же знак, что и делимое. Для любых целых значений a и b справедливо, что (a/b)*b+a%b равно a (если b не 0).



Начало блока или функции


Формат вспомогательных элементов для начала блока или функции изображен ниже:

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