Язык Си

         

A1. Введение


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

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

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



A2.1. Лексемы (tokens)


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

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



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


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



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


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



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


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

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

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

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



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


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

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

Тип целой константы зависит от ее вида, значения и суффикса (о типах см. ). Если константа - десятичная и не имеет суффикса, то она принимает первый из следующих типов, который годится для представления ее значения: 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. Если константа имеет суффикс ul или UL, то она принимает тип unsigned long int.

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



A2.5.2. Символьные константы


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

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

новая строка (newline, linefeed) NL (LF) \n
горизонтальная табуляция (horizontal 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 был добавлен главным образом для азиатских языков.



А2.5.3. Константы с плавающей точкой


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

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



A2.5.4. Константы-перечисления


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



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


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

константа: целая–константа символьная-константа константа-с-плавающей-точкой константа-перечисление



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


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

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

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

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



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


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



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


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

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

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

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



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


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

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

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



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


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

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

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

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

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

Для беззнаковых целых в объявлениях используется ключевое слово unsigned. Такие целые подчиняются арифметике по модулю 2 в степени n, где n - число битов в представлении числа, и, следовательно, в арифметике с беззнаковыми целыми никогда не бывает переполнения. Множество неотрицательных значений, которые могут храниться в объектах со знаком, является подмножеством значений, которые могут храниться в соответствующих объектах без знака; знаковое и беззнаковое представления каждого такого значения совпадают. Любые два из типов с плавающей точкой: с одинарной точностью (float), с двойной точностью (double) и с повышенной точностью (long double) могут быть синонимами, но каждый следующий тип этого списка должен по крайней мере обеспечивать точность предыдущего.

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




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

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

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


A4.3. Производные типы


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

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

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



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


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



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


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



A5. Объекты и Lvalues


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



A6.1. Целочисленное повышение


Объект типа перечисление, символ, короткое целое, целое в битовом поле - все они со знаком или без могут использоваться в выражении там, где возможно применение целого. Если тип int позволяет "охватить" все значения исходного типа операнда, то операнд приводится к int, в противном случае он приводится к unsigned int. Эта процедура называется целочисленным повышением (Integral promotion – целочисленное повышение – иногда также переводят как "интегральное продвижение” – Примеч. ред.).



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


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

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



A6.3. Целые и числа с плавающей точкой


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

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



A6.4. Типы с плавающей точкой


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



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


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

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

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



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


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

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

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

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

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

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

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

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

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



A6.7. Тип void


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

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



А6.8. Указатели на void


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

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



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


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



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


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



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


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

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

константа

строка

(выражение)

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

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

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

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



A7.3.1. Обращение к элементам массива


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



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


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

extern int identifier();

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

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

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

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

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


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

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

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

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


A7.3.3. Обращение к структурам


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

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

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

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



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


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



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


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

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

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

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

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

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

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



А7.4.1. Префиксные операторы инкремента и декремента


Унарное выражение, перед которым стоит ++ или --, есть унарное выражение. Операнд увеличивается (++) или уменьшается (--) на 1.

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



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


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



A7.4.3. Оператор косвенного доступа


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



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


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

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



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


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



A7.4.6. Оператор побитового отрицания


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



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


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



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


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



А7.4. Унарные операторы


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

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

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

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

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

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

sizeof (имя-типа)

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



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


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

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

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



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 указывает за границы массива. В остальных случаях результат сравнения не определен.

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