Содержание

Приложение A. Справочное руководство

A1. Введение

Данное руководство является описанием языка программирования Си, определенного в соответствии с проектом, утвержденным в ANSI в качестве Американского национального стандарта для информационных систем: Язык программирования Си. X3.159-1989 («American National Standard for Information Systems — Programming Language C, X3.159-1989»). Это описание — лишь некоторый вариант предлагаемого стандарта, а не сам стандарт, однако мы специально заботились о том, чтобы оно было надежным руководством по языку.

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

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

A2. Соглашения о лексике

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

A2.1. Лексемы

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

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

A2.2. Комментарий

Литеры /* открывают комментарий, а литеры */ закрывают его. Комментарии нельзя вкладывать друг в друга, их нельзя помещать внутрь стрингов или текстовых литералов.

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

Идентификатор — последовательность букв и цифр. Первой литерой должна быть буква; знак подчеркивания _ считается буквой. Буквы нижнего и верхнего регистров различаются. Идентификаторы могут иметь любую длину; для внутренних идентификаторов значимыми являются первая 31 литера; в некоторых реализациях принято большее число значимых литер. К внутренним идентификаторам относятся имена макросов и все другие имена, не имеющие внешних связей (A11.2). На идентификаторы с внешними связями могут накладываться большие ограничения: иногда воспринимаются не более шести первых литер и/или не различаются буквы верхнего и нижнего регистров.

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

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

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

В некоторых реализациях резервируются также слова fortran и asm.

Ключевые слова const, signed и volatile впервые появились в стандарте ANSI; enum и void — новые по отношению к первому изданию, но уже использовались; ранее зарезервированное entry нигде не использовалось и поэтому более не резервируется.

A2.5. Константы

Существует несколько видов констант. Каждая имеет свой тип данных; базовые типы рассматриваются в A4.2.

константа:
целая_константа
литерная_константа
с_плав_точкой_константа
перечислимая_константа

A2.5.1. Целые константы

Целая константа, состоящая из последовательности цифр, воспринимается как восьмеричная, если она начинается с 0 (цифры нуль), и как десятичная в противном случае. Восьмеричная константа не содержит цифр 8 и 9. Последовательность цифр, перед которой стоят 0x или 0X, рассматривается как шестнадцатиричное целое. В шестнадцатиричные цифры включены буквы от a (или A) до f (или F) со значениями от 10 до 15.

Целая константа может быть записана с буквой-суффиксом u (или U) для спецификации ее как беззнаковой константы. Она также может быть с буквой- суффиксом l (или L) для указания, что она имеет тип long.

Тип целой константы зависит от ее вида, значения и суффикса. (О типах см. A4.) Если константа — десятичная и не имеет суффикса, то она принимает первый из следующих типов, который годится для представления ее значения: int, long int и unsigned long int. Восьмеричная или шестнадцатиричная константа без суффикса принимает первый возможный из типов int, unsigned int, long int и unsigned long int. Если константа имеет суффикс u или U, то она принимает первый возможный из типов unsigned int и unsigned long int. Если константа имеет суффикс l или L, то она принимает первый возможный из типов long int и unsigned long int.

Типы целых констант получили существенное развитие в сравнении с первой редакцией, в которой большие целые имели просто тип long. Суффиксы U и u введены впервые.

A2.5.2. Литерные константы

Литерная константа — последовательность из одной или нескольких литер, заключенная в одиночные кавычки (например, 'x'). Если внутри одиночных кавычек расположена одна литера, значением константы является числовое значение этой литеры в кодировке, принятой на данной машине. Значение константы с несколькими литерами зависит от реализации.

Литерная константа не может содержать в себе одиночную кавычку «'» или литеру новая_строка; чтобы изобразить их и некоторые другие литеры, могут быть использованы эскейп-последовательности:

новая_строка (newline, linefeed) NL (LF) \n
гориз_таб (horisontal tab) HT \t
верт_таб (vertical tab) VT \v
возврат_на_шаг (backspace) BS \b
возврат_каретки (carriage return) CR \r
перевод_страницы (formfeed) FF \f
сигнал_звонок (audible alert, bell) BEL \a
обратная_наклонная_черта (backslash) \ \\
знак_вопроса (question mark) ? \?
одиночная_кавычка (single quote) ' \'
двойная_кавычка (double quote) " \"
восьмеричный_код (octal number) ooo \ooo
шестнадцатиричный_код (hex number) hh \xhh

Эскейп-последовательность \ooo состоит из обратной наклонной черты, за которой следуют одна, две или три восьмеричные цифры, специфицирующие значение желаемой литеры. Наиболее частым примером такой конструкции является \0 (за которой не следует цифра); она специфицирует null-литеру. Эскейп-последовательность \xhh состоит из обратной наклонной черты с буквой x, за которыми следуют шестнадцатиричные цифры, специфицирующие значение желаемой литеры. На количество цифр нет ограничений, но результат будет не определен, если значение полученной литеры превысит значение самой «большой» из допустимых литер. Если в данной реализации тип char трактуется как число со знаком, то значение и в восьмеричной, и в шестнадцатиричной эскейп-последовательности получается при помощи «размножения знака», как если бы выполнялась операция приведения к типу char. Результат не определен, если за \ не следует ни одна из перечисленных выше литер.

В некоторых реализациях имеется расширенный набор литер, который не может быть охвачен типом char. Константа для такого набора пишется с буквой L впереди (например, L'x') и называется «широкой» литерной константой. Такая константа имеет тип wchar_t (целочисленный тип, определенный в стандартном головном файле <stddef.h>). Как и в случае обычных литерных констант, здесь также возможны восьмеричные и шестнадцатиричные эскейп-последовательности; результат будет не определен, если специфицированное значение превысит тип wchar_t.

Некоторые из приведенных эскейп-последовательностей новые (шестнадцатиричные в частности). Новым является и расширенный тип для литер. Наборам литер, обычно используемым в Америке и Западной Европе, подходит тип char, а тип wchar_t был добавлен, главным образом для того, чтобы удовлетворить азиатские языки.

A2.5.3. Константы с плавающей точкой (плавающие константы)

Плавающая константа состоит из целой части, десятичной точки, дробной части, e или E и целого (возможно, со знаком), представляющего экспоненту, и, возможно, типа — суффикса, задаваемого одной из букв: f, F, l или L. И целая, и дробная часть представляют собой последовательность цифр. Либо целая часть, либо дробная часть (но не обе вместе) могут отсутствовать; также могут отсутствовать десятичная точка или E с экспонентой (но не обе одновременно). Тип определяется суффиксом; F или f определяют тип float, L или l — тип long double; при отсутствии суффикса подразумевается тип double.

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

A2.5.4. Перечислимые константы

Идентификаторы, объявленные как элементы перечисления (A8.4), являются константами типа int.

A2.6. Стринговые литералы

Стринговый литерал, который также называют стринговой константой, — это последовательность литер, заключенная в двойные кавычки (например, "..."). Стринг имеет тип «массив литер» и память класса static (A4), которая инициализируется заданными литерами. Представляются ли одинаковые стринговые литералы одной копией или несколькими, зависит от реализации. Поведение программы, пытающейся изменить стринговый литерал, не определено.

Рядом написанные стринговые литералы объединяются (конкатенируются) в один стринг. После любой конкатенации к стрингу добавляется null-байт (\0), что позволяет программе, просматривающей стринг, найти его конец. Стринговые литералы не могут содержать в себе новую_строку или двойную кавычку; в них нужно использовать те же эскейп-последовательности, что и в литерных константах.

Как и в случае с литерными константами, стринговый литерал с литерами из расширенного набора должен начинаться с буквы L (например, L"..."). Стринговый литерал из расширенного набора имеет тип «массив из wchar_t». Конкатенация обычных и «расширенных» стринговых литералов друг с другом не определена.

То, что стринговые литералы не обязательно представляются разными копиями, запрет на их модификацию, а также конкатенация соседних стринговых литералов являются нововведениями ANSI-стандарта. «Расширенные» стринговые литералы также объявлены впервые.

A3. Нотация синтаксиса

В нотации синтаксиса, используемой в этом руководстве, синтаксические понятия набираются курсивом, а слова и литеры, воспринимаемые буквально, обычным шрифтом. Альтернативные конструкции обычно перечисляются в столбик (каждая альтернатива на отдельной строке); в редких случаях длинные списки небольших по размеру альтернатив располагаются в одной строке, помеченной словами «один из». Необязательный терминальный или нетерминальный символ снабжается индексом «необ». Так, запись

{ выражениенеоб }

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

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

A4. Что обозначают идентификаторы

Существует ряд вещей, на которые ссылаются при помощи идентификаторов, или имен; это — функции; теги структур, объединений и перечислений; члены структур или объединений; typedef-имена и объекты. Объектом (называемым иногда переменной) является часть памяти, интерпретация которой зависит от двух главных характеристик: класса памяти и ее гнид. Класс памяти сообщает о времени жизни памяти, связанной с идентифицируемым объектом; тип определяет, какого рода значения находятся в объекте. С любым именем ассоциируется своя область действия (т.е. тот участок программы, где это имя «видимо») и атрибут связи, определяющий, ссылается ли это имя в другом файле на тот же самый объект или функцию. Область действия и атрибут связи обсуждаются в A11.

A4.1. Класс памяти

Существуют два класса памяти: автоматический и статический. Несколько ключевых слов в совокупности с контекстом деклараций объектов специфицируют класс памяти для этих объектов. Автоматические объекты локализованы в блоке (A9.3), они «исчезают» при выходе из него. Декларация, заданная внутри блока, если в ней отсутствует спецификация класса памяти или указан спецификатор auto, создает автоматический объект. Объект, помеченный в декларации словом register, является автоматическим и размещается по возможности в регистре машины.

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

A4.2. Базовые типы

Существует несколько базовых типов. Стандартный головной файл <limits.h>, описанный в приложении B, определяет самое большое и самое малое значения для каждого типа в данной конкретной реализации. В приложении B приведены минимально возможные величины.

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

Беззнаковые литеры, декларируемые при помощи слов unsigned char, имеют ту же разрядность, что и обычные литеры, но представляют неотрицательные значения; при помощи слов signed char можно явно декларировать литеры со знаком, которые занимают столько же места, как и обычные литеры.

Тип unsigned char не упоминался в первой редакции, но всеми использовался. Тип signed char — новый.

Помимо char среди целочисленных типов могут быть целые трех размеров: short int, int и long int. Обычные int-объекты имеют естественный размер, принятый в архитектуре данной машины, другие размеры предназначены для специальных нужд. Более длинные целые по крайней мере покрывают все значения более коротких целых, однако в некоторых реализациях обычные целые могут быть эквивалентны коротким (short) или длинным (long) целым. Все типы int представляют значения со знаком, если не оговорено противное.

Для беззнаковых целых в декларациях используется ключевое слово unsigned. Такие целые подчиняются арифметике по модулю 2n где n — число бит в представлении числа, и, следовательно, в арифметике с беззнаковыми целыми никогда не бывает переполнения. Множество неотрицательных значений, которые могут храниться в знаковых объектах, является подмножеством значений, которые могут храниться в соответствующих беззнаковых объектах; знаковое и беззнаковое представления каждого такого значения совпадают.

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

long double — новый тип. В первой редакции синонимом для double был long float, теперь последний изъят из обращения.

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

Поскольку объекты перечислимых типов можно рассматривать как числа, перечисление относят к арифметическому типу. Типы char и int всех размеров, каждый из которых может быть со знаком или без знака, а также перечислимые типы называют целочисленными. Типы float, double и long double называются плавающими.

Тип void специфицирует пустое множество значений. Он используется как «тип возвращаемого функцией значения» в том случае, когда она не генерирует никакого результирующего значения.

A4.3. Выводимые типы

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

В общем случае приведенные методы конструирования объектов могут применяться рекурсивно.

A4.4. Квалификаторы типов

Тип объекта может снабжаться квалификатором. Декларация объекта с квалификатором const указывает на то, что его значение далее не будет изменяться; объявляя объект как volatile (изменчивый, непостоянный (англ.)) мы указываем на его особые свойства в отношении оптимизации, выполняемой компилятором. Ни один из квалификаторов не влияет на диапазоны значений и арифметические свойства объектов. Квалификаторы обсуждаются в A8.2.

A5. Объекты и l-значения

Объект — это некоторая именованная область памяти; l-значение — выражение, ссылающееся на объект. Очевидным примером l-значения является идентификатор с соответствующим типом и классом памяти. Существуют операции, порождающие l-значения. Например, если E — выражение типа указатель, то *E есть выражение для l-значения, ссылающегося на объект, на который указывает E. Термин «l-значение» произошел от записи присваивания E1 = E2, в которой левый (left — левый (англ.), отсюда и буква l) операнд E1 должен быть выражением l-значения. Описывая каждый оператор, мы сообщаем, ожидает ли он l-значения в качестве операндов и выдает ли l-значение в качестве результата.

A6. Преобразования

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

A6.1. Повышение целочисленного типа

Объект перечислимого типа, литера, короткое целое, целое в поле бит — все они со знаком или без могут использоваться в выражении там, где возможно применение целого. Если тип int позволяет «охватить» все значения исходного типа операнда, то операнд приводится к int, в противном случае он приводится к unsigned int. Эта процедура называется повышением целочисленности.

A6.2. Целочисленные преобразования

Любое целое приводится к некоторому заданному беззнаковому типу путем поиска конгруэнтного (т.е. имеющего то же двоичное представление) наименьшего неотрицательного значения и взятия его по модулю nmax+1, где nmax — наибольшее число в этом беззнаковом типе. Для двоичного представления в дополнительном коде это означает либо выбрасывание лишних старших разрядов, если беззнаковый тип «уже» исходного типа, либо заполнение недостающих старших разрядов нулями (для значения без знака) или размноженным знаком (для значения со знаком), если беззнаковый тип «шире» исходного.

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

A6.3. Целые и плавающие

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

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

A6.4. Плавающие типы

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

A6.5. Арифметические преобразования

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

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

A6.6. Указатели и целые

К указателю можно прибавлять (и вычитать из него) выражение целочисленного типа; последнее в этом случае подвергается преобразованию, описанному в A7.7 при рассмотрении оператора сложения.

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

Целочисленное константное выражение со значением 0 или оно же, но приведенное к типу void *, может быть преобразовано в указатель любого типа операторами приведения, присваивания и сравнения. Результатом будет null-указатель, который равен любому другому null-указателю того же типа, но не равен никакому указателю, ссылающемуся на реальный объект или функцию.

Допускаются и другие преобразования для указателей, но в связи с ними возникает проблема зависимости результата от реализации. Эти преобразования должны быть специфицированы явным оператором преобразования типа или оператором приведения (A7.5 и A8.8).

Указатель можно привести к достаточно большому для его хранения целочисленному типу; требуемый размер зависит от реализации. Функция преобразования также зависит от реализации.

Объект целочисленного типа можно явно преобразовать в указатель. Если целое получено из указателя и имеет достаточно большой размер, это преобразование даст тот же указатель; в противном случае результат зависит от реализации.

Указатель на один тип можно преобразовать в указатель на другой тип. Если исходный указатель ссылается на объект, должным образом не выровненный по границам слов памяти, то в результате может получиться указатель, адресующий к «исключенному» фрагменту. Если требования на выравнивание у нового типа меньше или совпадают с требованиями на выравнивание первоначального типа, то гарантируется, что преобразование указателя в другой тип и обратно его не изменит; понятие «выравнивание» реализационно-зависимо, однако в любой реализации объекты типа char предъявляют минимальные требования на выравнивание. Как описано в A6.8, указатель может также преобразовываться в void * и обратно, значение указателя при этом не изменяется.

Указатель может быть преобразован в другой указатель того же типа с добавлением или удалением квалификаторов (A4.4, A8.2) того типа объекта, на который этот указатель ссылается. Новый указатель, полученный добавлением квалификатора, имеет то же значение, но с дополнительными ограничениями, внесенными новыми квалификаторами. Операция по удалению квалификатора у объекта приводит к тому, что восстанавливается действие его начальных квалификаторов, заданных в декларации этого объекта.

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

A6.7. Тип void

Значение (несуществующее) объекта типа void никак нельзя использовать, его также нельзя явно или неявно привести к типу отличному от void. Поскольку выражение типа void обозначает отсутствие значения, его можно применять только там, где не требуется значения; например, в качестве выражения_инструкции (A9.2) или левого операнда у оператора «запятая» (A7.18).

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

Тип void не фигурировал в первом издании этой книги, однако за прошедшее время стал общеупотребительным.

A6.8. Указатели на void

Любой указатель на объект можно привести к типу void * без потери информации. Если результат подвергнуть обратному преобразованию, то мы получим прежний указатель. В отличие от преобразований указатель_в_указатель (рассмотренных в A6.6), которые требуют явных операторов приведения к типу, в присваиваниях и сравнениях указатель любого типа может выступать в паре с указателем типа void * без каких-либо предварительных преобразований типа.

Такая интерпретация указателей void * — новая; ранее роль обобщенного указателя отводилась указателю типа char *. Стандарт ANSI официально разрешает использование указателей void * совместно с указателями других типов в присваиваниях и сравнениях; в иных комбинациях указателей стандарт требует явных преобразований типа.

A7. Выражения

Приоритеты описываемых операторов имеют тот же порядок, что и подразделы данного параграфа (от высших к низшим). Например, для оператора +, описанного в A7.7, термин «операнды» означает «выражения, определенные в A7.1A7.6». В каждом подразделе описываются операторы, имеющие одинаковый приоритет, и указывается их ассоциативность (левая или правая). Приоритеты и ассоциативность всех операторов отражены в грамматике, приведенной в A13.

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

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

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

A7.1. Генерация указателя

Если для некоторого типа T тип выражения или подвыражения есть «массив из T», то значением этого выражения является указатель на первый элемент массива, и тип такого выражения заменяется на тип «указатель на T». Такая замена типа не делается, если выражение является операндом унарного оператора &, или операндом операций ++, --, sizeof, или левым операндом присваивания, или операндом оператора «.». Аналогично выражение типа «функция, возвращающая T», исключая случай, когда оно является операндом для &, преобразуется в тип «указатель на функцию, возвращающую T».

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

Первичные выражения — это идентификаторы, константы, стринги и выражения в скобках.

первичное_выражение:
идентификатор
константа
стринг
( выражение )

Идентификатор, если он был должным образом декларирован (о том, как это делается, речь пойдет ниже), — первичное выражение. Тип идентификатора специфицируется в его декларации. Идентификатор есть l-значение, если он обозначает объект (A5) арифметического типа либо объект типа «структура», «объединение» или «указатель».

Константа — первичное выражение. Ее тип зависит от формы записи, которая была рассмотрена в A2.5.

Стринговый литерал — первичное выражение. Изначально его тип — «массив из char» («массив из wchar_t» для стринга литер расширенного набора), но в соответствии с правилом, приведенным в A7.1, указанный тип обычно превращается в «указатель на char» («указатель на wchar_t») с результирующим значением «указатель на первую литеру стринга». Для некоторых инициализаторов такая замена типа не делается. (См. A8.7.)

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

A7.3. Постфиксные выражения

В постфиксных выражениях операторы выполняются слева направо.

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

A7.3.1. Ссылки на элементы массива

Постфиксное выражение, за которым следует выражение в квадратных скобках, есть постфиксное выражение, обозначающее ссылку в индексируемый массив. Одно из этих двух выражений должно иметь тип «указатель на T», где T — некоторый тип, а другое — принадлежать целочисленному типу; тип результата индексирования есть T. Выражение E1[E2] по определению идентично выражению *((E1)+(E2)). Подробности см. в A8.6.2.

A7.3.2. Вызов функции

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

extern int идентификатор ( );

помещенной в самом внутреннем блоке, содержащем вызов соответствующей функции. Постфиксное выражение (после, возможно, неявного описания и генерации указателя, см. A7.1) должно иметь тип «указатель на функцию, возвращающую T», где T — тип возвращаемого значения.

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

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

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

Имеются два способа декларирования функции. В новом способе типы параметров задаются явно и являются частью типа функции; такая декларация называется прототипом функции. При старом способе типы параметров не указываются. Способы декларирования функций обсуждаются в A8.6.3 и A10.1.

Если вызов находится в области действия декларации, написанной по-старому, каждый его аргумент подвергается операции повышения типа: для целочисленных аргументов осуществляется повышение целочисленности (A6.1), а для float-аргументов — преобразование в double. Результат работы вызова не определен, если число аргументов не соответствует количеству параметров в определении функции или если типы аргументов после повышения не согласуются с типами соответствующих параметров. Критерий согласованности типов зависит от способа (старого или нового) определения функции. При старом способе сравниваются повышенный тип аргумента в вызове и повышенный тип соответствующего параметра; при новом способе повышенный тип аргумента и тип параметра (без его повышения) должны быть одинаковыми.

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

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

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

A7.3.3. Ссылки на члены структуры

Постфиксное выражение, за которым стоит точка с последующим идентификатором, является постфиксным выражением. Выражение первого операнда должно быть структурой или объединением, а идентификатор — именем члена структуры или объединения. Значение — именованный член структуры или объединения, а тип значения — тип члена структуры или объединения. Выражение является l-значением, если первое выражение — l-значение и если тип второго выражения — не «массив».

Постфиксное выражение, за которым указана стрелка (составленная из знаков - и >) с последующим идентификатором, является постфиксным выражением. Выражение первого операнда должно быть указателем на структуру (объединение), а идентификатор — именем члена структуры (объединения). Результат — именованный член структуры (объединения), на которую ссылается указатель, а тип значения — тип члена структуры (объединения); результат — l-значение, если тип не есть «массив».

Таким образом, выражение E1->MOS означает то же самое, что и выражение (*E1).MOS. Структуры и объединения рассматриваются в A8.3.

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

A7.3.4. Постфиксные инкрементирование и декрементирование

Постфиксное выражение, за которым следует ++ или --, есть постфиксное выражение. Значением такого выражения является значение его операнда. После того как значение было взято, операнд увеличивается (++) или уменьшается (--) на 1. Операнд должен быть l-значением; информация об ограничениях, накладываемых на операнд, и деталях операций содержится в A7.7, где обсуждаются аддитивные операторы, и в A7.17, где рассматривается присваивание. Результат инкрементирования или декрементирования не есть l-значение.

A7.4. Унарные операторы

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

унарное_выражение:
постфиксное_выражение
++ унарное_выражение
-- унарное_выражение
унарный_оператор выражение_приведенное_к_типу
sizeof унарное_выражение
sizeof ( имя_типа )
унарный_оператор: один из
& * + - ~ !

A7.4.1. Префиксные инкрементирование и декрементирование

Унарное выражение, перед которым стоит ++ или --, есть унарное выражение. Операнд увеличивается (++) или уменьшается (--) на 1. Значением выражения является значение его операнда после увеличения (уменьшения). Операнд всегда — l-значение; информация об ограничениях на операнд и деталях операции содержится в A7.7, где обсуждаются аддитивные операторы, и в A7.17, где рассматривается присваивание. Результат инкрементирования и декрементирования не есть l-значение.

A7.4.2. Оператор получения адреса

Унарный оператор & обозначает операцию получения адреса своего операнда. Операнд должен быть либо l-значением, не ссылающимся ни на поле битов, ни на объект, объявленный как register, либо иметь тип «функция». Результат — указатель на объект (или функцию), адресуемый этим l-значением. Если тип операнда есть T. то типом результата является «указатель на T».

A7.4.3. Оператор косвенности

Унарный оператор * обозначает операцию косвенности (раскрытия указателя), возвращающую объект (или функцию), на который указывает ее операнд. Результат есть l-значение, если операнд — указатель на объект арифметического типа или на объект типа «структура», «объединение» или «указатель». Если тип выражения — «указатель на T», то тип результата — T.

A7.4.4. Оператор унарный плюс

Операнд унарного + должен иметь арифметический тип, результат — значение операнда. Целочисленный операнд подвергается повышению целочисленности. Типом результата является повышенный тип операнда.

Унарный + был добавлен для симметрии с унарным -.

A7.4.5. Оператор унарный минус

Операнд для унарного - должен иметь арифметический тип, результат — значение операнда с противоположным знаком. Целочисленный операнд подвергается повышению целочисленности. Отрицательное значение от беззнаковой величины вычисляется вычитанием приведенного к повышенному типу операнда из максимального числа повышенного типа, увеличенного на 1; однако минус нуль есть нуль. Типом результата будет повышенный тип операнда.

A7.4.6. Оператор обращения разрядов

Операнд оператора ~ должен иметь целочисленный тип, результат — дополнение операнда до единиц по всем разрядам. Выполняется повышение целочисленности типа операнда. Если операнд беззнаковый, то результат получается вычитанием его значения из самого большого числа повышенного типа. Если операнд знаковый, то результат вычисляется посредством приведения «повышенного операнда» к беззнаковому типу, выполнения операции ~ и обратного приведения его к знаковому типу. Тип результата — повышенный тип операнда.

A7.4.7. Оператор логического отрицания

Операнд оператора ! должен иметь арифметический тип или быть указателем. Результат равен 1, если сравнение операнда с 0 дает истину, и равен 0 в противном случае. Тип результата — int.

A7.4.8. Оператор определения размера sizeof

Оператор sizeof дает число байтов, требуемое для хранения объекта того типа, который имеет его операнд. Операнд — либо выражение (которое не вычисляется), либо имя типа, записанное в скобках. Примененный к char оператор sizeof дает 1. Для массива результат равняется общему количеству байтов в массиве, для структуры или объединения — числу байтов в объекте, включая и байты-заполнители, которые понадобились бы, если бы из элементов составлялся массив. Размер массива из n элементов всегда равняется n, помноженному на размер отдельного его элемента. Данный оператор нельзя применять к операнду типа «функция», к незавершенному типу и к полю битов. Результат — беззнаковая целочисленная константа; конкретный ее тип зависит от реализации. В стандартном головном файле <stddef.h> (см. приложение B) этот тип определяется под именем size_t.

A7.5. Оператор приведения типа

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

выражение_приведенное_к_типу:
унарное_выражение
( имя_типа ) выражение_приведенное_к_типу

Данная конструкция называется приведением. Имена типов даны в A8.8. Результат преобразований описан в A6. Выражение с приведением типа не является l-значением.

A7.6. Мультипликативные операторы

Мультипликативные операторы *, / и % выполняются слева направо.

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

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

Бинарный оператор * обозначает умножение.

Бинарный оператор / получает частное, а % — остаток от деления первого операнда на второй; если второй операнд есть 0, то результат не определен. В противном случае всегда выполняется соотношение: (a/b)*b + a%b равняется a. Если оба операнда не отрицательные, то остаток не отрицательный и меньше делителя; в противном случае гарантируется только, что абсолютное значение остатка меньше абсолютного значения делителя.

A7.7. Аддитивные операторы

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

аддитивное_выражение:
мультипликативное_выражение
аддитивное_выражение + мультипликативное_выражение
аддитивное_выражение - мультипликативное_выражение

Результат выполнения оператора + есть сумма его операндов. Указатель на объект в массиве можно складывать с целочисленным значением. При этом последнее преобразуется в адресное смещение посредством умножения его на размер объекта, на который ссылается указатель. Сумма является указателем на объект того же типа; только ссылается этот указатель на другой объект того же массива, отстоящий от первоначального соответственно вычисленному смещению. Так, если P — указатель на объект в массиве, то P+1 — указатель на его следующий объект. Если полученный в результате суммирования указатель выводит за границы массива, то за исключением случая, когда он ссылается на место, находящееся непосредственно за концом массива, результат будет неопределенным.

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

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

Если к двум указателям на объекты одного и того же типа применить оператор вычитания, то в результате получится целочисленное значение со знаком, представляющее собой расстояние между объектами, на которые ссылаются эти указатели; указатель на следующий объект на 1 больше указателя на предыдущий объект. Тип результата зависит от реализации; в стандартном головном файле <stddef.h> он определен под именем ptrdiff_t. Значение не определено, если указатели ссылаются на объекты не одного и того же массива; однако если P указывает на последний элемент массива, то (P+1)-P имеет значение, равное 1.

A7.8. Операторы сдвига

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

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

Значение E1 << E2 равно значению E1 (рассматриваемому как цепочка битов), сдвинутому влево на E2 бит; при отсутствии переполнения такая операция эквивалентна умножению на 2E2. Значение E1 >> E2 равно значению E1, сдвинутому вправо на E2 битовые позиции. Если E1 беззнаковое или имеет неотрицательное значение, то правый сдвиг эквивалентен делению на 2E2, в противном случае результат зависит от реализация.

A7.9. Операторы отношения

Операторы отношения выполняются слева направо, однако это свойство едва ли может оказаться полезным; согласно грамматике языка выражение a<b<c трактуется так же, как (a<b)<c, а результат вычисления a<b всегда есть 0 или 1.

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

Операторы: < (меньше), > (больше), <= (меньше или равно) и >= (больше пли равно) — все выдают 0, если специфицируемое отношение ложно, и 1, если оно истинно. Тип результата — int. Над арифметическими операндами выполняются обычные арифметические преобразования. Можно сравнивать указатели на объекты одного и того же (без учета квалификаторов) типа; результат будет зависеть от их относительного расположения в памяти. Допускается, однако, сравнение указателей на разные части одного и того же объекта: если два указателя ссылаются на один и тот же простой объект, то они равны; если они ссылаются на члены одной структуры, то указатель на член с более поздней декларацией в структуре больше: если указатели ссылаются на члены одного и того же объединения, то они равны; если указатели ссылаются на элементы некоторого массива, то сравнение этих указателей эквивалентно сравнению их индексов. Если P указывает на последний элемент массива, то P+1 больше, чем P, хотя P+1 «выводит» нас за границы массива. В остальных случаях результат сравнения не определен.

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

A7.10. Операторы равенства

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

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

Операторы равенства подчиняются тем же правилам, что и операторы отношения. И кроме того, они дают возможность сравнивать указатель с целочисленным константным выражением, значение которого равно нулю, и с указателем на void. (см. A6.6.)

A7.11. Оператор побитового И

И_выражение:
выражение_равенства
И_выражение & выражение_равенства

Выполняются обычные арифметические преобразования; результат — побитовое И операндов. Оператор применяется только к целочисленным операндам.

A7.12. Оператор побитового исключающего ИЛИ

исключающее_ИЛИ_выражение:
И_выражение
исключающее_ИЛИ_выражение ^ И_выражение

Выполняются обычные арифметические преобразования; результат — побитовое исключающее ИЛИ операндов. Оператор применяется только к целочисленным операндам.

A7.13. Оператор побитового ИЛИ

ИЛИ_выражение:
исключающее_ИЛИ_выражение
ИЛИ_выражение | исключающее_ИЛИ_выражение

Выполняются обычные арифметические преобразования; результат — побитовое ИЛИ операндов. Оператор применяется только к целочисленным операндам.

A7.14. Оператор логического И
логическое_И_выражение:
ИЛИ_выражение
логическое_И_выражение && ИЛИ_выражение

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

Операнды могут принадлежать разным типам, но либо операнд должен иметь арифметический тип, либо быть указателем. Тип результата — int.

A7.15. Оператор логического ИЛИ

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

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

Операнды могут принадлежать разным типам, но либо операнд должен иметь арифметический тип, либо быть указателем. Тип результата — int.

A7.16. Условный оператор

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

Вычисляется первое выражение, включая все побочные эффекты; если оно не равно 0, то результат есть значение второго выражения, в противном случае — значение третьего выражения. Вычисляется только один из двух последних операндов: второй или третий. Если второй и третий операнды арифметические, то выполняются обычные арифметические преобразования, приводящие к некоторому общему типу, который и будет типом результата. Если оба операнда имеют тип void, или являются структурами или объединениями одного и того же типа, или представляют собой указатели на объекты одного и того же типа, то результат будет иметь тот же тип, что и операнды. Если один из операндов имеет тип «указатель», а другой является константой 0, то 0 приводится к типу «указатель», этот же тип будет иметь и результат. Если один операнд является указателем на void, а второй — указатель другого типа, то последний преобразуется в указатель на void, который и будет типом результата.

При сравнении типов указателей квалификаторы типов (A8.2) объектов, на которые указатели ссылаются, во внимание не принимаются, но тип результата наследует квалификаторы обеих ветвей условного выражения.

A7.17. Выражения присваивания

Существует несколько операторов присваивания; они выполняются справа налево.

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

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

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

Выражение E1 op= E2 эквивалентно выражению E1 = E1 op (E2) с одним исключением: E1 вычисляется только один раз.

A7.18. Оператор запятая

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

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

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

три аргумента, из которых второй имеет значение 5.

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

Синтаксически, константное выражение — это выражение с ограниченным подмножеством операторов:

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

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

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

Для константных выражений в инициализаторах допускается большая свобода; операндами могут быть константы любого типа, а к внешним или статическим объектам и внешним и статическим массивам, индексируемым константными выражениями, возможно применять унарный оператор &. Унарный оператор & может также неявно «присутствовать» при использовании массива без индекса или функции без списка аргументов. Вычисление инициализатора должно давать константу или адрес ранее декларированного внешнего или статического объекта плюс-минус константа.

Меньшая свобода допускается для целочисленных константных выражений, используемых после #if: не разрешаются sizeof-выражения, перечислимые константы и операции приведения типа. (См. A12.5.)

A8. Декларации

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

декларация:
спецификаторы_декларации список_иниц_деклараторовнеоб;

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

спецификаторы_декларации:
спецификатор_класса_памяти спецификаторы_декларациинеоб
спецификатор_типа спецификаторы_декларациинеоб
квалификатор_типа спецификаторы_декларациинеоб
список_иниц_деклараторов:
иниц_декларатор
список_иниц_деклараторов , иниц_декларатор
иниц_декларатор:
декларатор
декларатор = инициализатор

Деклараторы содержат подлежащие описанию имена. Мы рассмотрим их позже, в A8.5. Либо декларация должна иметь по крайней мере один декларатор, либо ее спецификатор типа должен определять тег структуры или объединения, либо — задавать члены перечисления; пустая декларация незаконна.

A8.1. Спецификаторы класса памяти

Класс памяти специфицируется следующим образом:

спецификатор_класса_памяти:
auto
register
static
extern
typedef

Смысл классов памяти обсуждался в A4.

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

Новым является правило, согласно которому вычислять адрес объекта класса register нельзя, а класса auto можно.

Спецификатор static дает декларируемым объектам класс статической памяти, он может использоваться и внутри, и вне функций. Внутри функции этот спецификатор вызывает выделение памяти и служит определением; его роль вне функций будет объяснена в A11.2.

Декларация со спецификатором external, используемая внутри функции, объявляет, что для декларируемого объекта где-то выделена память; о ее роли вне функций будет сказано в A11.2.

Спецификатор typedef не резервирует никакой памяти и назван спецификатором класса памяти из соображений стандартности синтаксиса; речь об этом спецификаторе пойдет в A8.9.

Декларация может содержать не более одного спецификатора класса памяти. Если он в декларации отсутствует, то действуют следующие правила: считается, что объекты, декларируемые внутри функций, имеют класс auto; функции, декларируемые внутри функций, — класс extern; объекты и функции, декларируемые вне функций, — статические и имеют внешние связи (A10, A11).

A8.2. Спецификаторы типа

Спецификаторы типа определяются следующим образом:

Спецификатор_типа:
void
char
short
int
long
float
double
signed
unsigned
структ_или_объед_спецификатор
enum-спецификатор
typedef-имя

Вместе с int допускается использование еще какого-то одного слова — long или short; причем сочетание long int имеет тот же смысл, что и просто long; аналогично short int — то же самое, что и short. Слово long может употребляться вместе с double. С int и другими его модификациями (short, long или char) разрешается употреблять одно из слов signed или unsigned. Любое из последних может использоваться самостоятельно, в этом случае подразумевается int. Спецификатор signed бывает полезен, когда требуется обеспечить, чтобы char-объекты имели знак; его можно применять и к другим целочисленным типам, но в этих случаях он избыточен.

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

Для указания особых свойств декларируемых объектов предназначаются квалификаторы:

квалификатор_типа:
const
volatile

Квалификаторы типа могут употребляться с любым спецификатором типа. const-объект разрешается инициализировать, однако присваивать ему что-либо в дальнейшем запрещается. Смысл квалификатора volatile зависит от реализации.

Средства const и volatile (изменчивый) введены ANSI-стандартом. Квалификатор const применяется, чтобы разместить объекты в памяти, открытой только на чтение, или чтобы способствовать возможной оптимизации. Назначение квалификатора volatile — подавить оптимизацию, которая без этого указания могла бы возникнуть. Например, в машинах, где адреса регистров ввода-вывода отображены на адресное пространство памяти, указатель на регистр некоторого устройства мог бы быть декларирован как volatile, чтобы запретить компилятору экономить очевидно избыточную ссылку через указатель. Компилятор может игнорировать указанные квалификаторы, однако обязан сигнализировать о явных попытках изменить значение const-объектов.

A8.3. Декларации структур и объединений

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

структ_или_объед_спецификатор:
структ_или_объед идентификаторнеоб { список_структ_деклараций }
структ_или_объед идентификатор
структ_или_объед:
struct
union

Список_структ_деклараций является последовательностью деклараций членов структуры или объединения:

список_структ_деклараций:
структ_декларация
список_структ_деклараций структ_декларация
структ_декларация:
список_спецификаторов_квалификаторов список_структ_деклараторов;
список_спецификаторов_квалификаторов:
спецификатор_типа список_спецификаторов_квалификаторовнеоб
квалификатор_типа сносок_спецификаторов_квалификаторовнеоб
список_структ_деклараторов:
структ_декларатор
список_структ_деклараторов , структ_декларатор

Обычно структ_декларатор — это просто декларатор члена структуры или объединения. Членом структуры может быть также некоторая последовательность битов заданной длины. Такой член называется полем_бит или просто полем; указатель его длины отделяется от декларатора имени поля двоеточием.

структ_декларатор:
декларатор
деклараторнеоб : константное_выражение

Спецификатор типа, имеющий вид

структ_или_объед идентификатор { список_структ_деклараций }

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

структ_или_объед идентификатор

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

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

В отношении деклараций вида

структ_или_объед идентификатор;

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

Это «неясное» правило введено ANSI-стандартом. Оно позволяет во внутренней области действия программы создавать «взаимно-рекурсивные» декларации структур с тегами, которые уже могли быть объявлены во внешней области действия.

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

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

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

Член (не поле) может быть объектом любого типа. Поле (которое может и не иметь декларатора и, следовательно, быть без имени) имеет тип int, unsigned int или signed int и интерпретируется как объект целочисленного типа с заданным числом битов; рассматривается ли поле типа int как знаковое, зависит от реализации. Также от реализации зависит способ и порядок «упаковки» соседних полей в памяти. Если поле, следующее за другим полем, не помещается в частично заполненной ячейке памяти, то либо оно будет разрезано на частя, которые попадут в разные ячейки, либо в структуре возникнут пропуски из неиспользуемых разрядов. Поле без имени длины 0 предписывает пропустить оставшиеся разряды и следующее поле расположить с начала ячейки памяти.

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

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

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

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

struct tnode {
    char tword[20];
    int count;
    struct tnode *left;
    struct tnode *right;
};

Если после указанной декларации поместить запись

struct tnode s, *sp;

то она будет определять s как структуру заданного вида, а sp — как указатель на такую структуру. Согласно приведенным определениям выражение

sp->count

есть ссылка на член count в структуре, на которую указывает sp;

s.left

— указатель на левое поддерево в структуре s; а

s.right->tword[0]

есть первая литера из tword — члена правого поддерева s.

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

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) ...

A8.4. Перечисления

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

переч_спецификатор:
enum идентификаторнеоб { список_перечислителей }
enum идентификатор
список_перечислителей:
перечислитель
список_перечислителей , перечислитель
перечислитель:
идентификатор
идентификатор = константное_выражение

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

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

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

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

A8.5. Деклараторы

Деклараторы имеют следующий синтаксис:

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

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

A8.6. Что означают деклараторы

Список деклараторов располагается сразу после спецификаторов типа и указателя класса памяти. Главный элемент любого декларатора — это объявляемый им идентификатор; в простейшем случае декларатор из одного его и состоит, что отражено в первой строке продукции грамматики с именем собственно_декларатор. Спецификаторы класса памяти относятся непосредственно к идентификатору, а его тип зависит от вида декларатора. Декларатор следует воспринимать как утверждение: если в выражении идентификатор появляется в том же контексте, что и в деклараторе, то он обозначает объект специфицируемого типа.

Если соединить спецификаторы декларации, относящиеся к типу (A8.2), и некоторый конкретный декларатор, то декларация примет следующий вид: «T D», где T — тип, а D — декларатор. Эта запись описывает тип для идентификатора любого декларатора индуктивно.

В декларации T D, где D — просто идентификатор, тип идентификатора есть T.

В декларации T D, где D имеет вид

( D1 )

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

A8.6.1. Деклараторы указателей

В декларации T D, где D имеет вид

* список_квалификаторов_типа D1

а тип идентификатора декларации T D1 есть «модификатор_типа T», тип идентификатора D есть «модификатор_типа список_квалификаторов типа указатель на T». Квалификаторы, следующие за *, относятся к самому указателю, а не к объекту, на который он указывает.

Рассмотрим, например, декларацию

int *ap[];

Здесь ap[] играет роль D1; декларацию «int ap[]» следует расшифровать (см. ниже) как «массив из int»; список квалификаторов типа здесь пуст, а модификатор типа есть «массив из». Следовательно, на самом деле декларация ap гласит: «массив из указателей на int».

Вот еще примеры деклараций:

int i, *pi, *const cpi = &i;
const int ci = 3, *pci;

В них объявляются целое i и указатель на целое pi. Значение указателя cpi неизменно; cpi всегда будет указывать в одно и то же место, даже если значение, на которое он ссылается, станет иным. Целое ci есть константа, оно изменяться не может (хотя может инициализироваться, как в данном случае). Тип указателя pci произносится как «указатель на const int»; сам указатель возможно изменить; при этом он будет ссылаться на другое место, но значение, на которое он будет указывать, посредством pci изменить нельзя.

A8.6.2. Деклараторы массивов

В декларации T D, где D имеет вид

D1 [ константное_выражение ]

и где тип идентификатора декларации T D1 есть «модификатор_типа T», тип идентификатора D есть «модификатор_типа массив из T». Если константное выражение присутствует, то оно должно быть целочисленным и больше 0. Если константное выражение, специфицирующее количество элементов в массиве, отсутствует, то массив имеет незавершенный тип.

Массив можно конструировать из объектов арифметического типа, указателей, структур и объединений, а также других массивов (генерируя при этом многомерные массивы). Любой тип, из которого конструируется массив, должен быть завершенным, он не может быть, например, структурой или массивом незавершенного типа. Это значит, что для многомерного массива пустой может быть только первая размерность. Незавершенный тип массива получает свое завершение либо в другой декларации этого массива (A10.2), либо при его инициализации (A8.7). Например, запись

float fa[17], *afp[17];

декларирует массив из float-чисел и массив из указателей на float-числа. Аналогично

static int x3d[3][5][7];

декларирует статический трехмерный массив целых размера 3x5x7. На самом деле, если быть точными, x3d является массивом из трех элементов, каждый из которых есть массив из пяти элементов, содержащих по 7 целых.

Операция индексирования E1[E2] определена так, что она идентична операции *(E1+E2). Следовательно, несмотря на асимметричность записи, индексирование — коммутативная операция. Учитывая правила преобразования, применяемые для оператора + и массивов (A6.6, A7.1, A7.7), можно сказать, что если E1 — массив, а E2 — целое, то E1[E2] обозначает E2-й элемент массива E1.

Так, x3d[i][j][k] означает то же самое, что и *(x3d[i][j] + k). Первое подвыражение, x3d[i]][j], согласно A7.1 приводится к типу «указатель на массив целых»; по A7.7 сложение включает умножение на размер объекта типа int. Из этих же правил следует, что массивы запоминаются «построчно» (последние индексы меняются чаще) и что первая размерность в декларации помогает определить количество памяти, занимаемой массивом, однако в вычислении адреса элемента массива участия не принимает.

A8.6.3. Деклараторы функций

При новом способе декларация функции T D, где D имеет вид

D1 ( список_типов_параметров )

и тип идентификатора декларации T D1 есть «модификатор_типа T», тип идентификатора в D есть «модификатор_типа функция с аргументами список_типов_параметров, возвращающая T».

Параметры имеют следующий синтаксис:

список_типов_параметров:
список_параметров
список_параметров , ...
список_параметров:
декларация_параметра
список_параметров , декларация_параметра
декларация_параметра:
спецификаторы_декларации декларатор
спецификаторы_декларации абстрактный_деклараторнеоб

При новом способе описания функций список параметров специфицирует их типы, а если функция вообще не имеет параметров, на месте списка типов указывается одно слово — void. Если список типов параметров заканчивается многоточием «, ...», то функция может иметь больше аргументов, чем число явно описанных параметров. (См. A7.3.2.)

Типы параметров, являющихся массивами и функциями, заменяются на указатели в соответствии с правилами преобразования параметров (A10.1). Единственный спецификатор класса памяти, который разрешен в декларации параметра, — это register, однако он игнорируется, если декларатор функции не является заголовком ее определения. Аналогично, если деклараторы в декларациях параметров содержат идентификаторы, а декларатор функции не является заголовком определения функции, то эти идентификаторы тотчас же выводятся из текущей области действия.

При старом способе декларация функции T D, где D имеет вид

D1 ( список_идентификаторов )

и тип идентификатора декларации T D1 есть «модификатор_типа T», тип идентификатора в D есть «модификатор_типа функция от неспецифицированных аргументов, возвращающая T». Параметры, если они есть, имеют следующий вид:

список_идентификаторов:
идентификатор
список_идентификаторов , идентификатор

При старом способе, если декларатор функции не используется в качестве заголовка определения функции (A10.1), список идентификаторов должен отсутствовать. Никакой информации о типах параметров в декларации не содержится.

Например, декларация

int f(), *fpi(), (*pfi)();

описывает функцию f, возвращающую целое, функцию fpi, возвращающую указатель на целое, и указатель pfi на функцию, возвращающую целое. Ни для одной функции в декларации не указаны типы параметров; все функции описаны старым способом.

Вот как выглядит декларация в новой записи:

int strcpy(char *dest, const char *source), rand(void);

Здесь strcpy — функция с двумя аргументами, возвращающая значение типа int; первый аргумент — указатель на значение типа char, а второй — указатель на неизменяющееся значение типа char. Имена параметров играют роль хороших комментариев. Вторая функция, rand, аргументов не имеет и возвращает int.

Прототипы деклараторов функций с параметрами — наиболее важное нововведение ANSI-стандарта. В сравнении со старым способом, принятым в первой редакции языка, они позволяют проверять и приводить к нужному типу аргументы во всех вызовах. Следует однако отметить, что их введение привнесло в язык некоторую сумятицу и необходимость согласования обеих форм. Чтобы обеспечить совместимость, потребовались некоторые «синтаксические уродства» типа void для явного указания на отсутствие параметров.

Многоточие «, ...» применительно к функциям с варьируемым числом аргументов — также новинка, которая вместе со стандартным головным файлом макросов <stdarg.h> формализует неофициально используемый, но официально запрещенный в первой редакции механизм.

Указанные способы записи заимствованы из языка Си++.

A8.7. Инициализация

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

инициализатор:
выражение_присваивания
{ список_инициализаторов }
{ список_инициализаторов , }
список_инициализаторов:
инициализатор
список_инициализаторов , инициализатор

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

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

Статический объект, инициализация которого явно не указана, инициализируется так, как если бы ему (или его членам) присваивалась константа 0. Начальное значение автоматического объекта, явным образом не инициализированного, не определено.

Инициализатор указателя или объекта арифметического типа — это единичное выражение (возможно, заключенное в фигурные скобки), которое присваивается объекту.

Инициализатор структуры — это либо выражение того же структурного типа, либо заключенные в фигурные скобки инициализаторы ее членов, заданные по порядку. Безымянные поля битов игнорируются и не инициализируются. Если инициализаторов в списке меньше, чем членов, то оставшиеся члены инициализируются нулем. Инициализаторов не должно быть больше числа членов.

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

Как особый выделен случай инициализации массива литер. Последний можно инициализировать при помощи стрингового литерала; литеры инициализируют члены массива в том порядке, в каком они заданы в стринговом литерале. Точно так же, при помощи литерала из расширенного набора литер (A2.6), можно инициализировать массив типа wchar_t. Если размер массива не известен, то он определяется числом литер стринга, включающим и завершающую null-литеру; если размер массива известен, то число литер стринга, не считая завершающей null-литеры, не должно превышать его размера.

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

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

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

Например,

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][0] и y[0][2]. Аналогично инициализируются следующие две строки: y[1] и y[2]. Инициализаторов не хватило на весь массив, поэтому элементы строки y[3] будут нулевыми. В точности тот же результат был бы достигнут при помощи следующей декларации:

float y[4][3] = {
    1, 3, 5, 2, 4, 6, 3, 5, 7
};

Инициализатор для y начинается с левой фигурной скобки, но для y[0] скобки нет, поэтому из списка будут взяты три элемента. Аналогично по три элемента будут взяты для y[1], а затем и для y[2]. В

float y[4][3] = {
    { 1 }, { 2 }, { 3 }, { 4 }
};

инициализируется первый столбец матрицы y, все же другие элементы остаются нулевыми.

Наконец,

char msg[] = "Синтаксическая ошибка в строке %s\n";

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

A8.8. Имена типов

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

имя_типа:
список_спецификаторов_квалификаторов абстрактный_деклараторнеоб
абстрактный_декларатор:
указатель
указательнеоб собственно_абстрактный_декларатор
собственно_абстрактный_декларатор:
( абстрактный_декларатор )
собственно_абстрактный_деклараторнеоб [ константное_выражениенеоб ]
собственно_абстрактный_деклараторнеоб ( список_типов_параметровнеоб )

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

int
int *
int *[3]
int (*)[]
int *()
int (*[])(void)

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

A8.9. Декларация typedef

Декларации, в которых спецификатор класса памяти есть typedef, не декларируют объекты — они определяют идентификаторы, представляющие собой имена типов. Эти идентификаторы называются typedef-именами.

typedef-имя:
идентификатор

Декларация typedef приписывает тип каждому имени своего декларатора обычным способом. (См. A8.6.) С этого момента typedef-имя синтаксически эквивалентно ключевому слову спецификатора типа, обозначающему связанный с ним тип. Например, после

typedef long Blockno, *Blockptr;
typedef struct { double r, theta; } Complex;

допустимы следующие декларации:

Blockno b;
extern Blockptr bp;
Complex z, *zp;

Тип b есть long, тип bp — «указатель на long», тип z — структура заданного вида, а zp — указатель на такую структуру.

Декларация typedef не вводит новых типов, она только дает имена типам, которые могли бы быть специфицированы и другим способом. Например, b имеет тот же тип, что и любой другой long-объект.

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

extern Blockno;

не переопределяет Blockno, а вот

extern int Blockno;

переопределяет.

A8.10. Эквивалентность типов

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

Два типа считаются совпадающими, если их абстрактные деклараторы (A8.8) после замены всех typedef-имен их типами и выбрасывания имен параметров функций составят эквивалентные списки спецификаторов типов. При сравнении учитываются размеры массивов и типы параметров функций.

A9. Инструкции

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

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

A9.1. Помеченные инструкции

Инструкции может предшествовать метка.

помеченная_инструкция:
идентификатор : инструкция
case константное_выражение : инструкция
default : инструкция

Метка, состоящая из идентификатора, одновременно служит и декларацией этого идентификатора. Единственное назначение идентификатора_метки — указать место перехода для goto. Областью действия идентификатора_метки является текущая функция. Так как метки имеют свое собственное пространство имен, они не «конфликтуют» с другими идентификаторами и не могут быть перекрыты. (См. A11.1.)

case-метки и default-метки используются в инструкции switch (A9.4). Константное выражение в case должно быть целочисленным.

Сами по себе метки не изменяют порядка вычислений.

A9.2. Инструкция–выражение

Наиболее употребительный вид инструкции — это инструкция-выражение.

инструкция_выражение:
выражениенеоб ;

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

A9.3. Составная инструкция

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

составная_инструкция:
{ список_декларацийнеоб список_инструкцийнеоб }
список_декларации:
декларация
список_деклараций декларация
список_инструкций:
инструкция
список_инструкций инструкция

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

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

A9.4. Инструкции выбора

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

инструкция_выбора:
if ( выражение ) инструкция
if ( выражение ) инструкция else инструкция
switch ( выражение ) инструкция

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

Инструкция switch вызывает передачу управления на одну из нескольких инструкций в зависимости от значения выражения, которое должно иметь целочисленный тип. Управляемая при помощи switch подинструкция обычно составная. Любая инструкция внутри этой подинструкции может быть помечена одной или несколькими case-метками (A9.1). Управляющее выражение подвергается целочисленному повышению (A6.1), а case-константы приводятся к повышенному типу. После такого преобразования никакие две case-константы в одной инструкции switch не должны иметь одинаковых значений. Со switch-инструкцией может быть связано не более одной default-метки. Конструкции switch допускается вкладывать друг в друга; case и default-метки относятся к самой внутренней switch-инструкции из тех, которые их содержат.

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

В первой редакции требовалось, чтобы выражение и case-константы в switch были целого типа.

A9.5. Циклические инструкции

Циклические инструкции специфицируют циклы.

циклическая_инструкция:
while ( выражение ) инструкция
do инструкция while ( выражение )
for ( выражениенеоб ; выражениенеоб ; выражениенеоб ) инструкция

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

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

for ( выражение1 ; выражение2 ; выражение3 ) инструкция

эквивалентна конструкции

выражение1 ; while ( выражение2 ) { инструкция выражение3 ; }

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

A9.6. Инструкции перехода

Инструкции перехода осуществляют безусловную передачу управления.

инструкция_перехода:
goto идентификатор ;
continue ;
break ;
return выражениенеоб ;

В goto-инструкции идентификатор должен быть меткой (A9.1), расположенной в текущей функции. Управление передается на помеченную инструкцию.

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

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

инструкция continue, если она не «погружена» в еще более внутренний цикл, делает то же самое, что и goto contin.

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

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

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

A10. Внешние декларации

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

компонента_трансляции:
внешняя_декларация
компонента_трансляции внешняя_декларация
внешняя_декларация:
определение_функции
декларация

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

A10.1. Определение функции

Определение функции имеет следующий вид:

определение_функции:
спецификаторы_декларациинеоб декларатор список_декларацийнеоб составная_инструкция

Из спецификаторов класса памяти в спецификаторах_декларации возможны только extern и static; различия между последними рассматриваются в A11.2.

Типом возвращаемого функцией значения может быть арифметический тип, структура, объединение, указатель и void, но не «функция» и не «массив». Декларатор в декларации функции должен явно указывать на то, что описываемый им идентификатор имеет тип «функция», т.е. он должен иметь одну из следующих двух форм (A8.6.3):

собственно_декларатор ( список_типов_параметров )
собственно_декларатор ( список_идентификаторов )

где собственно_декларатор есть идентификатор или идентификатор, заключенный в скобки. Заметим, что на тип «функция» посредством typedef ссылаться нельзя.

Первая форма соответствует определению функции новым способом, для которого характерно декларирование параметров в списке_типов_параметров вместе с их типами; список_деклараций, располагаемый за деклараторам, должен отсутствовать. Если список_типов_параметров не состоит из одного-единственного слова void, показывающего, что параметров у функции нет, то в каждом деклараторе в списке_типов_параметров обязан присутствовать идентификатор. Если список_типов_параметров заканчивается знаками «, ...», то вызов функции может иметь аргументов больше, чем параметров; в таком случае, чтобы ссылаться на дополнительные аргументы, следует пользоваться механизмом макроса va_arg из головного файла <stdarg.h>, описанного в приложении B. Функции с переменным числом аргументов должны иметь по крайней мере один именованный параметр.

Вторая форма — определение функции старым способом. Список_идентификаторов содержит имена параметров, а список_деклараций приписывает им типы. В списке_деклараций разрешено декларировать только именованные параметры, инициализация запрещается, и из спецификаторов класса памяти возможен только register.

И в том и другом способе определения функции мыслится, что все параметры как бы декларированы в самом начале составной инструкции, образующей тело функции, и совпадающие с ними имена здесь декларироваться не должны (хотя, как и любые идентификаторы, их можно переопределить в более внутренних блоках). Декларацию параметра «массив из тип» можно трактовать, как «указатель на тип»; аналогично декларацию параметра «функция, возвращающая тип» — как «указатель на функцию, возвращающую тип». В момент вызова функции ее аргументы соответствующим образом преобразуются и присваиваются параметрам. (См. A7.3.2.).

Новый способ определения функций введен ANSI-стандартом. Есть также небольшие изменения в операции повышения типа; в первой редакции параметры типа float следовало читать как double. Различие между float и double становилось заметным, лишь когда внутри функции генерировался указатель на параметр.

Ниже приведен пример определения функции новым способом:

int max(int a, int b, int c)
{
    int m;

    m = (a > b) ? a : b;

    return (m > c) ? m : c;
}

Здесь int — спецификаторы_декларации; max(int a, int b, int c) — декларатор функции, а { ... } — блок, задающий ее код. Определение старым способом той же функции выглядит следующим образом:

int max(a, b, c)
int a, b, c;
{
    /* ... */
}

где max(a, b, c) — декларатор, а int a, b, c — список_деклараций для параметров.

A10.2. Внешние декларации

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

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

Две декларации объекта или функции считаются согласованными по типу в соответствии с правилами, рассмотренными в A8.10. Кроме того, если декларации отличаются лишь тем, что в одной из них тип структуры, объединения иди перечисления незавершен (A8.3), а в другой соответствующий ему тип с тем же тегом завершен, то такие типы считаются согласованными. Если два типа массива (8.6.2) отличаются лишь тем, что один завершенный, а другой незавершенный, то такие типы также считаются согласованными. Наконец, если один тип специфицирует функцию старым способом, а другой — ту же функцию новым способом (с декларациями параметров), то такие типы также считаются согласованными.

Если первая внешняя декларация функции или объекта помечена спецификатором static, то у декларируемого идентификатора — внутренняя связь; в противном случае у него — внешняя связь. Способы связей обсуждаются в A11.2.

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

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

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

A11. Область действия и связи

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

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

A11.1. Лексическая область действия

Каждый идентификатор попадает в одно из нескольких пространств имен. Эти пространства никак не связаны друг с другом. Один и тот же идентификатор может использоваться в разных смыслах даже в одной области действия, если он принадлежит разным пространствам имен. Ниже через точку с запятой перечислены классы объектов, имена которых представляют собой отдельные независимые пространства: объекты, функции, typedef-имена и enum-константы; метки инструкций; теги структур, объединений и перечислений; члены каждой отдельной структуры или объединения.

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

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

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

A11.2. Связи

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

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

A12. Препроцессирование

Препроцессор выполняет макроподстановку, условную компиляцию, включение именованных файлов. Строки, начинающиеся со знака # (перед которым возможны пробельные литеры), устанавливают связь с препроцессором. Их синтаксис не зависит от остальной части языка; они могут появляться где угодно и оказывать влияние (независимо от области действия) вплоть до конца транслируемой компоненты. Границы строк принимаются во внимание; каждая строка анализируется отдельно (однако есть возможность «склеивать» строки, см. A12.2). Лексемами для препроцессора являются все лексемы языка и последовательности литер, задающие имена файлов, как, например, в директиве #include (A12.4). Кроме того, любая литера, не определенная каким-либо другим способом, воспринимается как лексема. Влияние пробельных литер, отличающихся от пробелов и горизонтальных табуляций, внутри строк препроцессора не определено.

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

  1. Трехзнаковые последовательности, описанные в A12.1, заменяются их эквивалентами. Между строками вставляются литеры новая_строка, если того требует операционная система.
  2. Выбрасываются пары литер, состоящие из обратной наклонной черты с последующей литерой новая_строка; тем самым осуществляется «склеивание» строк (A12.2).
  3. Программа разбивается на лексемы, разделенные литерами пропусков. Комментарии заменяются на единичные пробелы. Затем выполняются директивы препроцессора и макроподстановки (A12.3A12.10).
  4. Эскейп-последовательности в литерных константах и стринговых литералах (A2.5.2, A2.6) заменяются на литеры, которые они обозначают. Соседние стринговые литералы конкатенируются.
  5. Результат транслируется. Затем устанавливаются связи с другими программами и библиотеками посредством сбора необходимых программ и данных и соединения ссылок на внешние функции и объекты с их определениями.

A12.1. Трехзнаковые последовательности

Множество литер, из которых набираются исходные Си-программы, основано на семибитовом ASCII-коде. Однако он шире, чем инвариантный код литер ISO 646-1983 (ISO 646-1983 Invariant Code Set). Чтобы дать возможность пользоваться сокращенным набором литер, все указанные ниже трехзнаковые последовательности заменяются на соответствующие им единичные литеры. Замена осуществляется до любой иной обработки.

??= #
??( [
??< {
??/ \
??) ]
??> }
??' ^
??! |
??- ~

Никакие другие замены, кроме указанных, не делаются.

Трехзнаковые последовательности введены ANSI-стандартом.

A12.2. Склеивание строк

Строка, заканчивающаяся обратной наклонной чертой, соединяется со следующей, поскольку литера \ и следующая за ней литера новая_строка выбрасываются. Это делается перед «разбиением» текста на лексемы.

A12.3. Макроопределение и макрорасширение

Управляющая строка вида

# define идентификатор последовательность_лексем

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

Строка вида

# define идентификатор( список_идентификаторов ) последовательность_лексем

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

Управляющая строка вида

# undef идентификатор

предписывает препроцессору «забыть» определение, данное идентификатору. Применение #undef к неизвестному идентификатору ошибкой не считается.

Если макроопределение было задано вторым способом, то текстуальная последовательность, состоящая из его идентификатора с, возможно, следующими за ним пробельными литерами, знака (, списка лексем, разделенных запятыми, и знака ), представляет собой вызов макроса. Аргументами вызова макроса являются лексемы, разделенные запятыми (запятые, «закрытые» кавычками или вложенными скобками, в разделении аргументов не участвуют). Аргументы при их выделении макрорасширениям не подвергаются. Количество аргументов в вызове макроса должно соответствовать количеству параметров макроопределения. После выделения аргументов окружающие их пробельные литеры выбрасываются. Затем в замещающей последовательности лексем макроса идентификаторы — параметры (если они не окружены кавычками) заменяются на соответствующие им аргументы. Если в замещающей последовательности перед параметром не стоит знак # и ни перед ним, ни после него нет знака ##, то лексемы аргумента проверяются: не содержат ли они в себе макровызова, и если это так, то прежде чем аргумент будет подставлен, производится соответствующее ему макрорасширение.

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

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

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

Если полученное расширение начинается со знака #, оно не будет воспринято как директива препроцессора.

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

Описанные возможности можно использовать для показа смысловой сущности констант, как, например, в

#define TABSIZE 100
int table[TABSIZE];

Определение

#define ABSDIFF(a, b) ((a) > (b) ? (a)-(b) : (b)-(a))

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

Если имеется определение

#define tempfile(dir)   #dir  "/%s"

то макровызов tempfile(/usr/tmp) даст в результате

"/usr/tmp" "/%s"

Далее эти два стринга превратятся в один стринг. По макросу

#define cat(x, y)   x ## y

вызов cat(var, 123) сгенерирует var123. Однако cat(cat(1,2),3) не даст желаемого, так как оператор ## воспрепятствует получению правильных аргументов для внешнего вызова cat. В результате будет выдана следующая цепочка лексем:

cat ( 1 , 2 )3

где )3 (результат «склеивания» последней лексемы первого аргумента с первой лексемой второго аргумента) не является правильной лексемой. Если второй уровень макроопределения задан в виде

#define xcat(x, y)   cat(x, y)

то никаких коллизий здесь не возникает; xcat(xcat(1,2),3) в итоге даст 123, поскольку сам xcat не использует ##.

Аналогично сработает и ABSDIFF(ABSDIFF(a,b),c), и мы получим правильный результат.

A12.4. Включение файла

Управляющая строка

# include <имя_файла>

заменяется на содержимое файла с именем имя_файла. Среди литер, составляющих имя_файла, не должно быть знака > и литеры новая_строка. Результат не определен, если имя_файла содержит любую из литер ", ', \ или пару литер /*. Порядок поиска указанного файла зависит от реализации.

Подобным же образом выполняется управляющая строка

# include "имя_файла"

Сначала поиск осуществляется по тем же правилам, по каким компилятор ищет первоначальный исходный файл (механизм этого поиска зависит от реализации), а в случае неудачи осуществляется методом поиска, принятым в #include первого типа. Результат остается неопределенным, если имя файла содержит ', \ или /*; использование знака > разрешается.

Наконец, директива

# include последовательность_лексем

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

Файлы, вставляемые при помощи #include, сами могут содержать в себе директивы #include.

A12.5. Условная компиляция

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

условная_конструкция_препроцессора:
if-строка текст elif-части else-частьнеоб #endif
if-строка:
# if константное_выражение
# ifdef идентификатор
# ifndef идентификатор
elif-части:
elif-строка текст
elif-частинеоб
elif-строка:
# elif константное_выражение
else-часть:
else-строка текст
else-строка:
# else

Каждая из директив (if-строка, elif-строка и #endif) записывается на отдельной строке. Константные выражения в #if и последующих #elif-строках вычисляются по порядку, пока не обнаружится выражение с ненулевым значением; текст, следующий за строкой с нулевым значением, выбрасывается. Текст, расположенный за директивой с ненулевым значением, обрабатывается обычным образом. Под словом «текст» здесь имеется в виду любая последовательность строк, включая строки препроцессора, которые не являются частью условной структуры; текст может быть и пустым. Если #if или #elif-строка с ненулевым значением выражения найдена и ее текст обработан, то последующие #elif и #else-строки вместе со своими текстами выбрасываются. Если все выражения имеют нулевые значения и присутствует строка #else, то следующий за ней текст обрабатывается обычным образом. Тексты «неактивных» ветвей условных конструкций, за исключением тех, которые заведуют вложенностью условных конструкций, игнорируются.

Константные выражения в #if и #elif являются объектами для обычной макроподстановки. Более того, прежде чем просматривать выражения вида

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

и

defined ( идентификатор )

на предмет наличия в них макровызова, они заменяются на 1L или 0L в зависимости от того, был или не был определен препроцессором указанный в них идентификатор. Все идентификаторы, оставшиеся после макрорасширения, заменяются на 0L. Наконец, предполагается, что любая целая константа всегда имеет суффикс L, т.е. вся арифметика имеет дело с операндами только типа long или unsigned long.

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

Управляющие строки

#ifdef идентификатор
#ifndef идентификатор

эквивалентны соответственно строкам

# if defined идентификатор
# if ! defined идентификатор

Строки #elif не было в первой редакции, хотя она и использовалась в некоторых препроцессорах. Оператор препроцессора #defined — также новый.

A12.6. Нумерация строк

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

# line константа "идентификатор"
# line константа

которая предписывает компилятору «считать», что указанные десятичное целое и идентификатор являются номером следующей строки и именем текущего файла соответственно. Если имя файла отсутствует, то ранее запомненное имя не изменяется. Расширения макровызовов в line-директиве выполняются до интерпретации последней.

A12.7. Генерация сообщения об ошибке

Строка препроцессора вида

# error последовательность_лексем

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

A12.8. Прагма

Управляющая строка вида

# pragma последовательность_лексем
призывает препроцессор выполнить зависящие от реализации действия. Неопознанная прагма игнорируется.

A12.9. Пустая директива

Строка препроцессора вида

#

не вызывает никаких действий.

A12.10. Заранее определенные имена

Препроцессор «понимает» несколько заранее определенных идентификаторов; их он заменяет специальной информацией. Эти идентификаторы (и оператор препроцессора #defined в том числе) нельзя повторно переопределять, к ним нельзя также применять директиву #undef. Это следующие идентификаторы:

__LINE__ Номер текущей строки исходного текста, десятичная константа.
__FILE__ Имя компилируемого файла, стринг.
__DATE__ Дата компиляции в виде «Ммм дд гггг», стринг.
__TIME__ Время компиляции в виде «чч:мм:сс», стринг.
__STDC__ Константа 1. Предполагается, что этот идентификатор определен как 1 только в тех реализациях, которые следуют стандарту.

Строки #error и #pragma впервые введены ANSI-стандартом. Заранее определенные макросы препроцессора также до сих пор не описывались, хотя и использовались в некоторых реализациях.

A13. Грамматика

Ниже приведены сведенные воедино грамматические правила, описанные в данном приложении. Они имеют то же содержание, но даны в ином порядке.

Здесь не приводятся определения следующих терминальных символов: целая_константа, литерная_константа, с_плав_точкой_константа, идентификатор, стринг и перечислимая_константа. Слова, набранные обычным латинским шрифтом, и знаки рассматриваются как терминальные символы и используются буквально в том виде, как записаны. Данную грамматику можно механически трансформировать в текст, понятный системе автоматической генерации грамматического распознавателя. Для этого помимо добавления некоторых синтаксических пометок, предназначенных для указания альтернативных продукций, потребуется расшифровка конструкции со словами «один из» и дублирование каждой продукции, использующей символ с индексомнеоб, причем один вариант продукции должен быть написан с этим символом, а другой — без него. С одним изменением, а именно удалением продукции typedef-имя: идентификатор и объявлением typedef-имя терминальным символом, данная грамматика будет понятна генератору грамматического распознавателя YACC. Ей присуще лишь одно противоречие, вызываемое неоднозначностью if-else-конструкции.

компонента_трансляции:
внешняя_декларация
компонента_трансляции внешняя_декларация
внешняя_декларация:
определение_функции
декларация
определение функции:
спецификаторы_декларациинеоб декларатор
список_декларацийнеоб составная_инструкция
декларация:
спецификаторы_декларации список_иниц_деклараторовнеоб;
список_деклараций:
декларация
список_деклараций декларация
спецификаторы_декларации:
спецификатор_класса_памяти спецификаторы_декларациинеоб
спецификатор_типа спецификаторы_декларациинеоб
квалификатор_типа спецификаторы_декларациинеоб
спецификатор_класса_памяти: один из
auto
register
static
extern
typedef
спецификатор_типа: один из
void
char
short
int
long
float
double
signed
unsigned
структ_или_объед_спецификатор
enum-спецификатор
typedef-имя
квалификатор_типа: один из
const
volatile
структ_или_объед_спецификатор:
структ_или_объед идентификаторнеоб { список_структ_деклараций }
структ_или_объед идентификатор
структ_или_объед: один из
struct
union
список_структ_деклараций:
структ_декларация
список_структ_деклараций структ_декларация
список_иниц_деклараторов:
иниц_декларатор
список_иниц_деклараторов , иниц_декларатор
иниц_декларатор:
декларатор
декларатор = инициализатор
структ_декларация:
список_спецификаторов_квалификаторов список_структ_деклараторов ;
список_спецификаторов_квалификаторов:
спецификатор_типа список_спецификаторов_квалификаторовнеоб
квалификатор_типа список_спецификаторов_квалификаторовнеоб
список_структ_деклараторов:
структ_декларатор
список_структ_деклараторов , струк_декларатор
структ_декларатор:
декларатор
деклараторнеоб : константное_выражение
переч_спецификатор:
enum идентификаторнеоб { список_перечислителей }
enum идентификатор
список_перечислителей:
перечислитель
список_перечислителей , перечислитель
перечислитель:
идентификатор
идентификатор = константное_выражение
декларатор:
указательнеоб собственно_декларатор
собственно_декларатор:
идентификатор
( декларатор )
собственно_декларатор [ константное_выражениенеоб ]
собственно_декларатор ( сносок_типов_параметров )
собственно_декларатор ( список_идентификаторовнеоб )
указатель:
* список_квалификаторов_типанеоб
* список_квалификаторов_типанеоб указатель
список_квалификаторов_типа:
квалификатор_типа
список_квалификаторов_типа квалификатор_типа
список_типов_параметров:
список_параметров
список_параметров , ...
список_параметров:
декларация_параметра
список_параметров , декларация_параметра
декларация_параметра:
спецификаторы_декларации декларатор
спецификаторам_декларации абстрактный_деклараторнеоб
список_идентификаторов:
идентификатор
список_идентификаторов , идентификатор
инициализатор:
выражение_присваивания
{ список_инициализаторов }
{ список_инициализаторов , }
список_инициализаторов:
инициализатор
список_инициализаторов , инициализатор
имя_типа:
список_спецификаторов_квалификаторов абстрактный_деклараторнеоб
абстрактный_декларатор:
указатель
указательнеоб собственно_абстрактный_декларатор
собственно_абстрактный_декларатор:
( абстрактный_декларатор )
собственно_абстрактный_деклараторнеоб [ константное_выражениенеоб ]
собственно_абстрактный_деклараторнеоб ( список_типов_параметровнеоб )
typedef-имя:
идентификатор
инструкция:
помеченная_инструкция
инструкция_выражение
составная_инструкция
инструкция_выбора
циклическая_инструкция
инструкция_перехода
помеченная_инструкция:
идентификатор : инструкция
case константное_выражение : инструкция
default : инструкция
инструкция_выражение:
выражениенеоб ;
составная_инструкция:
{ список_декларацийнеоб список_инструкцийнеоб }
список_инструкций:
инструкция
список_инструкций инструкция
инструкция_выбора:
if ( выражение ) инструкция
if ( выражение ) инструкция else инструкция
switch ( выражение ) инструкция
циклическая_инструкция:
while ( выражение ) инструкция
do инструкция while ( выражение )
for ( выражениенеоб ; выражениенеоб ; выражениенеоб ) инструкция
инструкция_перехода:
goto идентификатор ;
continue ;
break ;
return выражениенеоб ;
выражение:
выражение_присваивания
выражение , выражение_присваивания
выражение_присваивания:
условное_выражение
унарное_выражение оператор_присваивания выражение_присваивания
оператор_присваивания: один из
= *= /= %= += == <<= >>= &= ^= |=
условное_выражение:
логическое_ИЛИ_выражение
логическое_ИЛИ_выражение ? выражение : условное_выражение
константное_выражение:
условное_выражение
логическое_ИЛИ_выражение:
логическое_И_выражение
логическое_ИЛИ_выражение || логическое_И_выражение
логическое_И_выражение:
ИЛИ_выражение
логическое_И_выражение && ИЛИ_выражение
ИЛИ_выражение:
исключающее_ИЛИ_выражение
ИЛИ_выражение | исключающее_ИЛИ_выражение
исключающее_ИЛИ_выражение:
И_выражение
исключающее_ИЛИ_выражение ^ И_выражение
И_выражение:
выражение_равенства
И_выражение & выражение_равенства
выражение_равенства:
выражение_отношения
выражение_равенства == выражение_отношения
выражение_равенства != выражение_отношения
выражение_отношения:
сдвиговое_выражение
выражение_отношения < сдвиговое_выражение
выражение_отношения > сдвиговое_выражение
выражение_отношения <= сдвиговое_выражение
выражение_отношения >= сдвиговое_выражение
сдвиговое_выражение:
аддитивное_выражение
сдвиговое_выражение >> аддитивное_выражение
сдвиговое_выражение << аддитивное_выражение
аддитивное_выражение:
мультипликативное_выражение
аддитивное_выражение + мультипликативное_выражение
аддитивное_выражение - мультипликативное_выражение
мультипликативное_выражение:
выражение_приведенное_к_типу
мультипликативное_выражение * выражение_приведенное_к_типу
мультипликативное_выражение / выражение_приведенное_к_типу
мультипликативное_выражение % выражение_приведенное_к_типу
выражение_приведенное_к_типу:
унарное_выражение
( имя_типа ) выражение_приведенное_к_типу
унарное_выражение:
постфиксное_выражение
++ унарное_выражение
-- унарное_выражение
унарный_оператор выражение_приведенное_к_типу
sizeof унарное_выражение
sizeof ( имя_типа )
унарный_оператор: один из
& * + - ~ !
постфиксное_выражение:
первичное_выражение
постфиксное_выражение [ выражение ]
постфиксное_выражение ( список_аргументов_выраженийнеоб)
постфиксное_выражение . идентификатор
постфиксное_выражение -> идентификатор
постфиксное_выражение ++
постфиксное_выражение --
первичное_выражение:
идентификатор
константа
стринг
( выражение )
список_аргументов_выражений:
выражение_присваивания
список_аргументов_выражений , выражение_присваивания
константа:
целая_константа
литерная_константа
с_плав_точкой_константа
перечислимая_константа

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

управляющая_строка:
# define идентификатор последовательность_лексем
# define идентификатор( идентификатор, ..., идентификатор ) последовательность_лексем
# undef идентификатор
# include <имя_файла>
# include "имя_файла"
# include последовательность_лексем
# line константа "идентификатор"
# line константа
# error последовательность_лексемнеоб
# pragma последовательность_лексемнеоб
условная_конструкция_препроцессора
условная_конструкция_препроцессора:
if-строка текст elif-части else-частьнеоб # endif
if-строка:
# if константное_выражение
# ifdef идентификатор
# ifndef идентификатор
elif-части:
elif-строка текст
elif-частинеоб
eilf-строка:
# elif константное_выражение
else-часть:
else-строка текст
else-строка:
# else