КаталогИндекс раздела
НазадОглавление


Язык C

СОДЕРЖАНИЕ

1. Введение

2. Лексические соглашения
    2.1. Комментарии
    2.2. Идентификаторы (Имена)
    2.3. Ключевые слова
    2.4. Константы
        2.4.1. Целые константы
        2.4.2. Длинные целые константы
        2.4.3. Символьные константы
        2.4.4. Вещественные константы
        2.4.5. Перечислимые константы
    2.5. Текстовые константы
    2.6. Форма описания синтаксиса языка

3. Класс памяти и тип
    3.1. Класс памяти
    3.2. Тип
    3.3. Объекты и л_значения

4. Преобразования типов в операциях
    4.1. Символы и целые
    4.2. Одинарная и двойная точность
    4.3. Вещественные и целочисленные значения
    4.4. Указатели и целые
    4.5. Беззнаковые
    4.6. Арифметические преобразования
    4.7. Пустое значение

5. Выражения и операции
    5.1. Первичные выражения
    5.2. Унарные операции
    5.3. Мультипликативные операции
    5.4. Аддитивные операции
    5.5. Операции сдвига
    5.6. Операции отношения
    5.7. Операции равенства
    5.8. Побитная операция И
    5.9. Побитная операция исключающее ИЛИ
    5.10. Побитная операция ИЛИ
    5.11. Логическая операция И
    5.12. Логическая операция ИЛИ
    5.13. Условная операция
    5.14. Операции присваивания
    5.15. Операция запятая

6. Описания
    6.1. Спецификаторы класса памяти
    6.2. Спецификаторы типа
    6.3. Описатели
    6.4. Смысл описателей
    6.5. Описания структур и объединений
    6.6. Описания перечислимых типов
    6.7. Инициализация
    6.8. Имена типов
    6.9. Неявные описания
    6.10. Определяемые типы

7. Операторы
    7.1. Оператор-выражение
    7.2. Составной оператор (блок)
    7.3. Условный оператор if
    7.4. Оператор цикла while
    7.5. Оператор цикла do
    7.6. Оператор цикла for
    7.7. Оператор выбора switch
    7.8. Оператор break
    7.9. Оператор continue
    7.10. Оператор возврата return
    7.11. Оператор перехода goto
    7.12. Оператор с меткой
    7.13. Пустой оператор

8. Внешние определения
    8.1. Внешние определения функций
    8.2. Внешние определения данных

9. Правила видимости
    9.1. Лексическая видимость
    9.2. Видимость внешних объектов

10. Командные строки препроцессора
    10.1. Замена лексем
    10.2. Включение файлов
    10.3. Условная компиляция
    10.4. Управление строками
    10.5. Управление версиями

11. Еще о типах
    11.1. Структуры и объединения
    11.2. Функции
    11.3. Массивы, указатели и индексирование
    11.4. Явные преобразования указателей

12. Константные выражения

13. Вопросы мобильности

14. Сводка синтаксиса
    14.1. Выражения
    14.2. Описания
    14.3. Операторы
    14.4. Внешние определения
    14.5. Препроцессор


 

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, подчиняются законам арифметики по модулю (2n), где 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. Арифметические преобразования

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

4.7. Пустое значение

(Несуществующее) значение объекта типа void никаким образом невозможно использовать, к нему нельзя применить ни явное, ни неявное преобразования. Так как выражение типа void обозначает несуществующее значение, такое выражение можно использовать только как оператор-выражение (см. пункт Оператор-выражение в разделе ОПЕРАТОРЫ) или как левый операнд в выражении запятая (см. пункт Операция запятая в разделе ВЫРАЖЕНИЯ И ОПЕРАЦИИ).

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

 

5. ВЫРАЖЕНИЯ И ОПЕРАЦИИ

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

В остальном порядок обработки выражений не определен. В частности, компилятор волен вычислять подвыражения в том порядке, который он считает наиболее эффективным, даже если подвыражения имеют побочные эффекты. Выражения, содержащие коммутативные и ассоциативные операции (*, +, &, |, ^), могут быть произвольным образом переупорядочены даже при наличии скобок. Чтобы навязать определенный порядок вычислений, нужно использовать явное присваивание временным переменным.

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

5.1. Первичные выражения

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

       первичное_выражение:
           идентификатор
           константа
           текстовая_константа
           ( выражение )
           первичное_выражение [ выражение ]
           первичное_выражение ( список_выражений  )
           первичное_выражение . идентификатор
           первичное_выражение -> идентификатор

       список_выражений:
           выражение
           список_выражений , выражение

Идентификатор является первичным выражением при условии, что он соответствующим образом описан (описания обсуждаются ниже). Тип идентификатора специфицируется в описании. Если тип идентификатора - "массив ...", то значение такого выражения есть указатель на первый объект в массиве, а тип выражения - "указатель на ...". Кроме того, идентификатор массива не является л_значением. Аналогично, идентификатор, описанный как "функция, возвращающая ...", при использовании всюду кроме позиции имени функции в конструкции вызова, преобразуется к типу "указатель на функцию, возвращающую ...".

Константа является первичным выражением. Тип константы может быть int, long или double в зависимости от ее внешнего вида. Символьные константы имеют тип int, вещественные константы имеют тип double.

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

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

Первичное выражение, за которым следует выражение в квадратных скобках, является первичным выражением. Его интуитивный смысл - индексирование. Обычно первое выражение имеет тип "указатель на ...", выражение-индекс - int, тип результата - "...". Выражение E1[E2] тождественно (по определению) *((E1)+(E2)). Все сведения, необходимые для понимания данной записи, приводятся в этом пункте, а также в пунктах Унарные операции и Аддитивные операции в связи с обсуждением идентификаторов, * и +, соответственно. Выводы делаются в пункте Массивы, указатели и индексирование (раздел ЕЩЕ О ТИПАХ).

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

Все фактические параметры типа float преобразуются перед вызовом к типу double. Все параметры типов char или unsigned long - к int. Имена массивов преобразуются в указатели. Никаких других преобразований автоматически не делается; в частности, компилятор не сравнивает типы фактических и формальных параметров. Если преобразование необходимо, его следует задать явно (см. Унарные операции, а также пункт Имена типов в разделе ОПИСАНИЯ).

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

Первичное выражение, за которым следует точка, а затем идентификатор, также является первичным. Первое выражение должно быть структурой или объединением, а идентификатор должен именовать элемент структуры или объединения. Значение выражения - названный элемент структуры или объединения; это л_значение, если первое выражение - л_значение.

Первичное выражение, за которым следует стрелка -> (составленная из знаков - и >), а затем идентификатор, также является первичным. Первое выражение должно быть указателем на структуру или объединение, а идентификатор должен именовать элемент этой структуры или объединения. Значение выражения - л_значение, обозначающее названный элемент указуемой структуры или объединения. Таким образом, выражение E1->MOS - то же самое, что и (*E1).MOS. Структуры и объединения обсуждаются в пункте Описания структур и объединений раздела ОПИСАНИЯ.

5.2. Унарные операции

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

       унарное_выражение:
           * выражение
           & л_значение
           - выражение
           ! выражение
           ~ выражение
           ++ л_значение
           -- л_значение
           л_значение ++
           л_значение --
           ( имя_типа ) выражение
           sizeof выражение
           sizeof ( имя_типа )

Унарная операция * вызывает "переход к значению"; операнд должен быть указателем, результатом является л_значение, обозначающее объект, на который указывает операнд. Если тип операнда - "указатель на ...", то тип результата - "...".

Результатом унарной операции &; является указатель на объект, который обозначен л_значением. Если тип л_значения - "...", то тип результата - "указатель на ...".

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

Унарная операция + отсутствует.

Результат операции логического отрицания ! равен единице, если значение операнда равно нулю, и нулю, если значение операнда ненулевое. Тип результата - int. Операция применима к любому арифметическому типу и к указателям.

Операция ~ дает побитное отрицание своего операнда. Выполняются обычные арифметические преобразования. Тип операнда должен быть целочисленным.

При выполнении префиксной операции ++ объект, который обозначается операндом-л_значением, увеличивается на единицу. Результат равен новому значению операнда, однако он не является л_значением. Выражение ++x эквивалентно x += 1. (См. информацию о преобразованиях в пунктах Аддитивные операции и Операции присваивания).

Операнд-л_значение префиксной операции -- уменьшается на единицу по аналогии с префиксной операцией ++.

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

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

Выражение, которому предшествует заключенное в скобки имя типа, преобразуется к названному типу. Такая конструкция называется явным преобразованием. Имена типов обсуждаются в пункте Имена типов раздела ОПИСАНИЯ.

Операция sizeof выдает размер своего операнда в байтах. (Понятие байта в языке фигурирует только как единица измерения sizeof, однако во всех существующих реализациях байт - это фрагмент памяти, необходимый для хранения символа.) Если операция применяется к массиву, результат равен общему числу байт в массиве. Размер вычисляется по описаниям объектов. Семантически данное выражение является константой типа 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 или unsigned long; смысл такой записи - тот же, что и в случае отсутствия int. Вместе с float можно указывать ключевое слово long; эта запись имеет тот же смысл, что и double. Ключевое слово unsigned может быть указано само по себе, либо вместе с int или с любым из его вариантов (long или unsigned long), а также с char.

В остальных случаях в описании может быть задано не более одного спецификатора_типа. В частности, не допускается использование дополнений long, unsigned long и 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. Составной оператор (блок)

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


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

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

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

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

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

7.3. Условный оператор if

Имеется два варианта условного оператора:


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

В обоих вариантах сначала вычисляется выражение; если результат оказывается ненулевым, выполняется первый оператор. Во втором варианте условного оператора, если значение выражения равно 0, выполняется второй оператор. Синтаксическая неоднозначность else разрешается путем присоединения 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 :

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

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

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

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

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

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 и unsigned long воспринимаются как int. Кроме того, поскольку ссылка на массив в любом контексте (в частности, в качестве фактического параметра) рассматривается как указатель на первый элемент массива, то описания формальных параметров вида "массив..." воспринимаются как "указатель на ...".

8.2. Внешние определения данных

Внешнее определение данных имеет вид:


       определение_данных:
           описание

Такие данные могут иметь класс памяти extern (выбирается по умолчанию) либо static, но не auto или register.

 

9. ПРАВИЛА ВИДИМОСТИ

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

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

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

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

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

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

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

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

9.2. Видимость внешних объектов

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

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

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

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

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

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

Идентификаторы, описанные на верхнем уровне внешних определений как 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 ( имя_типа )
           ( имя_типа ) выражение
           выражение  биноп  выражение
           выражение ? выражение : выражение
           л_значение присвоп выражение
           выражение , выражение

       первичное_выражение:
           идентификатор
           константа
           текстовая_константа
           ( выражение )
           первичное_выражение [ выражение ]
           первичное_выражение ( список_выражений  )
           первичное_выражение . идентификатор
           первичное_выражение -> идентификатор

       список_выражений:
           выражение
           список_выражений , выражение

       л_значение:
           идентификатор
           первичное_выражение [ выражение ]
           л_значение . идентификатор
           <b>* выражение
           ( л_значение )

Операции, образующие первичные выражения,


       ( )  [ ]  .  ->

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

       *  &  -  !  ~  ++  --  sizeof  ( имя_типа )

имеют приоритет ниже первичных операций, но выше, чем любая бинарная операция, и группируются справа налево. Бинарные операции группируются слева направо; их приоритет понижается в соответствии с приведенным ниже перечнем.


       биноп:
           *  /  %
           +  -
           >>  <<

14.2. Описания


       описатель_данных:
           описатель  инициализатор

       описатель:
           идентификатор
           ( описатель )
           * описатель
           описатель ( )
           описатель [ константное_выражение  ]

       спецификатор_структуры_или_объединения:
           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 "версия"


НазадОглавление
КаталогИндекс раздела