Язык Си

         

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


выражение-равенства: выражение-отношения

выражение-равенства == выражение-отношения

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

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

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



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


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

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

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



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


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

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

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



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


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

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

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



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


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

логическое-И-выражение && ИЛИ-выражение

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

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



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


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

логическое-ИЛИ-выражение логическое-И-выражение

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

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



А7.16. Условный оператор


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

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

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

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



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


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

выражение-присваивания: условное-выражение

унарное-выражение оператор-присваивания выражение-присваивания

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

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

Тип выражения присваивания соответствует типу его левого операнда, а значение равно значению его левого операнда после завершения присваивания.

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

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



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


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

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

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

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

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



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


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

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

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

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

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

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



A7. Выражения


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

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

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

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



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


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

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

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

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

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

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

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

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

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



А8.2. Спецификаторы типа


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

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

спецификатор-перечисления

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, но не в иных случаях. Тип становится завершенным при появлении последующего спецификатора с этим тегом, содержащего список объявлений. Даже в спецификаторах со списком объявляемый тип структуры или объединения является незавершенным внутри списка и становится завершенным только после появления символа }, заканчивающего спецификатор.


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

Особое правило применяется к объявлениям вида

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

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

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

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

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

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

Элемент структуры или объединения, не являющийся полем, может иметь любой тип объекта. Поле (которое не имеет объявителя и, следовательно, может быть безымянным) имеет тип int, unsigned int или signed int и интерпретируется как объект целочисленного типа указанной в битах длины. Считается ли поле int знаковым или беззнаковым, зависит от реализации. Соседний элемент-поле упаковывается в ячейки памяти в зависимости от реализации в зависящем от реализации направлении. Когда следующее за полем другое поле не влезает в частично заполненную ячейку памяти, оно может оказаться разделенным между двумя ячейками, или ячейка может быть забита балластом. Безымянное поле нулевой ширины обязательно приводит к такой забивке, так что следующее поле начнется с края следующей ячейки памяти.

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



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

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

Вот простой пример объявления структуры:

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

Эта структура содержит массив из 20 символов, число типа int и два указателя на подобную структуру. Если дано такое объявление, то

struct tnode s, *sp;

объявит s как структуру заданного вида, a 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 по мере чтения объявления слева направо. Перечислитель со знаком = дает соответствующему идентификатору значение; последующие идентификаторы продолжают прогрессию от заданного значения.

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

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

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



А8.5. Объявители


Объявители имеют следующий синтаксис:

объявитель: указательнеоб собственно-объявитель

собственно-объявитель: идентификатор

(объявитель) собственно-объявитель [константное-выражениенеоб] собственно-объявитель (список-типов-параметров) собственно-объявитель (список-идентификаторовнеоб)

указатель: * список-квалификаторов-типанеоб

* список-квалификаторов-типанеоб указатель

список-квалификаторов-типа: квалификатор-типа

список-квалификаторов-типа квалификатор-типа

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



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 изменить нельзя.



А8.6.2. Объявители массивов


В объявления T D, где D имеет вид

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

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

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

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

объявляет массив из чисел типа float и массив из указателей на числа типа float. Аналогично

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

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

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

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



А8.6.3. Объявители функций


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

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

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

список-типов-параметров: список-параметров

список-параметров , ...

список-параметров: объявление-параметра

список-параметров, объявление-параметра

объявление-параметра: спецификаторы-объявления объявитель

спецификатор-объявления абстрактный-объявительнеоб

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

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

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

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

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

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

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

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

Например, объявление


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

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

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

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

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

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

Многоточие ", ..." применительно к функциям с варьируемым числом аргументов - также новинка, которая вместе со стандартным заголовочным файлом макросов <stdarg.h> формализует неофициально используемый, но официально запрещенный в первой редакции механизм. Указанные способы записи заимствованы из языка C++.


A8.6. Что означают объявители


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

Если соединить спецификаторы объявления, относящиеся к типу (), и некоторый конкретный объявитель, то объявление примет вид "T D", где T - тип, a D - объявитель. Эта запись индуктивно придает тип идентификатору любого объявителя.

В объявлении T D, где D - просто идентификатор, тип идентификатора есть T.

В объявлении T D, где D имеет вид

( D1 )

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



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


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

инициализатор: выражение-присваивания

{ список-инициализаторов } { список-инициализаторов, }

список-инициализаторов: инициализатор

список-инициализаторов , инициализатор

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

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

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

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

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

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


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

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

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

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

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

Наконец,

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

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


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


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

имя-типа: список-спецификаторов-квалификаторов абстрактный-объявительнеоб

абстрактный-объявитель: указатель

указательнеоб собственно-абстрактный-объявитель

собственно-абстрактный-объявитель: ( абстрактный-объявитель ) собственно-абстрактный-объявительнеоб[константное-выражениенеоб] собственно-абстрактный-объявительнеоб(список-типов-параметровнеоб)

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

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

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



А8.9. Объявление typedef


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

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

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

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

допустимы следующие объявления:

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

b принадлежит типу long, bp — типу "указатель на long"; z — это структура заданного вида, a zp - принадлежит типу "указатель на такую структуру".

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

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

extern Blockno;

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

extern int Blockno;

переобъявляет.



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


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

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



A8. Объявления


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

объявление: спецификаторы-объявления список-инициализаторов-объявителейнеоб

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

спецификаторы-объявления: спецификатор-класса-памяти спецификаторы-объявлениянеоб

спецификатор-типа спецификаторы-объявлениянеоб

квалификатор-типа спецификаторы-объявлениянеоб

список-инициализаторов-объявителей: инициализатор-объявитель

список-инициализаторов-объявителей , инициализатор-объявитель

инициализатор-объявитель: объявитель

объявитель = инициализатор

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



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


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

помеченная-инструкция: идентификатор : инструкция

case константное-выражение : инструкция

default : инструкция

Метка, состоящая из идентификатора, одновременно служит и объявлением этого идентификатора. Единственное назначение идентификатора-метки - указать место перехода для goto. Областью видимости идентификатора-метки является текущая функция. Так как метки имеют свое собственное пространство имен, они не "конфликтуют" с другими идентификаторами и не могут быть перекрыты (см. ).

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

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



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


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

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

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



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


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

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

список-объявлений: объявление

список-объявлений объявление

список-инструкций: инструкция

список-инструкций инструкция

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

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



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


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

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

if ( выражение ) инструкция else инструкция

switch ( выражение ) инструкция

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

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

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

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

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



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


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

циклическая-инструкция: while (выражение) инструкция

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

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

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

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

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

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

выражениеЗ; }

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



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


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

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

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

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

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

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

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

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

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

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

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



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


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

инструкция: помеченная–инструкция

инструкция–выражение

составная-инструкция

инструкция-выбора

циклическая-инструкция

инструкция-перехода



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


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

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

составная-инструкция

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

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

собственно-объявитель ( список-типов-параметров ) собственно-объявитель ( список-идентификаторовнеоб)

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

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

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


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

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

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

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

m = (a > b) ? a: b; return (m > с) ? m : с; }

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

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

где max(a, b, c) – объявитель, а int a, b, c - список-объявлений для параметров.


A10.2. Внешние объявления


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

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

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

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

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

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

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



А10. Внешние объявления


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

единица-трансляции: внешнее-объявление

единица-трансляции внешнее-объявление

внешнее-объявление: определение-функции

объявление

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



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


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

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

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

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



A11.2. Связи


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

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



A11. Область видимости и связи


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

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



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


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

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

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

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



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


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



А12.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(хсat(1, 2), 3)

в итоге даст 123, поскольку сам xcat не использует оператора ##.

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


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


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

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

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

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

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

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

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

и

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

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

Константное выражение () здесь используется с ограничениями: оно должно быть целочисленным, не может содержать в себе перечислимых констант, преобразований типа и операторов 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__ Дата компиляции в виде "MMM DD YYYY",строка. __TIME__ Время компиляции в виде "hh:mm:ss", строка. __STDC__ Константа 1. Предполагается, что этот идентификатор определен как 1 только в тех реализациях, которые следуют стандарту.

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



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


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

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

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



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


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

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

единица–трансляции: внешнее-объявление

единица-трансляции внешнее-объявление

внешнее-объявление: определение-функции

объявление

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

список-объявленийнеоб составная-инструкция

объявление: спецификаторы-объявления список-инициализаторов-объявителейнеоб

список-объявлений: объявление

список-объявлений объявление

спецификаторы-объявления: спецификатор-класса-памяти спецификаторы-объявлениянеоб

спецификатор-типа спецификаторы-объявлениянеоб

квалификатор-типа спецификаторы-объявлениянеоб

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

спецификатор-типа: один из void char short int long float double signed unsigned спецификатор-структуры-или-объединения


спецификатор-перечисления

typedef-имя

квалификатор-типа: один из const volatile

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

структура-или-объединение: одно из struct union

список-объявлений-структуры: объявление-структуры

список-объявлений-структуры объявление-структуры

список-объявителей-ииициализаторов: объявитель-инициализатор

список-объявителей-инициализаторов , объявитель-инициализатор

объявитель-инициализатор: объявитель

объявитель = инициализатор

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

список-спецификаторов-квалификаторов: спецификатор-типа список-спецификаторов-квалификаторовнеоб

квалификатор-типа список-спецификаторов-квалификаторовнеоб

список-структуры-объявителей: структуры–объявитель

список-структуры-объявителей , структуры-объявитель

структуры-объявитель: объявитель

объявительнеоб : константное-выражение

спецификатор-перечисления: enum идентификаторнеоб { список-перечислителей } enum идентификатор

список-перечислителей: перечислитель

список-перечислителей перечислитель

перечислитель: идентификатор

указательнеоб собственно-объявитель

собственно-объявитель: идентификатор

( объявитель ) собственно-объявитель [ константное-выражениенеоб ] собственно-объявитель ( список-типов-параметров ) собственно-объявитель ( список-идентификаторовнеоб )

указатель: * список~квалификаторов-типанеоб

* список-квалификаторов-типанеоб указатель

список-квалификаторов-типа: квалификатор-типа

список-квалификаторов-типа квалификатор-типа

список-типов-параметров: список-параметров

список-параметров , ...

список-параметров: объявление-параметра

список-параметров , объявление-параметра

объявление-параметра: спецификаторы-объявления объявитель

спецификаторы-объявления абстрактный-объявительнеоб

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

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



инициализатор: выражение-присваивания

{ список-инициализаторов } { список-инициализаторов, }

список-инициализаторов: инициализатор

список-инициализаторов , инициализатор

имя-типа: список-спецификаторое-квалификаторов абстрактный-объявительнеоб

абстрактный-объявитель: указатель

указательнеоб собственно-абстрактный-объявитель

собственно-абстрактный-объявитель: ( абстрактный-объявитель ) собственно-абстрактный-объявительнеоб [константное-выражениенеоб] собственно-абстрактный-объявительнеоб (список-типов-параметровнеоб)

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

инструкция: помеченная-инструкция

инструкция–выражение

составная-инструкция

инструкция-выбора

циклическая-инструкция

инструкция-перехода

помеченная-инструкция: идентификатор : инструкция

case константное-выражение : инструкция

default : инструкция

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

составная-инструкция: ( список-объявленийнеоб список-инструкцийнеоб)

список-инструкций: инструкция

список-инструкций инструкция

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

if ( выражение ) инструкция else инструкция

switch ( выражение ) инструкция

циклическая-инструкция: while ( выражение ) инструкция

do инструкция while ( выражение ) return выражениенеоб;

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

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

выражение-присваивания: условное-выражение

унарное-выражение оператор-присваивания выражение-присваивания

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

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

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

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

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

логическое-ИЛИ-выражение логическое-И-выражение

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

логическое-И-выражение && ИЛИ-выражение

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

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

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

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



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

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

выражение-равенства: выражение-отношения

выражение-равенства == выражение-отношения

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

выражение-отношения: сдвиговое-выражение

выражение-отношения < сдвиговое-выражение

выражение-отношения > сдвиговое-выражение

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

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

сдвиговое-выражение: аддитивное-выражение

сдвиговое-выражение >> аддитивное-выражение

сдвиговое-выражение << аддитивное-выражение

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

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

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

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

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

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

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

выражение-приведенное-к-типу: унарное-выражение

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

унарное-выражение: постфиксное –выражение

++ унарное-выражение

-- унарное-выражение

унарный-оператор выражение-приведенное-к-типу

sizeof унарное-выражение

sizeof( имя-типа )

унарный-оператор: один из & * + - ~ !

постфиксное-выражение: первичное-выражение

постфиксное-выражение [ выражение ] постфиксное-выражение ( список-аргументов-выраженийнеоб ) постфиксное-выражение , идентификатор

постфиксное-выражение -> идентификатор

постфиксное-выражение ++ постфиксное-выражение –

первичное -выражение: идентификатор

константа

строка

( выражение )

список-аргументов-выражений: выражение-присваивания

список-аргументов-выражений , выражение-присваивания

константа: целая-константа

символьная-константа

константа-с-плавающей-точкой

константа-перечисление

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

управляющая-строка: #define идентификатор последовательность-лексем



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

#include <имя-файла> #include "имя-файла" #include последовательность-лексем

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

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

#pragma последовательность-лексемнеоб

# условная-конструкция-препроцессора

условная-конструкция-препроцессора: if-строка текст elif-части else-частьнеоб #endif

if-строка: #if константное-выражение

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

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

elif-части: elif-строка текст

elif-частинеоб

elif-строка: #elif константное-выражение

else-часть: else-строка текст

else-строка: #else