Язык C Язык C СОДЕРЖАНИЕ 1. Введение 4 2. Лексические соглашения 5 2.1. Комментарии 5 2.2. Идентификаторы (Имена) 5 2.3. Ключевые слова 5 2.4. Константы 5 2.4.1. Целые константы 6 2.4.2. Длинные целые константы 6 2.4.3. Символьные константы 6 2.4.4. Вещественные константы 7 2.4.5. Перечислимые константы 7 2.5. Текстовые константы 7 2.6. Форма описания синтаксиса языка 7 3. Класс памяти и тип 8 3.1. Класс памяти 8 3.2. Тип 8 3.3. Объекты и л_значения 10 4. Преобразования типов в операциях 11 4.1. Символы и целые 11 4.2. Одинарная и двойная точность 11 4.3. Вещественные и целочисленные значения 11 4.4. Указатели и целые 11 4.5. Беззнаковые 12 4.6. Арифметические преобразования 12 4.7. Пустое значение 13 5. Выражения и операции 14 5.1. Первичные выражения 14 5.2. Унарные операции 16 5.3. Мультипликативные операции 18 5.4. Аддитивные операции 18 5.5. Операции сдвига 19 5.6. Операции отношения 19 5.7. Операции равенства 20 5.8. Побитная операция И 20 5.9. Побитная операция исключающее ИЛИ 20 5.10. Побитная операция ИЛИ 21 5.11. Логическая операция И 21 5.12. Логическая операция ИЛИ 21 5.13. Условная операция 21 5.14. Операции присваивания 22 5.15. Операция запятая 23 6. Описания 24 6.1. Спецификаторы класса памяти 24 6.2. Спецификаторы типа 25 6.3. Описатели 26 6.4. Смысл описателей 26 6.5. Описания структур и объединений 28 6.6. Описания перечислимых типов 31 6.7. Инициализация 32 6.8. Имена типов 34 6.9. Неявные описания 35 6.10. Определяемые типы 35 7. Операторы 37 7.1. Оператор-выражение 37 7.2. Составной оператор (блок) 37 7.3. Условный оператор if 37 7.4. Оператор цикла while 38 7.5. Оператор цикла do 38 7.6. Оператор цикла for 38 7.7. Оператор выбора switch 39 7.8. Оператор break 40 7.9. Оператор continue 40 7.10. Оператор возврата return 41 7.11. Оператор перехода goto 41 7.12. Оператор с меткой 41 7.13. Пустой оператор 41 8. Внешние определения 42 8.1. Внешние определения функций 42 8.2. Внешние определения данных 43 9. Правила видимости 44 9.1. Лексическая видимость 44 9.2. Видимость внешних объектов 45 10. Командные строки препроцессора 46 10.1. Замена лексем 46 10.2. Включение файлов 47 10.3. Условная компиляция 47 10.4. Управление строками 49 10.5. Управление версиями 49 11. Еще о типах 50 11.1. Структуры и объединения 50 11.2. Функции 50 11.3. Массивы, указатели и индексирование 51 11.4. Явные преобразования указателей 52 12. Константные выражения 53 13. Вопросы мобильности 54 14. Сводка синтаксиса 55 14.1. Выражения 55 14.2. Описания 56 14.3. Операторы 59 14.4. Внешние определения 59 14.5. Препроцессор 60 1. ВВЕДЕНИЕ Данная глава содержит краткое изложение лексических и синтакси- ческих правил языка программирования C. 2. ЛЕКСИЧЕСКИЕ СОГЛАШЕНИЯ Имеется шесть классов лексем: идентификаторы, ключевые слова, константы, текстовые константы, знаки операций и прочие разде- лители. Пробелы, табуляции, символы перевода строки и коммента- рии (собирательное название - символы-невидимки), как будет описано позже, игнорируются, если только не служат разделителя- ми лексем. Если входной поток разделен на лексемы вплоть до данного симво- ла, в качестве следующей выбирается цепочка символов максималь- ной длины, которую можно считать лексемой. 2.1. Комментарии Комментарий начинается символами /* и заканчивается символами */. Комментарии не могут быть вложенными. 2.2. Идентификаторы (имена) Идентификатор - это последовательность букв и цифр. Первый сим- вол должен быть буквой. Подчеркивание (_) считается буквой. Прописные и строчные буквы различаются. Ограничение на длину имени отсутствует. Реализации могут игнорировать различия в размерах букв внешних имен, ограничивать число значащих симво- лов как для внешних, так и для локальных имен. 2.3. Ключевые слова Следующие идентификаторы зарезервированы для использования в качестве ключевых слов и не могут использоваться в другом смыс ле: |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. 2.4. Константы Имеется несколько видов констант. Каждая константа имеет тип; начальная информация о типах дается в разделе КЛАСС ПАМЯТИ И ТИП. 2.4.1. Целые константы Целая константа, состоящая из последовательности цифр, считает- ся восьмеричной, если начинается с 0 (цифра нуль). Восьмеричная константа состоит только из цифр от 0 до 7. Последовательность цифр, которой предшествует 0x или 0X, трактуется как шестнадца- теричное число. Шестнадцатеричные цифры включают символы от a (или A) до f (или F) со значениями от 10 до 15. В остальных случаях целая константа считается десятичной. Если значение де- сятичной константы больше, чем максимальное для данной машины знаковое целое число, считается, что она имеет тип long; анало- гично считается, что восьмеричная или шестнадцатеричная конс- танта, превосходящая максимальное беззнаковое целое число, име- ет тип long. В остальных случаях целые константы имеют тип int. 2.4.2. Длинные целые константы Десятичная, восьмеричная или шестнадцатеричная целая константа, за которой следует символ l (или L), имеет тип long. Как следу- ет из дальнейшего обсуждения, для процессоров MC68020/30 значе- ния типов int и long неразличимы. 2.4.3. Символьные константы Символьная константа - это символ, заключенный в одинарные ка- вычки ', например 'x'. Значение символьной константы равно чис- ленному значению символа в принятой для данной системы кодиров ке. Некоторые неизображаемые символы, одинарную кавычку (') и обратную наклонную черту (\) можно представить в соответствии со следующей таблицей управляющих последовательностей: | +---------------------------+-----------+ | | перевод строки | \n | | | горизонтальная табуляция | \t | | | вертикальная табуляция | \v | | | забой | \b | | | возврат каретки | \r | | | переход к новой странице | \f | | | обратная наклонная черта | \\ | | | одинарная кавычка | \' | | | набор бит | \ddd | | +---------------------------+-----------+ Управляющая последовательность \ddd состоит из символа \, за которым следуют восьмеричные цифры (одна, две или три), задаю- щие значение требуемого символа. Специальный случай данной конструкции - \0 (дальше идут не цифры) - обозначает ASCII-сим- вол NUL. Если символ, следующий за \, не совпадает ни с одним из приведенных в таблице, действие не определено. Явный символ перевода строки в символьной константе недопустим. Тип символь ной константы - int. 2.4.4. Вещественные константы Вещественная константа состоит из целой части, десятичной точ- ки, дробной части, символа e или E, и целой экспоненты (быть может со знаком). Целая и дробная части являются последователь- ностями цифр. Целая часть или дробная часть (но не обе сразу) могут быть опущены. Десятичная точка или символ e с экспонентой (но не то и другое сразу) могут быть опущены. Вещественная константа всегда имеет тип double. 2.4.5. Перечислимые константы Имена, описанные как перечисляемые (см. Описания структур и объединений и Описания перечислимых типов), имеют тип int. 2.5. Текстовые константы Текстовая константа - это последовательность символов, ограни- ченная двойными кавычками: "...". Текстовая константа имеет тип "массив символов" и класс памяти static (см. КЛАСС ПАМЯТИ И ТИП) и инициализируется указанными символами. В конце каждой текстовой константы компилятор помещает нулевой байт (\0); бла годаря этому программы, просматривающие текстовую константу, могут обнаружить ее конец. Если требуется включить в текстовую константу символ двойной кавычки ("), перед ним надо поставить знак \; кроме того, могут использоваться те же управляющие сим- волы, что и в символьных константах. Знак \ и следующий непосредственно за ним символ перевода стро ки игнорируются. Все текстовые константы, даже внешне идентич- ные, располагаются в отдельных фрагментах памяти. 2.6. Форма описания синтаксиса языка Синтаксические категории обозначаются курсивом, а литералы и ключевые слова - жирным шрифтом. Альтернативные категории поме- щаются в разных строках. Необязательные компоненты помечаются знаком . Например, конструкция |{ выражение } обозначает необязательное выражение, заключенное в фигурные скобки. В конце главы приводится СВОДКА СИНТАКСИСА. 3. КЛАСС ПАМЯТИ И ТИП В языке C интерпретация идентификатора основана на двух атрибу тах: классе памяти и типе. Класс памяти определяет размещение и время жизни области памяти, сопоставленной идентификатору; тип определяет смысл значений, помещенных в эту область памяти. 3.1. Класс памяти Имеется четыре объявляемых класса памяти: Автоматический. Статический. Внешний. Регистровый. Автоматические переменные являются локальными для каждого обра- щения к блоку (см. Составной оператор (блок) в разделе ОПЕРАТО- РЫ) и перестают существовать при выходе из блока. Статические переменные локализованы в блоке, но сохраняют свои значения при повторном входе в блок даже в том случае, когда управление вы- ходило за его пределы. Внешние переменные существуют и сохраня- ют свои значения в течение выполнения всей программы и могут использоваться для взаимосвязи между любыми функциями, даже раздельно компилируемыми. Регистровые переменные заносятся (насколько это возможно) в быстрые регистры процессора; как и автоматические переменные, они являются локальными для каждого блока и исчезают при выходе из него. 3.2. Тип Язык 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, подчиняются законам арифметики по модулю (2 в степени n), где n - число бит в их представлении. Для вещественных чисел обычная (float) и двойная (double) точ ности могут в некоторых реализациях совпасть. Поскольку объекты перечисленных выше типов можно с успехом ин- терпретировать как числа, будем далее относить их к арифмети- ческим типам. char, int всех размеров (со знаком или без), а также enum будут собирательно называться целочисленными типами. Типы float и double будут собирательно называться вещественными типами. Тип void задает пустое множество значений. Он используется для обозначения типа функций, не возвращающих значения. Кроме базовых арифметических типов имеется потенциально беско- нечный класс производных типов, построенных из базовых типов с помощью следующих конструкторов: Массивы объектов большинства типов. Функции, возвращающие объекты данного типа. Указатели на объекты данного типа. Структуры, которые содержат в себе последовательность объектов различных типов. Объединения, которые могут содержать в себе один из нескольких объектов различных типов. В общем случае перечисленные методы конструирования типов могут применяться рекурсивно. 3.3. Объекты и л_значения Объект - это обрабатываемая область памяти. Л_значение - это выражение, обозначающее объект. Очевидным примером л_значения является идентификатор. Есть операции, чей результат - л_значе- ние. Например, если E - выражение типа указатель, то *E л_значение, обозначающее объект, на который указывает E. Термин "л_значение" возник потому, что в присваивании E1 = E2 левый операнд должен обозначать объект, то есть быть л_значением. Ни- же при обсуждении каждой операции указывается, требуются ли ей в качестве операндов л_значения и является ли л_значением ре- зультат. 4. ПРЕОБРАЗОВАНИЯ В ОПЕРАЦИЯХ Ряд операций может, в зависимости от операндов, вызывать преоб- разование значения операнда из одного типа в другой. В данном разделе описывается результат, ожидаемый от таких преобразова- ний. Сводка преобразований, необходимых в большинстве обычных операций, приводится в пункте Арифметические преобразования. При обсуждении отдельных операций сводка будет дополняться, там где это требуется. 4.1. Символы и целые Символ или короткое целое можно использовать повсюду, где можно использовать целое. Во всех этих случаях данное значение преоб- разуется в целое. Преобразование более короткого целого в более длинное сохраняет знак. Для процессоров MC68020/30 переменные типа char являются знаковыми. Когда более длинное целое преобразуется в более короткое или в char, оно усекается слева. Лишние биты просто отбрасываются. 4.2. Одинарная и двойная точность Все вещественные операции в языке C выполняются с двойной точ- ностью. Где бы в выражении не встретилось значение типа float, оно преобразуется в double. Если при преобразовании значения типа double в float (например, в присваивании) результат не мо жет быть представлен как float, он не определен. 4.3. Вещественные и целочисленные значения Преобразования вещественных значений в целочисленные до некото- рой степени машинно-зависимы. В частности, различается способ округления отрицательных чисел. Результат не определен, если он не помещается в отведенное место. Преобразования целочисленных значений к вещественному типу уст- роены более регулярным образом. Возможна лишь некоторая потеря точности при преобразовании к типу float, если длины мантиссы не хватает для представления числа. 4.4. Указатели и целые Выражение целочисленного типа можно сложить с указателем или вычесть из него; в таком случае оно преобразуется по правилам, которые будут изложены при обсуждении операции сложения. Два указателя на объекты одного типа можно вычесть; в этом случае результат преобразуется к целому типу по правилам, которые бу дут изложены при обсуждении операции вычитания. 4.5. Беззнаковые Если операндами операции являются беззнаковое и обычное целое, последнее преобразуется в беззнаковое (и результат операции по- лучается беззнаковым). Результат преобразования есть наименьшее беззнаковое целое, совпадающее по модулю (2 в степени размер_с лова) со знаковым целым. Если целые числа представляются в до- полнительном коде, это преобразование оказывается мысленным - ни один бит реально не изменяется. Когда беззнаковое короткое целое преобразуется в длинное, полу чается то же численное значение, то есть преобразование сводит- ся к дополнению слева нулями. 4.6. Арифметические преобразования Почти все операции похожи друг на друга способами преобразова- ния операндов и определения типа результата. Используемые пр этом правила будем называть правилами "обычных арифметических преобразований". Они состоят в следующем: Сначала все операнды типов char или short преобразуются к типу int, а все операнды типов unsigned char или un- signed short преобразуются к типу unsigned int. Затем, если один из операндов имеет тип double, другой преобразуется к типу double, который и объявляется ти- пом результата. Иначе, если один из операндов имеет тип unsigned long, другой преобразуется к типу unsigned long, который и объявляется типом результата. Иначе, если один операнд имеет тип long, а другой - un- signed int, они оба преобразуются к типу unsigned long, который и объявляется типом результата. Иначе, если один из операндов имеет тип long, другой преобразуется к типу long, который и объявляется типом результата. Иначе, если один из операндов имеет тип unsigned, дру- гой преобразуется к типу unsigned, который и объявляет- ся типом результата. Иначе оба операнда преобразуются к типу int, который и объявляется типом результата. 4.7. Пустое значение (Несуществующее) значение объекта типа void никаким образом не возможно использовать, к нему нельзя применить ни явное, ни не- явное преобразования. Так как выражение типа void обозначает несуществующее значение, такое выражение можно использовать только как оператор-выражение (см. пункт Оператор-выражение в разделе ОПЕРАТОРЫ) или как левый операнд в выражении запятая (см. пункт Операция запятая в разделе ВЫРАЖЕНИЯ И ОПЕРАЦИИ). Выражение можно преобразовать к типу void при помощи явной опе- рации преобразования (например, чтобы отбросить результат вызо- ва функции, используемого как оператор-выражение). 5. ВЫРАЖЕНИЯ И ОПЕРАЦИИ Основные пункты данного раздела, описывающие различные опера- ции, упорядочены по убыванию приоритета операций. Поэтому, нап- ример, выражения, являющиеся операндами + (см. Аддитивные опе рации), - это выражения, описанные в пунктах Первичные выраже ния, Унарные операции и Мультипликативные операции. Операции, описанные в рамках одного пункта, имеют одинаковый приоритет. В каждом пункте определяется левая или правая ассоциативность об- суждаемых операций. Приоритет и ассоциативность операций резю- мируются в разделе СВОДКА СИНТАКСИСА. В остальном порядок обработки выражений не определен. В част- ности, компилятор волен вычислять подвыражения в том порядке, который он считает наиболее эффективным, даже если подвыражения имеют побочные эффекты. Выражения, содержащие коммутативные и ассоциативные операции (*, +, &, |, ^), могут быть произвольным образом переупорядочены даже при наличии скобок. Чтобы навязать определенный порядок вычислений, нужно использовать явное прис- ваивание временным переменным. Реакция на переполнение и деление на нуль при вычислении выра жений не определяется. Большинство существующих реализаций иг норируют целые переполнения; интерпретация деления на 0 и нюан- сы вещественной арифметики различаются для разных компьютеров и обычно регулируются библиотечными функциями. 5.1. Первичные выражения Первичные выражения, содержащие ., ->, индексные конструкции и вызовы функций, группируются слева направо. |первичное_выражение: | идентификатор | константа | текстовая_константа | ( выражение ) | первичное_выражение [ выражение ] | первичное_выражение ( список_выражений ) | первичное_выражение . идентификатор | первичное_выражение -> идентификатор |список_выражений: | выражение | список_выражений , выражение Идентификатор является первичным выражением при условии, что он соответствующим образом описан (описания обсуждаются ниже). Тип идентификатора специфицируется в описании. Если тип идентифика- тора - "массив ...", то значение такого выражения есть указа- тель на первый объект в массиве, а тип выражения - "указатель на ...". Кроме того, идентификатор массива не является л_значе- нием. Аналогично, идентификатор, описанный как "функция, возв- ращающая ...", при использовании всюду кроме позиции имен функции в конструкции вызова, преобразуется к типу "указатель на функцию, возвращающую ...". Константа является первичным выражением. Тип константы может быть int, long или double в зависимости от ее внешнего вида. Символьные константы имеют тип int, вещественные константы име- ют тип double. Текстовая константа является первичным выражением. Первоначаль- но ее тип - "массив символов", но, следуя правилу, сформулиро- ванному выше для идентификаторов, он преобразуется в "указатель на символ"; результатом является указатель на первый символ в текстовой константе. (Исключение составляет определенный вид инициализаторов; см. пункт Инициализация в разделе ОПИСАНИЯ). Выражение в скобках является первичным выражением, его тип и значение совпадают с типом и значением вложенного выражения. Наличие скобок не влияет на то, будет ли выражение л_значением. Первичное выражение, за которым следует выражение в квадратных скобках, является первичным выражением. Его интуитивный смысл - индексирование. Обычно первое выражение имеет тип "указатель на ...", выражение-индекс - int, тип результата - "...". Выражение E1[E2] тождественно (по определению) *((E1)+(E2)). Все сведе- ния, необходимые для понимания данной записи, приводятся в этом пункте, а также в пунктах Унарные операции и Аддитивные опера- ции в связи с обсуждением идентификаторов, * и +, соответствен- но. Выводы делаются в пункте Массивы, указатели и индексирова- ние (раздел ЕЩЕ О ТИПАХ). Вызов функции есть первичное выражение, за которым следует зак- люченный в скобки список (возможно пустой) выражений, являющих- ся фактическими параметрами функции. Первичное выражение должно иметь тип "функция, возвращающая ...", а результат вызова функ ции имеет тип "...". Как поясняется ниже, не встретившийся до сих пор идентификатор, за которым следует левая скобка, неявно, исходя из контекста, описывается как функция, возвращающая це- лый результат. Все фактические параметры типа float преобразуются перед вызо- вом к типу double. Все параметры типов char или short - к int Имена массивов преобразуются в указатели. Никаких других преоб- разований автоматически не делается; в частности, компилятор не сравнивает типы фактических и формальных параметров. Если пре- образование необходимо, его следует задать явно (см. Унарные операции, а также пункт Имена типов в разделе ОПИСАНИЯ). При обработке вызова функции изготавливается копия каждого фак тического параметра. Таким образом, в языке C все параметры пе- редаются только по значению. Функция может изменить значения формальных параметров, но эти изменения не повлияют на значения параметров фактических. При передаче указателей следует учиты вать, что функция может изменить указуемый объект. Порядок об- работки параметров в языке не определяется; отметим, что в раз- ных компиляторах он разный. Допускаются рекурсивные вызовы лю бой функции. Первичное выражение, за которым следует точка, а затем иденти фикатор, также является первичным. Первое выражение должно быть структурой или объединением, а идентификатор должен именовать элемент структуры или объединения. Значение выражения - назван ный элемент структуры или объединения; это л_значение, если первое выражение - л_значение. Первичное выражение, за которым следует стрелка -> (составлен- ная из знаков - и >), а затем идентификатор, также является первичным. Первое выражение должно быть указателем на структуру или объединение, а идентификатор должен именовать элемент этой структуры или объединения. Значение выражения - л_значение, обозначающее названный элемент указуемой структуры или объеди- нения. Таким образом, выражение E1->MOS - то же самое, что и (*E1).MOS. Структуры и объединения обсуждаются в пункте Описа ния структур и объединений раздела ОПИСАНИЯ. 5.2. Унарные операции Выражения с унарными операциями группируются справа налево. |унарное_выражение: | * выражение | & л_значение | - выражение | ! выражение | ~ выражение | ++ л_значение | -- л_значение | л_значение ++ | л_значение -- | ( имя_типа ) выражение | sizeof выражение | sizeof ( имя_типа ) Унарная операция * вызывает "переход к значению"; операнд дол- жен быть указателем, результатом является л_значение, обознача ющее объект, на который указывает операнд. Если тип операнда - "указатель на ...", то тип результата - "...". Результатом унарной операции & является указатель на объект, который обозначен л_значением. Если тип л_значения - "...", то тип результата - "указатель на ...". Результат унарной операции - равен значению операнда, взятому с противоположным знаком. Выполняются обычные арифметические пре- образования. Число, противоположное беззнаковому, вычисляется вычитанием операнда из (2 в степени n), где n - число бит в со- ответствующем знаковом типе. Унарная операция + отсутствует. Результат операции логического отрицания ! равен единице, если значение операнда равно нулю, и нулю, если значение операнда ненулевое. Тип результата - int. Операция применима к любому арифметическому типу и к указателям. Операция ~ дает побитное отрицание своего операнда. Выполняются обычные арифметические преобразования. Тип операнда должен быть целочисленным. При выполнении префиксной операции ++ объект, который обознача ется операндом-л_значением, увеличивается на единицу. Результат равен новому значению операнда, однако он не является л_значе- нием. Выражение ++x эквивалентно x += 1. (См. информацию о пре- образованиях в пунктах Аддитивные операции и Операции присваи- вания). Операнд-л_значение префиксной операции -- уменьшается на едини- цу по аналогии с префиксной операцией ++. Если к л_значению применяется постфиксная операция ++, резуль татом является объект, обозначенный данным л_значением. После того как выдан результат, объект увеличивается в точности ана- логично префиксной операции ++. Тип результата совпадает с ти- пом л_значения. Если к л_значению применяется постфиксная операция --, резуль- татом является объект, обозначенный данным л_значением. После того, как выдан результат, объект уменьшается аналогично пре- фиксной операции --. Тип результата совпадает с типом л_значе- ния. Выражение, которому предшествует заключенное в скобки имя типа, преобразуется к названному типу. Такая конструкция называется явным преобразованием. Имена типов обсуждаются в пункте Имена типов раздела ОПИСАНИЯ. Операция sizeof выдает размер своего операнда в байтах. (Поня тие байта в языке фигурирует только как единица измерения size of, однако во всех существующих реализациях байт - это фрагмент памяти, необходимый для хранения символа.) Если операция приме- няется к массиву, результат равен общему числу байт в массиве. Размер вычисляется по описаниям объектов. Семантически данное выражение является константой типа unsigned и может использо- ваться повсюду, где требуется константа. В основном описанная конструкция используется при взаимодействии с процедурами, по- добными процедурам динамического выделения памяти и ввода/выдо да. Операция sizeof может также применяться к заключенному в скобки имени типа. В этом случае она дает размер в байтах объектов указанного типа. Конструкция sizeof(тип) считается неделимой, поэтому выражение sizeof(тип)-2 - это то же самое, что и (sizeof(тип))-2. 5.3. Мультипликативные операции Выражения с мультипликативными операциями *, / и % группируются слева направо. Выполняются обычные арифметические преобразова- ния. |мультипликативное_выражение: | выражение * выражение | выражение / выражение | выражение % выражение Бинарная операция * обозначает умножение. Операция * ассоциа тивна, и выражения, содержащие несколько сомножителей, могут быть переупорядочены компилятором. Бинарная операция / обозна чает деление. Бинарная операция % дает остаток от деления первого выражения на второе. Операнды должны быть целочисленными. По общим правилам результат деления целых значений является це- лым. При делении положительных целых чисел округление происхо- дит в направлении 0; если хотя бы один из операндов отрицате- лен, способ округления машинно-зависимо; впрочем, обычно оста- ток имеет тот же знак, что и делимое. Для любых целых значений a и b справедливо, что (a/b)*b+a%b равно a (если b не 0). 5.4. Аддитивные операции Выражения с аддитивными операциями + и - группируются слева направо. Выполняются обычные арифметические преобразования. Для каждой из операций допустимы и некоторые дополнительные типы операндов. |аддитивное_выражение: | выражение + выражение | выражение - выражение Результат операции + равен сумме ее операндов. Можно складывать указатель на объект в массиве и значение любого целочисленного типа. Последнее во всех случаях преобразуется в адресный сдвиг умножением на размер указуемого объекта. Результатом являетс указатель того же типа, что и первоначальный, указывающий на другой объект в том же массиве, соответствующим образом сдвину- тый относительно первоначального объекта. Так, если P - указа тель на объект в массиве, то выражение P+1 - это указатель на следующий объект в массиве. Никакие другие комбинации с исполь зованием указательных типов недопустимы. Операция + ассоциативна, и выражения, состоящие из нескольких слагаемых, могут быть переупорядочены компилятором. Результат операции - равен разности ее операндов. Выполняются обычные арифметические преобразования. Кроме того, значение лю- бого целочисленного типа можно вычесть из указателя, при этом выполняются те же преобразования, что и в случае сложения. При вычислении разности двух указателей на объекты одного типа результат преобразуется (делением на размер объекта данного ти- па) к типу int; полученное значение есть число объектов между указуемыми точками. В общем случае это преобразование дает нео- жиданный результат, если только указатели не соответствуют од- ному массиву, поскольку указатели, даже для объектов одного ти- па, не обязательно различаются на число, кратное размеру объек- та. 5.5. Операции сдвига Выражения с операциями сдвига << и >> группируются слева напр во. Обе выполняют обычные арифметические преобразования операн- дов, каждый из которых должен быть целочисленным. Затем правый операнд преобразуется к типу int; тип результата совпадает с типом левого операнда. Результат не определен, если правый опе- ранд отрицателен либо больше или равен размеру (в битах) левого операнда. |выражение_сдвига: | выражение << выражение | выражение >> выражение Значение выражения E1<<E2 есть E1 (рассматриваемое как набор бит), сдвинутое влево на E2 бит. Oсвободившиеся биты заполняют- ся нулями. Значение выражения E1>>E2 есть E1, сдвинутое вправо на E2 бит. Гарантируется, что правый сдвиг будет логическим (с заполнением нулями), если E1 беззнаковое, в ином случае сдвиг может оказаться арифметическим. 5.6. Операции отношения Выражения с операциями отношения группируются слева направо. |выражение_отношения: | выражение < выражение | выражение > выражение | выражение >= выражение | выражение <= выражение Операции < (меньше), > (больше), <= (меньше или равно) и >= (больше или равно) дают в результате 1, если указанное отноше- ние истинно, и 0, если ложно. Тип результата - int. Выполняются обычные арифметические преобразования. Можно сравнить два ука- зателя; результат зависит от относительного расположения в ад- ресном пространстве указуемых объектов. Программы, использующие сравнение указателей, мобильны только в случае сравнения указа- телей на объекты из одного массива. 5.7. Операции равенства |выражение_равенства: | выражение == выражение | выражение != выражение Операции == (равно) и != (не равно) в точности аналогичны опе- рациям отношения, за исключением того, что имеют меньший прио- ритет. (Так, a<b==c<d равно 1, когда отношения a<b и c<d однов- ременно истинны либо ложны.) Указатель можно сравнить с целым, только если целое - это конс- танта 0. Указатель, которому присвоено значение 0, наверняка не указывает ни на какой объект; он и оказывается равным 0. Общеп- ринято считать такой указатель пустым. 5.8. Побитная операция И |выражение_побитного_и: | выражение & выражение Операция & ассоциативна, и выражения, содержащие &, могут быть переупорядочены. Выполняются обычные арифметические преобразо- вания. Результат равен побитной функции И от операндов. Опера- ция применима только к операндам целочисленных типов. 5.9. Побитная операция исключающее ИЛИ |выражение_побитного_исключающего_или: | выражение ^ выражение Операция ^ ассоциативна, и выражения, содержащие ^, могут быть переупорядочены. Выполняются обычные арифметические преобразо вания. Результат равен побитной функции исключающего ИЛИ от операндов. Операция применима только к операндам целочисленных типов. 5.10. Побитная операция ИЛИ |выражение_побитного_или: | выражение | выражение Операция | ассоциативна, и выражения, содержащие |, могут быть переупорядочены. Выполняются обычные арифметические преобразо- вания. Результат равен побитной функции ИЛИ от операндов. Опе- рация применима только к операндам целочисленных типов. 5.11. Логическая операция И |выражение_логического_и: | выражение && выражение Выражения с операциями && группируются слева направо. Результат равен 1, если оба операнда ненулевые, и 0 в противном случае. В отличие от &, операция && гарантирует обработку выражений слева направо; более того, второй операнд не вычисляется, если значе- ние первого операнда равно 0. Операнды не обязаны иметь один и тот же тип, но каждый из ни должен иметь базовый тип либо быть указателем. Результат - всегда int. 5.12. Логическая операция ИЛИ |выражение_логического_или: | выражение || выражение Выражения с операциями || группируются слева направо. Результат равен 1, если хотя бы один из операндов ненулевой, и 0 в про тивном случае. В отличие от |, операция || гарантирует обработ- ку выражений слева направо; более того, второй операнд не вы- числяется, если значение первого операнда не равно 0. Операнды не обязаны иметь один и тот же тип, но каждый из них должен иметь базовый тип либо быть указателем. Результат - всегда int. 5.13. Условная операция |условное_выражение: | выражение ? выражение : выражение Условные выражения группируются справа налево. Первое выражение вычисляется; если его значение отлично от 0, результат будет равен значению второго выражения, в противном случае - значению третьего выражения. Если возможно, выполняются обычные арифме- тические преобразования, чтобы привести второй и третий операн- ды к одному типу. Если они являются структурами или объединени- ями одного типа, результат будет структурой или объединением. Если они являются указателями одного типа, результат будет иметь тот же тип; в противном случае один из операндов должен быть указателем, а другой - константой 0; результат же имее тип указателя. Вычисляется только одно из выражений - второе или третье. 5.14. Операции присваивания Выражения с операциями присваивания группируются справа налево. Операций присваивания в языке C несколько. Все они требуют, чтобы левый операнд был л_значением. Тип результата операции присваивания совпадает с типом левого операнда. Результат равен значению, которое содержится в левом операнде после выполнения присваивания. Две части составного знака операции присваивания являются отдельными лексемами. |выражение_присваивания: | л_значение = выражение | л_значение += выражение | л_значение -= выражение | л_значение *= выражение | л_значение /= выражение | л_значение %= выражение | л_значение >>= выражение | л_значение <<= выражение | л_значение &= выражение | л_значение ^= выражение | л_значение |= выражение При простом присваивании (=) значение выражения помещается в объект, обозначенный л_значением. Если оба операнда имеют ариф- метический тип, перед присваиванием правый операнд преобразует- ся к типу левого. Во-вторых, оба операнда могут быть структура- ми или объединениями одного типа. Наконец, если левый операнд является указателем, то и правый операнд, вообще говоря, должен быть указателем того же типа. Указателю, однако, может быть присвоена константа 0; этим гарантируется, что будет сформиро- ван пустой указатель, отличный от любого указателя на объект. Выражение вида E1 оп= E2 почти эквивалентно E1 = E1 оп (E2), однако E1 вычисляется только один раз. В выражениях += и -= ле- вый операнд может быть указателем, в этом случае (целочислен- ный) правый операнд преобразуется по правилам, описанным в пункте Аддитивные операции. Все правые операнды и все левые операнды-неуказатели должны иметь арифметический тип. 5.15. Операция запятая |выражение_запятая: | выражение , выражение Пара выражений, разделенных запятой, вычисляется слева направо, значение левого выражения отбрасывается. Тип и значение резуль- тата совпадают с типом и значением правого операнда. Выражения с операциями запятая группируются слева направо. В контекстах, где запятая имеет особое значение, например, в списках факти ческих параметров функций (см. Первичные выражения) и в списках инициализаторов (см. пункт Инициализация в разделе ОПИСАНИЯ), обсуждаемая здесь операция запятая может указываться только в скобках. Пример: |f (a, (t=3, t+2), c) Функция f вызывается с тремя параметрами, второй из которых суть 5. 6. ОПИСАНИЯ Способ интерпретации идентификаторов задается в C при помощи описаний. Описания имеют вид: |описание: | спецификаторы список_описателей_данных ; Описатели в списке_описателей_данных содержат описываемые иден- тификаторы. Спецификаторы задают тип и класс памяти идентифика- торов. |спецификаторы: | спецификатор_типа спецификаторы | спецификатор_класса спецификаторы Набор спецификаторов должен быть непротиворечивым (смысл этого замечания раскрывается ниже). 6.1. Спецификаторы класса памяти Спецификаторы класса памяти имеют вид: |спецификатор_класса: | auto | static | extern | register | typedef Спецификатор typedef причислен к спецификаторам_класса только для удобства описания синтаксиса; память он не резервирует (см. дополнительную информацию в пункте Определяемые типы). Смысл различных классов памяти обсуждается в пункте Имена типов. Описания auto, static и register служат также и определениями, поскольку они вызывают выделение соответствующего объема памя- ти. Если в некоторой функции при описании каких-либо идентифи- каторов используется спецификатор extern (см. ВНЕШНИЕ ОПРЕДЕЛЕ- НИЯ), где-то вне этой функции должно быть указано их внешнее определение. Описание register лучше всего понимать как описание auto вместе с указанием компилятору, что описанные переменные будут интен- сивно использоваться. В каждой функции эффективными будут лишь несколько первых из подобных описаний. Более того, в регистрах можно разместить переменные только некоторых типов. Еще одно ограничение, касающееся переменных, описанных с использованием класса памяти register, состоит в том, что к ним нельзя приме- нять операцию вычисления адреса &. Можно надеяться, что прог- раммы при разумном использовании описаний register будут нес- колько меньше и быстрее. В описании может быть задано не более одного спецификатора_к- ласса памяти. Если спецификатор_класса опущен, он считается равным auto внутри функции и extern - вне. Исключение: функции не могут быть автоматическими. 6.2. Спецификаторы типа Спецификаторы типа имеют вид: |спецификатор_типа: | спецификатор_базового_типа | спецификатор_структуры_или_объединения | имя_определяемого_типа | спецификатор_перечислимого_типа |спецификатор_базового_типа: | базовый_тип | базовый_тип спецификатор_базового_типа |базовый_тип: | char | short | int | long | unsigned | float | double | void Вместе с ключевым словом int можно указать long или short; смысл такой записи - тот же, что и в случае отсутствия int. Вместе с float можно указывать ключевое слово long; эта запись имеет тот же смысл, что и double. Ключевое слово unsigned может быть указано само по себе, либо вместе с int или с любым из его вариантов (long или short), а также с char. В остальных случаях в описании может быть задано не более одно- го спецификатора_типа. В частности, не допускается использова- ние дополнений long, short и unsigned вместе с именами_опреде- ляемых_типов. Если в описании спецификатор_типа опущен, он счи- тается равным int. Спецификаторы для структур, объединений и перечислимых типов обсуждаются в пунктах Описания структур и объединений и Описа- ния перечислимых типов. Определяемые типы обсуждаются в пункте с соответствующим названием. 6.3. Описатели Описание может включать список описателей данных, разделенных запятыми; описателям могут быть сопоставлены инициализаторы. |список_описателей_данных: | описатель_данных | описатель_данных , список_описателей_данных |описатель_данных: | описатель инициализатор Инициализаторы обсуждаются в пункте Инициализация. Спецификато- ры в описании указывают класс памяти и тип объектов, на которые ссылаются при помощи описателей. Описатели имеют следующий син таксис: |описатель: | идентификатор | ( описатель ) | * описатель | описатель ( ) | описатель [ константное_выражение ] Группировка в описателях происходит так же, как и в выражениях. 6.4. Смысл описателей Можно трактовать всякий описатель как следующее утверждение: если конструкция, имеющая такой же вид, что и описатель, встре- чается в выражении, она выдает объект с указанными типом и классом памяти. Каждый описатель содержит ровно один идентификатор; это именно тот идентификатор, который описывается. Если в качестве описа- теля указан просто идентификатор, он будет иметь тип, заданный спецификатором_типа в начале описания. Идентификатор в скобках или без них в качестве описателя имеет один и тот же тип, однако скобки могут влиять на интерпретацию сложных описателей (примеры приведены ниже). Рассмотрим описание: |T D1 где T - спецификатор_типа (такой как int и т.п.), D1 - описа- тель. Предположим, что это описание придает идентификатору тип "... T", где "..." пусто, если D1 является просто идентификато- ром (например, тип объекта x в "int x" есть int). Если же опи- сатель D1 имеет вид |*D то тип содержащегося в нем идентификатора - "... указатель на T". Если описатель D1 имеет вид |D ( ) то тип содержащегося в нем идентификатора - "... функция, возв- ращающая T". Если описатель D1 имеет вид |D [константное_выражение] или |D [ ] то тип содержащегося в нем идентификатора - "... массив T". В первом случае константное_выражение есть выражение, тип которо го суть int, а значение определяется во время компиляции и должно быть положительным. (Точное определение константных_вы- ражений дается в разделе КОНСТАНТНЫЕ ВЫРАЖЕНИЯ). Если к описа- телю приписано несколько спецификаций "массив ...", формируется многомерный массив; константное_выражение, специфицирующее гра- ницы массива, может быть опущено только для первой границы. Это бывает полезно, если массив является внешним и настоящее опре- деление, для которого и выделяется память, задано где-то в дру- гом месте. Первое константное выражение может быть опущено так- же, когда за описателем следует инициализатор. В таком случае размер массива вычисляется исходя из числа указанных инициали- зирующих элементов. Тип элемента массива может быть одним из базовых типов, указа- телем, структурой либо объединением, или тоже массивом (при формировании многомерного массива). Не все синтаксически допустимые конструкции в действительности разрешаются. Имеются следующие ограничения: функции не могут возвращать массивы и функции, однако могут возвращать указатели на них; не может быть массивов функций, однако могут быть мас- сивы указателей на функции. Аналогично, структура или объедине- ние не могут содержать функции, но могут содержать указатели на функции. Например, запись |int i, *ip, f (), *fip (), (*pfi) (); описывает i как целое, ip - как указатель на целое, f - как функцию, возвращающую целое, fip - как функцию, возвращающую указатель на целое, и pfi - как указатель на функцию, возвраща ющую целое. Особенно полезно сравнить два последних описателя. *fip() интерпретируется как *(fip()). Описание указывает (этого требует и конструкция такого же вида в выражении), что вызов функции fip, а затем применение к результату-указателю операции перехода к значению, дает целое. В описателе (*pfi)() (как и в соответствующем выражении) дополнительные скобки необходимы, поскольку они обозначают, что операция перехода к значению, примененная к указателю на функцию, дает функцию, которая, вы- полнившись, возвращает целый результат. Еще один пример |float fa [17], *afp [17]; описывает массив типа float и массив указателей на float. Нако- нец, конструкция |static int x3d [3] [5] [7]; описывает статический трехмерный массив целых размера 3*5*7. Подробно: x3d - это массив из трех компонентов; каждый компо- нент - массив из пяти массивов; каждый из последних - массив из семи целых чисел. Любое из подвыражений x3d, x3d[i], x3d[i][j], x3d[i][j][k] может оказаться приемлемым в некотором выражении. Первые три из них имеют тип "массив", тип последнего - int. 6.5. Описания структур и объединений Структура есть объект, состоящий из последовательности имено- ванных элементов. Любой элемент может иметь произвольный тип. Объединение есть объект, который может в каждый момент времени содержать один из нескольких элементов. Спецификаторы структур и объединений имеют одинаковый вид: |спецификатор_структуры_или_объединения: | struct { список_структ_описаний } | struct идентификатор { список_структ_описаний } | struct идентификатор | union { список_структ_описаний } | union идентификатор { список_структ_описаний } | union идентификатор Список_структ_описаний - это последовательность описаний эле- ментов структуры или объединения: |список_структ_описаний: | структ_описание | структ_описание список_структ_описаний |структ_описание: | спецификатор_типа список_структ_описателей ; |список_структ_описателей: | структ_описатель | структ_описатель , список_структ_описателей Чаще всего структ_описатель - это именно описатель элемента структуры или объединения. Кроме того, элемент структуры может содержать набор бит заданной длины. Такой элемент называют бит- ным полем; его длина, неотрицательное константное_выражение, отделяется от имени поля двоеточием. |структ_описатель: | описатель | описатель : константное_выражение | : константное_выражение В пределах структуры описываемые объекты имеют адреса, возрас- тающие соответственно порядку описаний. Начало каждого элемента структуры, не являющегося битным полем, выравнивается в соот- ветствии с его типом; поэтому в структуре могут быть и неимено- ванные промежутки. Битные поля упаковываются в стандартные для данной машины слова, хранящие объекты типа int. Поля не могут пересекать границ слов; отсюда следует, что поле не может быть длиннее слова. Если в текущем слове после размещения предыдущих полей осталось слишком мало места, очередное поле будет хра- ниться, начиная с границы следующего слова. Структ_описатель без описателя, состоящий только из двоеточия и константного_выражения, обозначает неименованное битное поле; его удобно использовать как заполнитель для согласования с ус- тановленным извне расположением данных. Как особый случай, бит- ное поле длины 0 специфицирует выравнивание следующего поля в соответствии с реализационно-зависимыми границами. Язык не накладывает ограничений на типы объектов, описываемых как битные поля. Следует учитывать, что поля типа int будут рассматриваться как беззнаковые, если их длина меньше слова. По этой причине настоятельно рекомендуется явно описывать подобные поля как unsigned, чтобы текст программы соответствовал реаль- ному положению вещей. Не бывает массивов битных полей, к ним нельзя применять операцию вычисления адреса, &, поэтому не бы вает и указателей на битные поля. Объединение можно представлять себе как структуру, все элементы которой располагаются с одним и тем же отступом 0 от начала и размер которой достаточен, чтобы вместить любой из элементов. В каждый момент времени в объединении может храниться единствен- ный элемент. Спецификатор_структуры_или_объединения во второй форме, то есть |struct идентификатор { список_структ_описаний } |union идентификатор { список_структ_описаний } описывает идентификатор, являющийся тегом структуры (тегом объ единения), специфицируемой списком_структ_описаний. Последующие описания могут затем использовать третью форму спецификатора: |struct идентификатор |union идентификатор Теги структур позволяют описывать структуры, ссылающиеся сами на себя. Они позволяют также задать длинную часть описания од нажды, а использовать многократно. Недопустимо описывать струк- туры или объединения, содержащие экземпляры самих себя; однако структура или объединение может содержать указатель на экземп- ляр данного типа. Третью форму спецификатора_структуры_или_объединения можно ис- пользовать перед описанием, дающим полную спецификацию структу- ры или объединения в случаях, когда не требуется знать размер структуры или объединения. Размер не важен в двух ситуациях: когда описывается указатель на структуру или объединение и ког- да при помощи конструкции typedef описывается имя, являющееся синонимом структуры или объединения. Тем самым появляется воз- можность описать, например, пару структур, содержащих указатели друг на друга. Имена элементов и теги не конфликтуют между собой и с именами обычных переменных. Одно и то же имя не может дважды использо- ваться в одной структуре, однако в пределах одной области види- мости в нескольких разных структурах можно использовать одина- ковые имена элементов. Следующая спецификация, задающая бинарное дерево, является нес- ложным, но важным примером описания структуры: |struct tnode { | char tword [20]; | int count; | struct tnode *left; | struct tnode *right; |}; Структура содержит массив из 20 символов, целое число и два указателя на аналогичные структуры. Затем с помощью конструкции |struct tnode s, *sp; можно описать объект s - структуру данного сорта и sp - указа- тель на структуру данного сорта. В соответствии с этими описа- ниями выражение |sp->count обозначает поле count структуры, на которую указывает sp; |s.left есть указатель на левое поддерево в структуре s; |s.right->tword [0] есть первый символ поля tword в правом поддереве s. 6.6. Описания перечислимых типов Переменные и константы перечислимых типов имеют целочисленный тип. |спецификатор_перечислимого_типа: | enum { список_перечисляемых } | enum идентификатор { список_перечисляемых } | enum идентификатор |список_перечисляемых: | перечисляемое | список_перечисляемых , перечисляемое |перечисляемое: | идентификатор | идентификатор = константное_выражение Идентификаторы в списке_перечисляемых описываются как константы и могут указываться повсюду, где ожидается константа. Если не указано ни одного перечисляемого с =, идентификаторы в списке_- перечисляемых принимают последовательные целые значения, начи- ная с 0; значения идентификаторов растут при просмотре описания слева направо. Перечисляемое с = придает идентификатору указан- ное значение; идущие за ним идентификаторы принимают последова- тельные целые значения, превосходящие данное. Все имена перечисляемых в одной области видимости должны быть различными и отличаться от имен обычных переменных. Роль идентификатора в спецификаторе_перечислимого_типа совер- шенно аналогична роли тега структуры в спецификаторе структуры; он именует конкретный перечислимый тип. Например, фрагмент программы |enum color {chartreuse, burgundy, claret=20, winedark}; | . . . |enum color *cp, col; | . . . |col = claret; |cp = &col; | . . . |if (*cp == burgundy) ... делает color тегом перечислимого типа, описывающего различные цвета, а затем объявляет cp как указатель на объект данного т па и col как объект данного типа. Множество возможных значений типа - {0, 1, 20, 21}. 6.7. Инициализация Описатель может специфицировать начальное значение описываемого идентификатора. Инициализатор, начинающийся со знака =, есть выражение или список выражений, заключенных в фигурные скобки. Список может содержать вложенные подсписки. |инициализатор: | = выражение | = { список_инициализаторов } | = { список_инициализаторов , } |список_инициализаторов: | выражение | список_инициализаторов , список_инициализаторов | { список_инициализаторов } | { список_инициализаторов , } Все выражения в инициализаторе статической или внешней перемен- ной должны быть константными (см. КОНСТАНТНЫЕ ВЫРАЖЕНИЯ), или выражениями, которые сводятся к адресу уже описанной перемен ной, возможно сдвинутому на значение константного выражения. Автоматические и регистровые переменные можно инициализировать произвольными выражениями, включающими константы и описанные перед этим переменные и функции. Явным образом не проинициализированные статические и внешние переменные имеют в начале работы программы нулевые значения. Явным образом не проинициализированные автоматические и регист- ровые переменные первоначально содержат мусор. Если инициализатор относится к скаляру (указателю или объекту арифметического типа), он должен включать одно выражение, воз- можно, заключенное в фигурные скобки. Начальное значение объек- та равно значению данного выражения; выполняются те же преобра- зования типа, что и при присваивании. Если описываемая переменная является составной (то есть струк- турой или массивом), инициализатор должен быть заключенным в скобки списком_инициализаторов компонентов. Элементы списка разделяются запятыми и записываются в том же порядке, что и компоненты структуры или массива. Если составная переменная со- держит составные компоненты, то для них рекурсивно применяется данное правило. Если в списке меньше элементов, чем компонентов в составной переменной, оставшиеся компоненты заполняются нуля ми. Инициализировать объединения и автоматические составные пе- ременные не разрешается. В некоторых случаях внутренние скобки { } могут быть опущены. Если элемент списка_инициализаторов представляет собой заклю- ченный в скобки подсписок, он инициализирует компоненты состав- ного подобъекта; не допускается, чтобы элементов в подсписке было больше, чем компонентов. Если же скобки опущены, то из списка берется столько элементов, сколько требуется для инициа- лизации очередного компонента (простого или составного); остав- шиеся элементы пойдут на инициализацию следующих компонентов составного объекта, частью которого является текущий компонент. Наконец, еще одно сокращение позволяет инициализировать массив символов текстовой константой. В этом случае последовательные символы текстовой константы становятся начальными значениями элементов массива. Например, конструкция |int x [] = {1, 3, 5}; описывает и инициализирует x как одномерный массив из трех эле- ментов, поскольку размер массива не специфицирован и имеется список из трех выражений. Запись |float y [4] [3] = { | {1, 3, 5}, | {2, 4, 6}, | {3, 5, 7}, |}; - это инициализация с полным набором скобок; 1, 3 и 5 инициали зируют первую строку массива y[0], а именно элементы y[0][0], y[0][1] и y[0][2]. Аналогично инициализируются две следующие строки. Инициализатор преждевременно заканчивается, поэтому y[3] инициализируется нулями. В точности тот же результат можно было бы получить при помощи инициализации |float y [4] [3] = { | 1, 3, 5, 2, 4, 6, 3, 5, 7, |}; Здесь список_инициализаторов записан без внутренних скобок, по- этому для инициализации первой строки массива будут взяты три первых элемента списка. Аналогично, следующие три берутся для y[1] и затем для y[2]. Запись |float y[4][3] = { | {1}, {2}, {3}, {4} |}; инициализирует первый столбец y (считающегося двумерным масси- вом) и заполняет все остальное нулями. Наконец, запись |char msg [] = "Syntax error on line %s\n"; описывает символьный массив, элементы которого инициализируются при помощи текстовой константы. В текстовую константу (ее длина совпадает с размером массива) входит и заключительный символ NUL, \0. 6.8. Имена типов В некоторых контекстах (например, при использовании операции явного преобразования типа) должны употребляться имена типов данных. По существу имя типа - это описание объекта данного ти- па, в котором опущено имя объекта. |имя_типа: | спецификатор_типа абстрактный_описатель |абстрактный_описатель: | пусто | ( абстрактный_описатель ) | * абстрактный_описатель | абстрактный_описатель ( ) | абстрактный_описатель [ константное_выражение ] Чтобы избежать неоднозначности, в конструкции |( абстрактный_описатель ) абстрактный_описатель должен быть непустым. Благодаря этому ог- раничению в абстрактном_описателе можно однозначно определить позицию, в которой следовало указать идентификатор, если бы рассматриваемая конструкция являлась обычным описателем. Имя_- типа в таком случае совпадает с типом гипотетического идентифи- катора. Например, конструкции |int |int * |int * [3] |int (*) [3] |int * () |int (*) () |int (* [3]) () именуют, соответственно, типы "целое", "указатель на целое", "массив длины три указателей на целое", "указатель на массив целых длины три", "функция, возвращающая указатель на целое", "указатель на функцию, возвращающую целое", "массив длины три указателей на функции, возвращающие целое". 6.9. Неявные описания Не всегда нужно явно специфицировать в описании как класс памя- ти, так и тип идентификаторов. Во внешних определениях и в опи саниях формальных параметров и элементов структуры можно уста- новить класс памяти исходя из контекста. В описании внутри функции, если класс памяти задан, а тип - нет, считается, что идентификатор имеет тип int; если задан только тип, считается, что идентификатор принадлежит классу памяти auto. Исключение из последнего правила составляют функции, поскольку не может быть автоматических функций. Если тип идентификатора - "функция, возвращающая ...", идентификатор неявно полагается принадлежа- щим классу памяти extern. Если в выражении встречается еще не описанный идентификатор, а за ним стоит открывающая скобка (, идентификатор, исходя из контекста, неявно описывается как "функция, возвращающая це- лое". 6.10. Определяемые типы Описания, содержащие "спецификатор класса памяти" typedef, не вызывают отведение памяти, но определяют идентификаторы, кото- рые можно в дальнейшем использовать наравне с ключевыми слова- ми, обозначающими базовые или производные типы. |имя_определяемого_типа: | идентификатор В пределах области видимости описания, содержащего typedef, каждый введенный с его помощью идентификатор становится синтак- сически эквивалентным ключевому слову, обозначающему тип, со- поставленный идентификатору по правилам, описанным в пункте Смысл описателей. Например, после описаний |typedef int MILES, *KLICKSP; |typedef struct { double re,im; } complex; конструкции |MILES distance; |extern KLICKSP metricp; |complex z, *zp; являются корректными описаниями; тип distance есть int, тип metricp - "указатель на int", тип z - специфицированная струк- тура. zp - указатель на такую структуру. Конструкция typedef не вводит совершенно новых типов, а только синонимы типов, которые в принципе можно специфицировать и дру- гим способом. В приведенном выше примере можно считать, что distance имеет в точности такой же тип, как и любой другой объ- ект int. 7. ОПЕРАТОРЫ Операторы, за исключением особо указанных, выполняются послед вательно. 7.1. Оператор-выражение Большинство операторов является операторами-выражениями, имею- щими вид |выражение ; Обычно операторы-выражения - это присваивания или вызовы функ- ций. 7.2. Составной оператор (блок) Для того, чтобы несколько операторов можно было использовать там, где ожидается один, предусмотрен составной_оператор (назы- ваемый также блоком): |составной_оператор: | { список_описаний список_операторов } |список_описаний: | описание | описание список_описаний |список_операторов: | оператор | оператор список_операторов Если какой-либо идентификатор из списка_описаний ранее был опи- сан, более внешнее описание теряет силу до конца блока, после чего оно снова начинает действовать. Все инициализации автоматических и регистровых переменных вы полняются при каждом входе в блок через его начало. В настоящий момент можно (правда, это порочная практика) передавать управ- ление внутрь блока; в таком случае инициализации не выполняют- ся. Инициализации статических переменных выполняются только один раз, когда начинается выполнение программы. Описания ex- tern внутри блока память не резервируют, поэтому инициализация таких переменных не допускается. 7.3. Условный оператор if Имеется два варианта условного_оператора: |if ( выражение ) оператор |if ( выражение ) оператор else оператор В обоих вариантах сначала вычисляется выражение; если результат оказывается ненулевым, выполняется первый оператор. Во втором варианте условного_оператора, если значение выражения равно 0, выполняется второй оператор. Синтаксическая неоднозначность el- se разрешается путем присоединения else к самому внутреннему условному_оператору, которому часть else еще не сопоставлена. 7.4. Оператор цилка while Цикл while имеет вид |while ( выражение ) оператор Выполнение оператора повторяется, пока значение выражения оста ется ненулевым. Проверка делается перед каждым выполнением опе- ратора. 7.5. Оператор цикла do Цикл do имеет вид |do оператор while ( выражение ); Выполнение оператора повторяется до тех пор, пока значение вы- ражения не станет равным 0. Проверка делается после каждого вы- полнения оператора. 7.6. Оператор цикла for Цикл for имеет вид |for ( выр_1 ; выр_2 ; выр_3 ) оператор Если не учитывать действие оператора continue, данный цикл эк- вивалентен следующей конструкции: |выр_1 ; |while ( выр_2 ) { | оператор | выр_3 ; |} Таким образом, выр_1 задает инициализацию цикла; выр_2 - усло- вие, проверяемое перед каждой итерацией; выполнение цикла прек- ращается, когда значение выр_2 становится равным 0. Выр_3 обыч- но задает приращение, выполняемое после каждой итерации. Любое из выражений (или даже все) может быть опущено. Если опу щено выражение выр_2, цикл становится бесконечным, то есть в приведенном выше развернутом представлении выр_2 следует заме- нить на 1; другие недостающие выражения достаточно просто выб- росить. 7.7. Оператор выбора switch Выполнение оператора выбора switch приводит к передаче управле- ния на один из нескольких вложенных операторов в зависимости от значения выражения. Оператор выбора имеет вид |switch ( выражение ) оператор В выражении выполняется обычное арифметическое преобразование типов, а результат должен иметь тип int. Оператор обычно бывает составным; любой из операторов, вложенных в него, может быть помечен одним или несколькими префиксами case вида |case константное_выражение : Типом константного_выражения должен быть int. Никакие два конс тантных_выражения в одном операторе switch не могут иметь оди- наковые значения. Точное определение константных_выражений со- держится в разделе КОНСТАНТНЫЕ ВЫРАЖЕНИЯ. Кроме того, можно задать не более одного оператора с префиксом вида |default : который принято помещать после всех операторов с префиксами ca- se. При выполнении оператора switch значение выражения вычисляется и сравнивается по очереди со значениями всех константных_выра- жений из префиксов case. При обнаружении совпадения управление передается на оператор, следующий за сопоставленным префиксом. В противном случае, если указан префикс default, управление пе- редается на оператор с этим префиксом; иначе ни один из опера- торов в конструкции switch не выполняется. Префиксы case и default не оказывают влияния на поток управле- ния; оно беспрепятственно продолжается через эти префиксы. Для выхода из конструкции выбора используется оператор break (см. следующий пункт). Поскольку обычно оператор, входящий в switch, является состав ным, в начале этого оператора могут быть указаны описания; од- нако инициализации автоматических и регистровых переменных не действуют. Приведем несложный пример полного оператора switch: |switch (c) { | case 'r': | rflag = TRUE; | break; | case 'w': | wflag = TRUE; | break; | default: | (void) fprintf (stderr, "Неизвестная опция\n"); | exit (2); |} 7.8. Оператор break Оператор |break ; приводит к завершению самого внутреннего оператора while, do, for или switch; управление передается на оператор, следующий за завершенным. 7.9. Оператор continue Оператор |continue ; передает управление на конец текущей итерации самого внутренне- го цикла while, do или for и вызывает начало следующей. Более точно, в каждом из операторов |while (...) { do { for (...) { | . . . . . . . . . |contin: ; contin: ; contin: ; |} } while (...); } действие |continue ; эквивалентно выполнению |goto contin; То, что следует за contin:, является пустым оператором (см. пункт Пустой оператор). 7.10. Оператор возврата return Функция возвращает управление вызвавшей ее функции при помощи оператора return, который имеет одну из двух форм: |return ; |return выражение ; В первом случае возвращаемое значение не определено; во втором случае вызывающей функции возвращается значение выражения. Если требуется, значение выражения преобразуется, как при присваива- нии, к типу текущей функции. Попадание управления на конец функции эквивалентно выполнению оператора return без возвращае- мого значения. 7.11. Оператор перехода goto Управление можно передавать безусловно при помощи оператора |goto идентификатор ; Идентификатор должен быть меткой (см. следующий пункт), распо- ложенной в текущей функции. 7.12. Оператор с меткой Любому оператору может предшествовать префикс вида: |идентификатор : служащий для того, чтобы описать идентификатор как метку. Единственное использование метки - обозначение места перехода для соответствующего оператора goto. Областью видимости метки является текущая функция за исключением всех вложенных блоков, в котором данный идентификатор описывается повторно (см. раздел ПРАВИЛА ВИДИМОСТИ). 7.13. Пустой оператор Пустой оператор имеет вид |; Пустой оператор удобно использовать для того, чтобы помещать метку непосредственно перед } в составном операторе или зада- вать пустое тело циклического оператора. 8. ВНЕШНИЕ ОПРЕДЕЛЕНИЯ C-программа состоит из последовательности внешних определений. Внешнее определение задает идентификатор, принадлежащий классу памяти extern (по умолчанию) либо static, и имеющий специфици- рованный тип. Спецификатор типа (см. пункт Спецификаторы типа в разделе ОПИСАНИЯ) может быть пустым, в таком случае тип счита- ется равным int. Внешние определения имеют тот же синтаксис, что и все описания; единственное исключение - только на этом уровне можно задать тело функции. 8.1. Внешние определения функций Определения функций имеют вид: |определение_функции: | спецификаторы описатель_функции тело_функции Единственно допустимые спецификаторы класса памяти в специфика- торах - extern или static; различие между ними указано в пункте Видимость внешних объектов раздела ПРАВИЛА ВИДИМОСТИ. Описа- тель_функции подобен описателю "функции, возвращающей ...", за исключением того, что он содержит список формальных параметров определяемой функции. |описатель_функции: | описатель ( список_параметров ) |список_параметров: | идентификатор | идентификатор , список_параметров Тело_функции имеет вид: |тело_функции: | список_описаний составной_оператор В списке_описаний можно описывать идентификаторы из списка_па- раметров и только их. Если тип идентификатора не задан, он счи- тается равным int. Единственный класс памяти, который можно специфицировать - register; если он указан, соответствующий фактический параметр будет, по возможности, скопирован в ре- гистр в начале выполнения функции. Простой пример законченного определения функции: |int max (a, b, c) |int a, b, c; |{ | int m; | | m = (a > b) ? a : b; | return ((m > c) ? m : c); |} Здесь int - спецификатор, max(a,b,c) - описатель_функции, int a, b, c; - список_описаний формальных параметров, { ... } - блок, содержащий операторы функции. C-программа преобразует все фактические параметры типа float в double, поэтому описания формальных параметров типа float восп- ринимаются как double. Аналогично, все формальные параметры ти пов char и short воспринимаются как int. Кроме того, поскольку ссылка на массив в любом контексте (в частности, в качестве фактического параметра) рассматривается как указатель на первы элемент массива, то описания формальных параметров вида "мас сив..." воспринимаются как "указатель на ...". 8.2. Внешние определения данных Внешнее определение данных имеет вид: |определение_данных: | описание Такие данные могут иметь класс памяти extern (выбирается по умолчанию) либо static, но не auto или register. 9. ПРАВИЛА ВИДИМОСТИ Не обязательно компилировать C-программу всю целиком, единовре- менно. Исходный текст программы можно поместить в несколько файлов, а предварительно откомпилированные процедуры загружать из библиотек. Функции программы взаимодействуют между собой как при помощи явных вызовов, так и манипулируя внешними данными. В языке C рассматриваются два рода видимости: во-первых, та, которую можно назвать лексической видимостью идентификатора, она по существу описывается областью программы, где идентифика- тор можно использовать, не получая диагностики "неопределенн идентификатор"; и во-вторых, видимость, связанная с внешними идентификаторами и характеризующаяся правилом, устанавливающим, в каких случаях одинаковые внешние идентификаторы обозначают один и тот же объект. 9.1. Лексическая видимость Область лексической видимости идентификаторов, заданных во внешних определениях, действует, начиная с места определения до конца исходного файла, в котором они встретились. Область лек- сической видимости идентификаторов, являющихся формальными па- раметрами функции, есть тело данной функции. Область лексичес- кой видимости идентификаторов, описанных при входе в блок, дей- ствует до его конца. Область лексической видимости меток - это целиком вся функция, в которой они встретились. Однако во всех случаях, когда идентификатор явно описан в нача- ле внутреннего блока (это может быть и блок, являющийся телом функции), любое более раннее описание того же идентификатора теряет силу до конца блока. Напомним также (см. пункт Описания структур и объединений и Описания перечислимых типов в разделе ОПИСАНИЯ), что теги, идентификаторы, сопоставленные простым переменным, и идентифи- каторы, сопоставленные элементам структур или объединений, об- разуют три различных непересекающихся класса. Все три класса идентификаторов подчиняются одним и тем же правилам видимости. Имена определяемых типов принадлежат классу обычных идентифика- торов. Они могут быть описаны повторно во внутренних блоках с явным указанием типа: |typedef float distance; | . . . |{ | int distance; | . . . Во втором описании должно быть указано int, поскольку иначе он будет воспринято как описание без описателей, состоящее из од- ного спецификатора типа distance. 9.2. Видимость внешних объектов Если функция обращается к идентификатору, описанному как ex- tern, то в каком-нибудь из файлов (либо библиотек), составляю- щих полную программу, должно быть по крайней мере одно внешнее определение данного идентификатора. Все функции некоторой прог- раммы, обращающиеся к одному и тому же внешнему идентификатору, ссылаются тем самым на один объект; поэтому нужно следить, что бы тип и размер, специфицированные в определении, были совмес- тимы с теми, которые задаются в функциях, обращающихся к данно- му объекту. Недопустимо, чтобы совокупность файлов и библиотек, составляю- щих программу, содержала более одной явной инициализации како- го-либо внешнего идентификатора. Однако можно иметь более одно- го определения любого внешнего идентификатора, не являющегося функцией. Явное использование ключевого слова extern не изменя- ет смысла внешнего описания. В ограниченном операционном окружении использование класса па- мяти extern имеет дополнительный смысл. В таком окружении явное появление ключевого слова extern в описаниях внешних объектов данных, не содержаших инициализаторов, обозначает, что память под эти объекты выделяется не здесь, а где-то в другом месте - либо в данном файле, либо в другом. Требуется, чтобы в програм- ме, составленной из многих файлов и библиотек, было ровно одно определение каждого внешнего идентификатора, не содержащее tern. Идентификаторы, описанные на верхнем уровне внешних определений как static, невидимы в других файлах. Функции могут быть описа- ны как static. 10. КОМАНДНЫЕ СТРОКИ ПРЕПРОЦЕССОРА Система компиляции C включает в себя препроцессор, обладающий возможностями макроподстановок, условной компиляции и включения указанных файлов. Взаимодействие с препроцессором осуществляет ся при помощи строк, начинающихся знаком #. Между # и директи вой препроцессора может быть произвольное число пробелов и г ризонтальных табуляций, но ничего больше (в частности, нельз указывать комментарии). Эти строки имеют синтаксис, независимый от синтаксиса всего остального языка; они могут встретиться в любом месте и действуют (независимо от областей видимости) до конца исходного файла. 10.1. Замена лексем Командная строка вида |#define идентификатор цепочка_лексем заставляет препроцессор заменить последующие вхождения иденти- фикатора на указанную цепочку_лексем. Знаки точки с запятой внутри или в конце цепочки_лексем являются частью цепочки. Строка вида |#define идентификатор(идентификатор,...) цепочка_лексем где символ ( идет вплотную за первым идентификатором, является определением макроса с параметрами. Допускается произвольное число формальных параметров, в том числе ни одного. Последующие вхождения первого идентификатора, за которым следует открываю щая скобка (, далее последовательность лексем, разделенных за пятыми, и закрывающая скобка ), заменяются цепочкой_лексем из определения, при этом каждое вхождение идентификатора, упомяну- того в списке формальных параметров, в цепочке_лексем заменяет- ся соответствующим фактическим параметром. Фактические параметры - это последовательности лексем, разде- ленные запятыми; однако запятые в текстовых константах или внутри вложенной пары скобок параметры не разделяют. Число фор- мальных и фактических параметров должно быть одинаковым. Преп- роцессор просматривает текстовые и символьные константы в це- почке_лексем из определения и выделяет в них формальные пара- метры, однако вне цепочки_лексем подобные константы на предмет выполнения подстановок не просматриваются. После подстановки цепочки_лексем результирующий текст вновь просматривается в поисках других вхождений идентификаторов, оп- ределенных директивами #define. Длинное определение может быть продолжено на следующей строке, для чего в конце продолжаемой строки нужно поместить знак \. Описанная возможность особенно полезна для наделения констант мнемоничными именами, как в следующем примере: |#define TABSIZE 100 | |int table [TABSIZE]; Командная строка вида |#undef идентификатор заставляет препроцессор отбросить определение идентификатора (если таковое было). Если идентификатор в последующей директиве #define определяется повторно, а директивы #undef между определениями нет, то две цепочки_лексем сравниваются текстуально. Если они не совпадают (все символы-"невидимки" в данном контексте эквивалентны), счи- тается, что идентификатор переопределяется. Об этом выдается предупреждающее сообщение. 10.2. Включение файлов Командная строка вида |#include "имя_файла" вызывает замену данной строки на содержимое файла с указанным именем. Названный файл ищется сначала в каталоге файла, содер- жащего #include, затем в последовательности явным образом спе- цифицированных или стандартных каталогов. Напротив, строка вида |#include <имя_файла> вызывает поиск только в последовательности специфицированных или стандартных каталогов, но не в каталоге файла, содержащего #include. (Способ явной спецификации каталогов поиска формули- руется вне языка. См. описание способа спецификации дополни- тельных библиотек в статье cpp(1) Справочника пользователя). Включаемый файл также может содержать директивы #include. 10.3. Условная компиляция Командная строка вида |#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. Эти конст- рукции могут быть вложены. 10.4. Управление строками Командная строка вида |#line константа "имя_файла" формирует управляющую информацию для других препроцессоров, участвующих в генерации C-программы. Компилятор при диагностике ошибок будет сообщать, что номер следующей строки во входном файле равен константе, и что текущий входной файл имеет указан- ное имя. Если имя_файла не указано, оно остается прежним. 10.5. Управление версиями Данная возможность, известная как S_списки, используется для формирования информации, необходимой для управления версиями. Командная строка вида |#ident "версия" помещает версию, представленную произвольной цепочкой символов, в секцию .comment объектного файла. Стоит помнить, что секция комментариев при выполнении в память не загружается. (Возмож- ность в настоящий момент не реализована.) 11. ЕЩЕ О ТИПАХ Этот раздел содержит обзор операций, которые можно выполнять над объектами определенных типов. 11.1. Структуры и объединения Структуры и объединения можно присваивать, передавать в качест- ве параметров функциям и возвращать как результаты функций. Другие вполне осмысленные операции, такие как проверка равенст ва и преобразование типа структуры, не реализованы. При ссылке на элемент структуры или объединения имя справа от -> или . должно специфицировать компонент составного объекта, поименованного или указанного выражением, стоящим слева. Вообще говоря, с элементами объединения нельзя работать до тех пор, пока объединению не присвоено значение с использованием именно этого элемента. Однако, чтобы упростить работу с объединениями, язык все же предоставляет следующую гарантию: если в объедине- ние входит несколько структур, имеющих общую начальную последо- вательность элементов, и если в данный момент объединение со- держит одну из них, можно обрабатывать общую начальную часть любой из этих структур. Например, корректен следующий фрагмент: |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) ... 11.2. Функции С функцией можно делать только две вещи: вызывать и вычислять ее адрес. Если ее имя встретилось в выражении не в позиции име- ни функции конструкции вызова, порождается указатель на функ- цию. Таким образом, чтобы передать одну функцию в другую, можно написать: |int f (); | . . . |g (f); Определение g может быть таким: |g (funcp) |int (*funcp) (); |{ | . . . | (*funcp) (); | . . . |} Идентификатор f должен быть явно описан в контексте вызова как имя функции, поскольку за вхождением f в конструкцию g(f) нет открывающей скобки. 11.3. Массивы, указатели и индексирование Всякий раз, когда идентификатор типа массив встречается в выра жении, он преобразуется в указатель на первый элемент массива. Из-за этого преобразования массивы не являются л_значениями. По определению, операция индексирования [] понимается таким обра- зом, что 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 размещаются построчно (последний индекс изме- няется быстрее всего); первый индекс в описании дает возмож- ность определить объем памяти, занимаемый массивом. Другой роли в исчислении индексов массивы не играют. 11.4. Явные преобразования указателей Некоторые преобразования, затрагивающие указатели, допустимы, но имеют особенности, зависящие от реализации. Имеются в виду операции явного преобразования типа (см. пункт Унарные операции в разделе ВЫРАЖЕНИЯ И ОПЕРАЦИИ и пункт Имена типов в разделе ОПИСАНИЯ). Указатель можно преобразовать в любой из целочисленных типов, достаточно большой, чтобы вместить его. Требуется ли int или long, зависит от конкретного компьютера. Отображающая функция также зависит от компьютера, но скорее всего не будет неожидан ной для квалифицированного пользователя. Объект целочисленного типа может быть явно преобразован в ука- затель. Отображение всегда переводит целое, полученное преобра зованием из указателя, в тот же указатель; в остальном оно ма- шинно-зависимо. Указатель на объект одного типа можно преобразовать в указатель на объект другого типа. При использовании указателя-результата может произойти ошибка адресации, если исходный указатель не был должным образом выравнен. Гарантируется, что можно преобра- зовать указатель на объект некоторого размера в указатель на объект меньшего размера а затем обратно, без изменений. Например, процедура выделения памяти получает размер (в байтах) объекта, который требуется разместить, и возвращает результат типа указатель на char; ее можно использовать так: |extern char *alloc (); |double *dp; |dp = (double *) alloc (sizeof (double)); |*dp = 22.0 / 7.0; Функция alloc() должна гарантировать (в смысле машинной зависи мости), что возвращаемое значение можно преобразовать в указа- тель на double; тогда программа, описанным образом использующая эту функцию, будет мобильной. 12. КОНСТАНТНЫЕ ВЫРАЖЕНИЯ В нескольких случаях в языке C требуются выражения, которые вы- рабатывают константное значение: после case, в качестве границ массива, и в инициализаторах. В первых двух случаях выражение может включать только целые константы, символьные константы, преобразования к целочисленным типам, константы перечислимых типов и выражения sizeof, быть может соединенные бинарными опе рациями |+ - * / % & | ^ << >> == != < > <= >= && || унарными операциями |- ~ или тернарной операцией |? : Разрешается использовать скобки, но только для группировки, а не для вызова функций. Несколько больше свободы в случае инициализаторов; кроме конс- тантных выражений, указанных выше, можно также использовать ве- щественные константы и преобразования к произвольным типам, можно применять унарную операцию & к внешним/статическим объек там и к внешним/статическим массивам, индексированным констант- ными выражениями. Унарная операция & может также применяться неявно при появлении функций и непроиндексированных массивов. Основное правило заключается в том, что значения инициализато- ров должны быть константами либо адресами предварительно опр деленных внешних или статических объектов плюс/минус константа. 13. ВОПРОСЫ МОБИЛЬНОСТИ Некоторые свойства языка C существенно машинно-зависимы. Не предполагается, что приведенный список возможных затруднений полон, однако основные сложности в нем указаны. Чисто аппаратные вопросы, подобные размеру слова, свойствам ве- щественной арифметики и целого деления, как показала практика, большой проблемы не составляют. Другие аспекты аппаратуры нахо- дят отражение в различающихся реализациях. За некоторыми из до- садных различий, в особенности за распространением знака (пре- образование отрицательного символа в отрицательное целое) и по- рядком, в котором байты размещаются в слове, надо тщательно следить. Большинство остальных различий большой проблемы не составляют. Число переменных register, которое в действительности можно по- местить в регистры, так же как и набор типов, допустимых для регистровых переменных, изменяется от компьютера к компьютеру. Однако компиляторы делают все, что возможно на данной машине; избыточные или недопустимые описания register игнорируются. Порядок обработки параметров функций в языке не фиксируется, поэтому не определяется и порядок проявления побочных эффектов Поскольку символьные константы на самом деле являются объектами типа int, могут быть разрешены константы из нескольких симво- лов. Конкретная реализация сильно зависит от компьютера, так как порядок размещения символов в слове изменяется от одной ма- шины к другой. Битные поля, как и символы, могут располагаться в машинном сло- ве слева направо или справа налево. Эти различия незаметны для изолированной программы, не позволяющей себе вольностей с типа ми (например, преобразование целого указателя в символьный ука затель и обследование указываемой им области памяти), однако должны учитываться при согласовании с заданным извне расположе- нием данных. 14. СВОДКА СИНТАКСИСА Данная сводка синтаксиса языка C предназначена скорее для луч шего понимания, чем для строгого изложения языка. 14.1. Выражения Основные выражения таковы: |выражение: | первичное_выражение | * выражение | & л_значение | - выражение | ! выражение | ~ выражение | ++ л_значение | -- л_значение | л_значение ++ | л_значение -- | sizeof выражение | sizeof ( имя_типа ) | ( имя_типа ) выражение | выражение биноп выражение | выражение ? выражение : выражение | л_значение присвоп выражение | выражение , выражение |первичное_выражение: | идентификатор | константа | текстовая_константа | ( выражение ) | первичное_выражение [ выражение ] | первичное_выражение ( список_выражений ) | первичное_выражение . идентификатор | первичное_выражение -> идентификатор |список_выражений: | выражение | список_выражений , выражение |л_значение: | идентификатор | первичное_выражение [ выражение ] | л_значение . идентификатор | * выражение | ( л_значение ) Операции, образующие первичные выражения, |( ) [ ] . -> имеют высший приоритет и группируются слева направо. Унарные операции |* & - ! ~ ++ -- sizeof ( имя_типа ) имеют приоритет ниже первичных операций, но выше, чем любая би- нарная операция, и группируются справа налево. Бинарные опера- ции группируются слева направо; их приоритет понижается в соот- ветствии с приведенным ниже перечнем. |биноп: | * / % | + - | >> << | < > <= >= | == != | & | ^ | | | && | || Условная операция группируется справа налево. Все операции присваивания имеют один и тот же приоритет и груп- пируются справа налево. |присвоп: | = += -= *= /= %= >>= <<= &= ^= |= Операция запятая имеет низший приоритет и группируется слева направо. 14.2. Описания |описание: | спецификаторы список_описателей_данных ; |спецификаторы: | спецификатор_типа спецификаторы | спецификатор_класса спецификаторы |спецификатор_класса: | auto | static | extern | register | typedef |спецификатор_типа: | спецификатор_базового_типа | спецификатор_структуры_или_объединения | имя_определяемого_типа | спецификатор_перечислимого_типа |спецификатор_базового_типа: | базовый_тип | базовый_тип спецификатор_базового_типа |базовый_тип: | char | short | int | long | unsigned | float | double | void |спецификатор_перечислимого_типа: | enum { список_перечисляемых } | enum идентификатор { список_перечисляемых } | enum идентификатор |список_перечисляемых: | перечисляемое | список_перечисляемых , перечисляемое |перечисляемое: | идентификатор | идентификатор = константное_выражение |список_описателей_данных: | описатель_данных | описатель_данных , список_описателей_данных |описатель_данных: | описатель инициализатор |описатель: | идентификатор | ( описатель ) | * описатель | описатель ( ) | описатель [ константное_выражение ] |спецификатор_структуры_или_объединения: | struct { список_структ_описаний } | struct идентификатор { список_структ_описаний } | struct идентификатор | union { список_структ_описаний } | union идентификатор { список_структ_описаний } | union идентификатор |список_структ_описаний: | структ_описание | структ_описание список_структ_описаний |структ_описание: | спецификатор_типа список_структ_описателей ; |список_структ_описателей: | структ_описатель | структ_описатель , список_структ_описателей |структ_описатель: | описатель | описатель : константное_выражение | : константное_выражение |инициализатор: | = выражение | = { список_инициализаторов } | = { список_инициализаторов , } |список_инициализаторов: | выражение | список_инициализаторов , список_инициализаторов | { список_инициализаторов } | { список_инициализаторов , } |имя_типа: | спецификатор_типа абстрактный_описатель |абстрактный_описатель: | пусто | ( абстрактный_описатель ) | * абстрактный_описатель | абстрактный_описатель ( ) | абстрактный_описатель [ константное_выражение ] |имя_определяемого_типа: | идентификатор 14.3. Операторы |составной_оператор: | { список_описаний список_операторов } |список_описаний: | описание | описание список_описаний |список_операторов: | оператор | оператор список_операторов |оператор: | составной_оператор | выражение ; | if ( выражение ) оператор | if ( выражение ) оператор else оператор | while ( выражение ) оператор | do оператор while ( выражение ) ; | for ( выражение ; выражение ; выражение ) оператор | switch ( выражение ) оператор | case константное_выражение : оператор | default: оператор | break ; | continue ; | return ; | return выражение ; | goto идентификатор ; | идентификатор : оператор | ; 14.4. Внешние определения |программа: | внешнее_определение | внешнее_определение программа |внешнее_определение: | определение_функции | определение_данных |определение_функции: | спецификаторы описатель_функции тело_функции |описатель_функции: | описатель ( список_параметров ) |список_параметров: | идентификатор | идентификатор , список_параметров |тело_функции: | список_описаний составной_оператор |определение_данных: | extern описание | static описание 14.5. Препроцессор |#define идентификатор цепочка_лексем |#define идентификатор (идентификатор,...) цепочка_лексем |#undef идентификатор |#include "имя_файла" |#include <имя_файла> |#if ограниченное_константное_выражение |#ifdef идентификатор |#ifndef идентификатор |#elif ограниченное_константное_выражение |#else |#endif |#line константа "имя_файла" |#ident "версия"