Страница:
Выражение в круглых скобках является первичным выраже-
нием, тип и значение которого идентичны типу и значению
этого выражения без скобок. Наличие круглых скобок не вли-
яет на то, является ли выражение l_значением или нет.
Первичное выражение, за которым следует выражение в
квадратных скобках, является первичным выражением. Это выра-
жение с индексом. Обычно первичное выражение имеет тип ука-
затель на ..., индексное выражение имеет тип int, а типом
результата является "...". Выражение e1[e2] по определению
идентично выражению * ((e1) + (e2)). Все, что необходимо
-11-
для понимания этой записи, содержится в этом разделе; воп-
росы, связанные с понятием идентификаторов и операций * и +
рассматриваются в п.п. 0.1, 0.2 и 0.4 соответственно; выводы
суммируются ниже.
Обращение к функции является первичным выражением, за
которым следует заключенный в круглые скобки возможно пустой
список выражений, разделенных запятыми, которые и представ-
ляют собой фактические аргументы функции. Первичное выраже-
ние должно быть типа функция, возвращающая ..., а результат
обращения к функции имеет тип "...". Как указывается ниже,
ранее не встречавщийся идентификатор, за которым непосредст-
венно следует левая круглая скобка, считается описанным по
контексту, как представляющий функцию, возвращающую целое;
следовательно чаще всего встречающийся случай функции, возв-
ращающей целое значение, не нуждается в описании.
Перед обращением любые фактические аргументы типа float
преобразуются к типу double, любые аргументы типа char или
short преобразуются к типу int, и, как обычно, имена масси-
вов преобразуются в указатели. Никакие другие преобразования
не выполняются автоматически; в частности, не сравниваются
типы фактических аргументов с типами формальных аргументов.
Если преобразование необходимо, используйте явное преобразо-
вание.
При подготовке к вызову функции делается копия каждого
фактического параметра; таким образом, все передачи аргумен-
тов в языке Си осуществляются строго по значению. Функция
может изменять значения своих формальных параметров, но эти
изменения не влияют на значения фактических параметров. С
другой стороны, имеется возможность передавать указатель,
при этом функция может изменять значение объекта, на который
этот указатель указывает. Порядок вычисления аргументов в
языке не определен; различные компиляторы вычисляют по раз-
ному.
Допускаются рекурсивные обращения к любой функции.
Первичное выражение, за которым следует точка и иденти-
фикатор, является выражением. Первое выражение должно быть
l_значением, именующим структуру или объединение, а иденти-
фикатор должен быть именем члена структуры или объединения.
Результатом является l_значение, ссылающееся на поименован-
ный член структуры или объединения.
Первичное выражение, за которым следует стрелка (сос-
тавленная из знаков - и >) и идентификатор, является выраже-
нием. Первое выражение должно быть указателем на структуру
или объединение, а идентификатор должен именовать член этой
структуры или объединения. Результатом является l_значение,
ссылающееся на поименованный член структуры или объединения,
на который указывает указательное выражение.
-12-
Следовательно, выражение e1->mos является тем же самым,
что и выражение (*e1).mos. Структуры и объединения рассмат-
риваются ниже. Приведенные здесь правила использования
структур и объединений не навязываются строго, для того
чтобы иметь возможность обойти механизм типов (см. "Допол-
нительная информация о типах").
Выражение с унарными операциями группируется справа
налево.
унарное_выражение:
* выражение
& l_значение
- выражение
! выражение
~ выражение
++ l_значение
-- l_значение
l_значение ++
l_значение --
(имя-типа) выражение
sizeof выражение
sizeof (имя_типа)
Унарная операция * означает косвенную адресацию: выражение
должно быть указателем, а результатом является l_значение,
ссылающееся на тот объект, на который указывает выражение.
Если типом выражения является указатель на ..., то типом
результата будет "...".
Результатом унарной операции & является указатель на
объект, к которому ссылается l_значение. Если l_значение
имеет тип "...", то типом результата будет указатель на ....
Результатом унарной операции - (минус) является ее опе-
ранд, взятый с противоположным знаком. Для величины типа
unsigned результат получается вычитанием ее значения из 2**n
(два в степени n), где n-число битов в int. Унарной операции
+ (плюс) не существует.
Результатом операции логического отрицания ! является
1, если значение ее операнда равно 0, и 0, если значение ее
операнда отлично от нуля. Результат имеет тип int. Эта опе-
рация применима к любому арифметическому типу или указате-
лям.
Операция ~ (символ "тильда", находится на клавише ^ в
нижнем регистре) дает обратный код (или дополнение до еди-
ницы) своего операнда. Выполняются обычные арифметические
преобразования. Операнд должен быть целочисленного типа.
-13-
Объект, на который ссылается операнд l_значения пре-
фиксной операции ++, увеличивается. Значением является новое
значение операнда, но это не l_значение. Выражение ++х экви-
валентно х += 1 . Информацию о преобразованиях смотри в раз-
боре операции сложения (п. 0.4) и операции присваивания (п.
0.14).
Префиксная операция -- аналогична префиксной операции
++, но приводит к уменьшению своего операнда l_значения.
При применении постфиксной операции ++ к l_значению
результатом является значение объекта, на который ссылается
l_значение. После того, как результат принят к сведению,
объект увеличивается точно таким же образом, как и в случае
префиксной операции ++. Результат имеет тот же тип, что и
выражение l_значения.
При применении постфиксной операции -- к l_значению
результатом является значение объекта, на который ссылается
l_значение. После того, как результат принят к сведению,
объект уменьшается точно таким же образом, как и в случае
префиксной операции --. Результат имеет тот же тип, что и
выражение l_значения.
Заключенное в круглые скобки имя типа данных, стоящее
перед выражением, вызывает преобразование значения этого
выражения к указанному типу. Эта конструкция называется
перевод (cast). Имена типов описываются в следующем разделе.
Операция sizeof выдает размер своего операнда в байта.
(Понятие байт в языке не определено, разве только, как зна-
чение операции sizeof. Однако во всех существующих реализа-
циях байтом является пространство, необходимое для хранения
объекта типа char). При применении к массиву результатом
является полное число байтов в массиве. Размер определяется
из описаний объектов в выражении. Это выражение семантически
является целой константой и может быть использовано в любом
месте, где требуется константа. Основное применение эта опе-
рация находит при вызове процедур, подобных распределителям
памяти, и в системах ввода- вывода.
Операция sizeof может быть также применена и к заклю-
ченному в круглые скобки имени типа. В этом случае она
выдает размер в байтах объекта указанного типа.
Конструкция sizeof (тип) рассматривается как целое, так
что выражение sizeof (тип) - 2 эквивалентно выражению
(sizeof (тип)) - 2.
Мультипликативные операции *, /, и % группируются слева
направо. Выполняются обычные арифметические преобразования.
-14-
мультипликативное_выражение:
выражение * выражение
выражение / выражение
выражение % выражение
Бинарная операция * означает умножение. Операция *
ассоциативна, и выражения с несколькими умножениями на одном
и том же уровне могут быть перегруппированы компилятором.
Бинарная операция / означает деление. При делении поло-
жительных целых осуществляется усечение по направлению к
нулю, но если один из операндов отрицателен, то форма усече-
ния зависит от используемой машины. Остаток имеет тот же
знак, что и делимое. Всегда справедливо, что (a/b)*b+a%b
равно a (если b не равно 0).
Бинарная операция % выдает остаток от деления первого
выражения на второе. Выполняются обычные арифметические пре-
образования. Операнды должны быть целого типа.
Аддитивные операции + и - группируются слева направо.
Выполняются обычные арифметические преобразования. Для каж-
дой операции имеются некоторые дополнительные возможности,
связанные с типами операндов.
аддитивное_выражение:
выражение + выражение
выражение - выражение
Результатом операции + является сумма операндов. Можно также
складывать указатель на объект в массиве и значение любого
целочисленного типа. Последнее преобразуется в адресное
смещение посредством умножения его на длину объекта, на
который указывает этот указатель. Результатом является ука-
затель того же самого типа, что и исходный указатель, кото-
рый указывает на другой объект в том же массиве, смещенный
соответствующим образом относительно первоначального
объекта. Таким образом, если p является указателем объекта в
массиве, то выражение p+1 является указателем на следующий
объект в этом массиве.
Никакие другие комбинации типов для указателей не раз-
решаются.
Операция + ассоциативна, и выражение с несколькими сло-
жениями на одном и том же уровне могут быть переупорядочены
компилятором.
-15-
Результатом операции - является разность операндов.
Выполняются обычные арифметические преобразования. Кроме
того, из указателя может быть вычтено значение любого цело-
численного типа, причем, проводятся те же самые преобразова-
ния, что и при операции сложения.
Если вычитаются два указателя на объекты одинакового
типа, то результат преобразуется (делением на длину объекта)
к типу int, представляя собой число объектов, разделяющих
указываемые объекты. Если эти указатели не на объекты из
одного и того же массива, то такое преобразование, вообще
говоря, даст неожиданные результаты, потому что даже указа-
тели на объекты одинакового типа не обязаны отличаться на
величину, кратную длине объекта.
Операции сдвига << и >> группируются слева направо.
Для обеих операций проводятся обычные арифметические преоб-
разования их операндов, каждый из которых должен быть целого
типа. Затем правый операнд преобразуется к типу int; резуль-
тат имеет тип левого операнда. Результат не определен, если
правый операнд отрицателен или больше или равен, чем длина
объекта в битах.
выражение_сдвига:
выражение << выражение
выражение >> выражение
Значением выражения e1<<e2 является e1 (интерпретируемое как
комбинация битов), сдвинутое влево на e2 битов; освобождаю-
щиеся биты заполняются нулем. Значением выражения e1>>e2
является e1, сдвинутое вправо на e2 битовых позиций. Если e1
имеет тип unsigned, то сдвиг вправо гарантированно будет
логическим (заполнение нулем); в противном случае сдвиг
может быть (как на CM-ЭВМ) арифметическим (освобождающиеся
биты заполняются копией знакового бита).
Операции отношения группируются слева направо, но этот
факт не очень полезен; выражение a<b<c не означает того, что
оно ,казалось бы, должно означать, а означает ((a<b)<c).
выражение_отношения:
выражение < выражение
выражение > выражение
выражение <= выражение
выражение >= выражение
Операции < (меньше), > (больше), <= (меньше или равно) и >=
(больше или равно) дают 0, если указанное отношение ложно,
и 1, если оно истинно. Результат имеет тип int. Выполняются
-16-
обычные арифметические преобразования. Могут сравниваться
два указателя; результат зависит от относительного располо-
жения указываемых объектов в адресном пространстве. Сравне-
ние указателей переносимо только в том случае, если указа-
тели указывают на объекты из одного и того же массива.
выражение_равенства:
выражение == выражение
выражение != выражение
Операции == (равно) и != (не равно) в точности аналогичны
операциям отношения, за исключением того, что они имеют
более низкий уровень старшинства. (поэтому значение выраже-
ния a<b==c<d равно 1 всякий раз, когда выражения a<b и c<d
имеют одинаковое значение истинности).
Указатель можно сравнивать с целым, но результат будет
машинно-независимым только в том случае, если целым является
константа 0. Гарантируется, что указатель, которому присво-
ено значение 0, не указывает ни на какой объект и на самом
деле оказывается равным 0; общепринято считать такой указа-
тель нулем.
выражение_и:
выражение & выражение
Операция & является ассоциативной, и включающие & выражения
могут быть переупорядочены компилятором. Выполняются обыч-
ные арифметические преобразования; результатом является
побитовая функция 'и' операндов. Эта операция применима
только к операндам целого типа.
выражение_исключающего_или:
выражение ^ выражение
Операция ^ (знак надчеркивания, код в КОИ-8 0136) является
ассоциативной, и включающие ^ выражения могут быть переупо-
рядочены компилятором. Выполняются обычные арифметические
преобразования; результатом является побитовая функция иск-
лючающего 'или' операндов. Операция применима только к опе-
рандам целочисленного типа.
выражение_включающего_или:
выражение | выражение
-17-
Операция | является ассоциативной, и содержащие | выражения
могут быть переупорядочены. Выполняются обычные арифметичес-
кие преобразования; результатом является побитовая функция
включающего 'или' операндов. Операция применима только к
операндам целочисленного типа.
выражение_логического_и:
выражение && выражение
Операция && группируется слева направо. Она возвращает 1,
если оба ее операнда отличны от нуля, и 0 в противном слу-
чае. В отличие от & операция && гарантирует вычисление слева
направо; более того, если первый операнд равен 0, то значе-
ние второго операнда вообще не вычисляется.
Операнды не обязаны быть одинакового типа, но каждый из
них должен быть либо одного из основных типов, либо указате-
лем. Результат всегда имеет тип int.
выражение_логического_или:
выражение || выражение
Операция || группируется слева направо. Она возвращает 1,
если один из операндов отличен от нуля, и 0 в противном слу-
чае. В отличие от операции | операция || гарантирует вычис-
ление слева направо; более того, если первый операнд отличен
от нуля, то значение второго операнда вообще не вычисляется.
Операнды не обязаны быть одинакового типа, но каждый из
них должен быть либо одного из основных типов, либо указате-
лем. Результат всегда имеет тип int.
условное_выражение:
выражение ? выражение : выражение
Условные выражения группируются слева направо. Вычисляется
значение первого выражения, и если оно отлично от нуля, то
результатом будет значение второго выражения; в противном
случае результатом будет значение третьего выражения. Если
это возможно, проводятся обычные арифметические преобразова-
ния, с тем, чтобы привести второе и третье выражения к
общему типу; в противном случае, если оба выражения являются
указателями одинакового типа, то результат имеет тот же тип;
в противном случае одно выражение должно быть указателем, а
другое - константой 0, и результат будет иметь тип указа-
теля. Вычисляется только одно из второго и третьего выраже-
ний.
-18-
Имеется ряд операций присваивания, каждая из которых
группируется слева направо. Все операции требуют в качестве
своего левого операнда l_значение, а типом выражения присва-
ивания является тип его левого операнда. Значением выражения
присваивания является значение, хранимое в левом операнде
после того, как присваивание уже будет произведено. Две
части составной операции присваивания являются отдельными
лексемами.
выражение_присваивания:
l_значение = выражение
l_значение += выражение
l_значение -= выражение
l_значение *= выражение
l_значение /= выражение
l_значение %= выражение
l_значение >>= выражение
l_значение <<= выражение
l_значение &= выражение
l_значение ^= выражение
l_значение |= выражение
Когда производится простое присваивание '=', значение
выражения заменяет значение объекта, на которое ссылается
l_значение. Если оба операнда имеют арифметический тип, то
перед присваиванием правый операнд преобразуется к типу
левого операнда.
В выражение вида e1 оп= e2, где оп - одна из перечис-
ленных выше операций, эквивалентно выражению
e1 = e1 оп (e2), с тем отличием, что выражение e1 вычисля-
ется только один раз. В случае операций += и -= левый опе-
ранд может быть указателем, причем при этом (целочисленный)
правый операнд преобразуется таким образом, как объяснено в
п. 0.4; все правые операнды и все отличные от указателей
левые операнды должны иметь арифметический тип.
Используемые в ОС ДЕМОС компиляторы допускают присваи-
вание указателя целому, целого указателю и указателя указа-
телю другого типа. Такое присваивание является чистым копи-
рованием без каких-либо преобразований. Такое употребление
операций присваивания является непереносимым и может приво-
дить к указателям, которые при использовании вызывают ошибки
адресации. Тем не менее гарантируется, что присваивание ука-
зателю константы 0 дает нулевой указатель, который можно
отличать от указателя на любой объект.
-19-
Структуры могут быть присвоены, а также переданы функ-
циям в качестве аргументов и возвращены функциями. Типы
участвующих операндов должны совпадать.
выражение_с_запятой:
выражение , выражение
Пара выражений, разделенных запятой, вычисляется слева нап-
раво и значение левого выражения отбрасывается. Типом и зна-
чением результата является тип и значение правого операнда.
Эта операция группируется слева направо. В контексте, где
запятая имеет специальное значение, как, например, в списке
фактических аргументов функций или в списках инициализато-
ров, операция запятая, описываемая в этом разделе, может
появляться только в круглых скобках; например, функция
f(a,(t=3,t+2),c)
имеет три аргумента, второй из которых имеет значение 5.
В приводимой ниже таблице сведены правила старшинства и
ассоциативности всех операций. Операции, расположенные в
одной строке, имеют один и тот же уровень старшинства;
строки расположены в порядке убывания старшинства. Так, нап-
ример, операции *, "/" и "%" имеют одинаковый уровень стар-
шинства, который выше, чем уровень операций "+" и "-".
-20-
Таблица 2
------------------------------------------
| Оператор | Ассоциативность |
|____________________|___________________|
| () [] -> . | слева направо |
|____________________|___________________|
| ~ ++ -- - f | справа налево |
|(type) * & sizeof| |
|____________________|___________________|
| * / % | слева направо |
|____________________|___________________|
| + - | слева направо |
|____________________|___________________|
| << >> | слева направо |
|____________________|___________________|
| < <= > >= | слева направо |
|____________________|___________________|
| == != | слева направо |
|____________________|___________________|
| & | слева направо |
|____________________|___________________|
| ^ | слева направо |
|____________________|___________________|
| | | слева направо |
|____________________|___________________|
| && | слева направо |
|____________________|___________________|
| || | слева направо |
|____________________|___________________|
| ?: | справа налево |
|____________________|___________________|
| = += -= и т.п. | справа налево |
|____________________|___________________|
| , | слева направо |
|____________________|___________________|
Отметим, что уровень старшинства побитовых логических
операций &, ^ и | ниже уровня операций == и !=. Это приводит
к тому, что осуществляющие побитовую проверку выражения,
подобные
if ((х & mask) == 0) ...
для получения правильных результатов должны заключаться в
круглые скобки, в противном случае оно будет понято так:
выражение: x & mask == 0
понято как: x & ( mask == 0 )
-21-
Описания используются для указания интерпретации, кото-
рую язык Си будет давать каждому идентификатору; они не обя-
зательно резервируют память, соответствующую идентификатору.
Описания имеют форму
описание:
спецификаторы_описания список_описателей;
необ
Описатели в списке описателей содержат описываемые идентифи-
каторы. Спецификаторы описания представляют собой последо-
вательность спецификаторов типа и спецификаторов класса
памяти.
спецификаторы_описания:
с_типа с_описания
необ
с_класса_памяти с_описания
необ
где c_... - спецификатор_...
Список описателей должен быть согласованным в смысле, описы-
ваемом ниже.
ниже перечисляются спецификаторы класса памяти:
спецификатор_класса_памяти:
auto
static
extern
register
typedef
Спецификатор typedef не резервирует память и называется
"спецификатором класса памяти" только по синтаксическим
соображениям; это обсуждается ниже. Смысл различных классов
памяти был обсужден ранее (см. "Объекты языка Си").
Описания auto, static и register служат также в
качестве определений в том смысле, что они вызывают резерви-
рование нужного количества памяти. В случае extern должно
присутствовать внешнее определение указываемых идентификато-
ров где то вне функции, в которой они описаны.
Описание register лучше всего представлять себе как
описание auto вместе с намеком компилятору, что описанные
таким образом переменные будут часто использоваться. Эффек-
тивны только несколько первых таких описаний. Кроме того, в
-22-
регистрах могут храниться только переменные определенных
типов; на CM-ЭВМ это int, char или указатель. Существует и
другое ограничение на использование регистровых переменных:
к ним нельзя применять операцию взятия адреса &. При разум-
ном использовании регистровых описаний можно ожидать получе-
ния меньших по размеру и более быстрых программ, но в буду-
щем улучшение генерирования кодов может сделать их ненуж-
ными.
В компиляторе для СМ ЭВМ воспринимаются первые 3 описа-
ния register в каждой функции.
Описание может содержать не более одного спецификатора
класса памяти. Если описание не содержит спецификатора
класса памяти, то считается, что он имеет значение auto,
если описание находится внутри некоторой функции, и extern в
противном случае. Исключение: функции никогда не бывают
автоматическими.
Ниже перечисляются спецификаторы типа.
спецификатор_типа:
char
short
int
long
unsigned
float
double
спецификатор_структуры_или_объединения
спецификатор_перечисления
определяющее_тип_имя
Слова long, short и unsigned можно рассматривать как прила-
нием, тип и значение которого идентичны типу и значению
этого выражения без скобок. Наличие круглых скобок не вли-
яет на то, является ли выражение l_значением или нет.
Первичное выражение, за которым следует выражение в
квадратных скобках, является первичным выражением. Это выра-
жение с индексом. Обычно первичное выражение имеет тип ука-
затель на ..., индексное выражение имеет тип int, а типом
результата является "...". Выражение e1[e2] по определению
идентично выражению * ((e1) + (e2)). Все, что необходимо
-11-
для понимания этой записи, содержится в этом разделе; воп-
росы, связанные с понятием идентификаторов и операций * и +
рассматриваются в п.п. 0.1, 0.2 и 0.4 соответственно; выводы
суммируются ниже.
Обращение к функции является первичным выражением, за
которым следует заключенный в круглые скобки возможно пустой
список выражений, разделенных запятыми, которые и представ-
ляют собой фактические аргументы функции. Первичное выраже-
ние должно быть типа функция, возвращающая ..., а результат
обращения к функции имеет тип "...". Как указывается ниже,
ранее не встречавщийся идентификатор, за которым непосредст-
венно следует левая круглая скобка, считается описанным по
контексту, как представляющий функцию, возвращающую целое;
следовательно чаще всего встречающийся случай функции, возв-
ращающей целое значение, не нуждается в описании.
Перед обращением любые фактические аргументы типа float
преобразуются к типу double, любые аргументы типа char или
short преобразуются к типу int, и, как обычно, имена масси-
вов преобразуются в указатели. Никакие другие преобразования
не выполняются автоматически; в частности, не сравниваются
типы фактических аргументов с типами формальных аргументов.
Если преобразование необходимо, используйте явное преобразо-
вание.
При подготовке к вызову функции делается копия каждого
фактического параметра; таким образом, все передачи аргумен-
тов в языке Си осуществляются строго по значению. Функция
может изменять значения своих формальных параметров, но эти
изменения не влияют на значения фактических параметров. С
другой стороны, имеется возможность передавать указатель,
при этом функция может изменять значение объекта, на который
этот указатель указывает. Порядок вычисления аргументов в
языке не определен; различные компиляторы вычисляют по раз-
ному.
Допускаются рекурсивные обращения к любой функции.
Первичное выражение, за которым следует точка и иденти-
фикатор, является выражением. Первое выражение должно быть
l_значением, именующим структуру или объединение, а иденти-
фикатор должен быть именем члена структуры или объединения.
Результатом является l_значение, ссылающееся на поименован-
ный член структуры или объединения.
Первичное выражение, за которым следует стрелка (сос-
тавленная из знаков - и >) и идентификатор, является выраже-
нием. Первое выражение должно быть указателем на структуру
или объединение, а идентификатор должен именовать член этой
структуры или объединения. Результатом является l_значение,
ссылающееся на поименованный член структуры или объединения,
на который указывает указательное выражение.
-12-
Следовательно, выражение e1->mos является тем же самым,
что и выражение (*e1).mos. Структуры и объединения рассмат-
риваются ниже. Приведенные здесь правила использования
структур и объединений не навязываются строго, для того
чтобы иметь возможность обойти механизм типов (см. "Допол-
нительная информация о типах").
Выражение с унарными операциями группируется справа
налево.
унарное_выражение:
* выражение
& l_значение
- выражение
! выражение
~ выражение
++ l_значение
-- l_значение
l_значение ++
l_значение --
(имя-типа) выражение
sizeof выражение
sizeof (имя_типа)
Унарная операция * означает косвенную адресацию: выражение
должно быть указателем, а результатом является l_значение,
ссылающееся на тот объект, на который указывает выражение.
Если типом выражения является указатель на ..., то типом
результата будет "...".
Результатом унарной операции & является указатель на
объект, к которому ссылается l_значение. Если l_значение
имеет тип "...", то типом результата будет указатель на ....
Результатом унарной операции - (минус) является ее опе-
ранд, взятый с противоположным знаком. Для величины типа
unsigned результат получается вычитанием ее значения из 2**n
(два в степени n), где n-число битов в int. Унарной операции
+ (плюс) не существует.
Результатом операции логического отрицания ! является
1, если значение ее операнда равно 0, и 0, если значение ее
операнда отлично от нуля. Результат имеет тип int. Эта опе-
рация применима к любому арифметическому типу или указате-
лям.
Операция ~ (символ "тильда", находится на клавише ^ в
нижнем регистре) дает обратный код (или дополнение до еди-
ницы) своего операнда. Выполняются обычные арифметические
преобразования. Операнд должен быть целочисленного типа.
-13-
Объект, на который ссылается операнд l_значения пре-
фиксной операции ++, увеличивается. Значением является новое
значение операнда, но это не l_значение. Выражение ++х экви-
валентно х += 1 . Информацию о преобразованиях смотри в раз-
боре операции сложения (п. 0.4) и операции присваивания (п.
0.14).
Префиксная операция -- аналогична префиксной операции
++, но приводит к уменьшению своего операнда l_значения.
При применении постфиксной операции ++ к l_значению
результатом является значение объекта, на который ссылается
l_значение. После того, как результат принят к сведению,
объект увеличивается точно таким же образом, как и в случае
префиксной операции ++. Результат имеет тот же тип, что и
выражение l_значения.
При применении постфиксной операции -- к l_значению
результатом является значение объекта, на который ссылается
l_значение. После того, как результат принят к сведению,
объект уменьшается точно таким же образом, как и в случае
префиксной операции --. Результат имеет тот же тип, что и
выражение l_значения.
Заключенное в круглые скобки имя типа данных, стоящее
перед выражением, вызывает преобразование значения этого
выражения к указанному типу. Эта конструкция называется
перевод (cast). Имена типов описываются в следующем разделе.
Операция sizeof выдает размер своего операнда в байта.
(Понятие байт в языке не определено, разве только, как зна-
чение операции sizeof. Однако во всех существующих реализа-
циях байтом является пространство, необходимое для хранения
объекта типа char). При применении к массиву результатом
является полное число байтов в массиве. Размер определяется
из описаний объектов в выражении. Это выражение семантически
является целой константой и может быть использовано в любом
месте, где требуется константа. Основное применение эта опе-
рация находит при вызове процедур, подобных распределителям
памяти, и в системах ввода- вывода.
Операция sizeof может быть также применена и к заклю-
ченному в круглые скобки имени типа. В этом случае она
выдает размер в байтах объекта указанного типа.
Конструкция sizeof (тип) рассматривается как целое, так
что выражение sizeof (тип) - 2 эквивалентно выражению
(sizeof (тип)) - 2.
Мультипликативные операции *, /, и % группируются слева
направо. Выполняются обычные арифметические преобразования.
-14-
мультипликативное_выражение:
выражение * выражение
выражение / выражение
выражение % выражение
Бинарная операция * означает умножение. Операция *
ассоциативна, и выражения с несколькими умножениями на одном
и том же уровне могут быть перегруппированы компилятором.
Бинарная операция / означает деление. При делении поло-
жительных целых осуществляется усечение по направлению к
нулю, но если один из операндов отрицателен, то форма усече-
ния зависит от используемой машины. Остаток имеет тот же
знак, что и делимое. Всегда справедливо, что (a/b)*b+a%b
равно a (если b не равно 0).
Бинарная операция % выдает остаток от деления первого
выражения на второе. Выполняются обычные арифметические пре-
образования. Операнды должны быть целого типа.
Аддитивные операции + и - группируются слева направо.
Выполняются обычные арифметические преобразования. Для каж-
дой операции имеются некоторые дополнительные возможности,
связанные с типами операндов.
аддитивное_выражение:
выражение + выражение
выражение - выражение
Результатом операции + является сумма операндов. Можно также
складывать указатель на объект в массиве и значение любого
целочисленного типа. Последнее преобразуется в адресное
смещение посредством умножения его на длину объекта, на
который указывает этот указатель. Результатом является ука-
затель того же самого типа, что и исходный указатель, кото-
рый указывает на другой объект в том же массиве, смещенный
соответствующим образом относительно первоначального
объекта. Таким образом, если p является указателем объекта в
массиве, то выражение p+1 является указателем на следующий
объект в этом массиве.
Никакие другие комбинации типов для указателей не раз-
решаются.
Операция + ассоциативна, и выражение с несколькими сло-
жениями на одном и том же уровне могут быть переупорядочены
компилятором.
-15-
Результатом операции - является разность операндов.
Выполняются обычные арифметические преобразования. Кроме
того, из указателя может быть вычтено значение любого цело-
численного типа, причем, проводятся те же самые преобразова-
ния, что и при операции сложения.
Если вычитаются два указателя на объекты одинакового
типа, то результат преобразуется (делением на длину объекта)
к типу int, представляя собой число объектов, разделяющих
указываемые объекты. Если эти указатели не на объекты из
одного и того же массива, то такое преобразование, вообще
говоря, даст неожиданные результаты, потому что даже указа-
тели на объекты одинакового типа не обязаны отличаться на
величину, кратную длине объекта.
Операции сдвига << и >> группируются слева направо.
Для обеих операций проводятся обычные арифметические преоб-
разования их операндов, каждый из которых должен быть целого
типа. Затем правый операнд преобразуется к типу int; резуль-
тат имеет тип левого операнда. Результат не определен, если
правый операнд отрицателен или больше или равен, чем длина
объекта в битах.
выражение_сдвига:
выражение << выражение
выражение >> выражение
Значением выражения e1<<e2 является e1 (интерпретируемое как
комбинация битов), сдвинутое влево на e2 битов; освобождаю-
щиеся биты заполняются нулем. Значением выражения e1>>e2
является e1, сдвинутое вправо на e2 битовых позиций. Если e1
имеет тип unsigned, то сдвиг вправо гарантированно будет
логическим (заполнение нулем); в противном случае сдвиг
может быть (как на CM-ЭВМ) арифметическим (освобождающиеся
биты заполняются копией знакового бита).
Операции отношения группируются слева направо, но этот
факт не очень полезен; выражение a<b<c не означает того, что
оно ,казалось бы, должно означать, а означает ((a<b)<c).
выражение_отношения:
выражение < выражение
выражение > выражение
выражение <= выражение
выражение >= выражение
Операции < (меньше), > (больше), <= (меньше или равно) и >=
(больше или равно) дают 0, если указанное отношение ложно,
и 1, если оно истинно. Результат имеет тип int. Выполняются
-16-
обычные арифметические преобразования. Могут сравниваться
два указателя; результат зависит от относительного располо-
жения указываемых объектов в адресном пространстве. Сравне-
ние указателей переносимо только в том случае, если указа-
тели указывают на объекты из одного и того же массива.
выражение_равенства:
выражение == выражение
выражение != выражение
Операции == (равно) и != (не равно) в точности аналогичны
операциям отношения, за исключением того, что они имеют
более низкий уровень старшинства. (поэтому значение выраже-
ния a<b==c<d равно 1 всякий раз, когда выражения a<b и c<d
имеют одинаковое значение истинности).
Указатель можно сравнивать с целым, но результат будет
машинно-независимым только в том случае, если целым является
константа 0. Гарантируется, что указатель, которому присво-
ено значение 0, не указывает ни на какой объект и на самом
деле оказывается равным 0; общепринято считать такой указа-
тель нулем.
выражение_и:
выражение & выражение
Операция & является ассоциативной, и включающие & выражения
могут быть переупорядочены компилятором. Выполняются обыч-
ные арифметические преобразования; результатом является
побитовая функция 'и' операндов. Эта операция применима
только к операндам целого типа.
выражение_исключающего_или:
выражение ^ выражение
Операция ^ (знак надчеркивания, код в КОИ-8 0136) является
ассоциативной, и включающие ^ выражения могут быть переупо-
рядочены компилятором. Выполняются обычные арифметические
преобразования; результатом является побитовая функция иск-
лючающего 'или' операндов. Операция применима только к опе-
рандам целочисленного типа.
выражение_включающего_или:
выражение | выражение
-17-
Операция | является ассоциативной, и содержащие | выражения
могут быть переупорядочены. Выполняются обычные арифметичес-
кие преобразования; результатом является побитовая функция
включающего 'или' операндов. Операция применима только к
операндам целочисленного типа.
выражение_логического_и:
выражение && выражение
Операция && группируется слева направо. Она возвращает 1,
если оба ее операнда отличны от нуля, и 0 в противном слу-
чае. В отличие от & операция && гарантирует вычисление слева
направо; более того, если первый операнд равен 0, то значе-
ние второго операнда вообще не вычисляется.
Операнды не обязаны быть одинакового типа, но каждый из
них должен быть либо одного из основных типов, либо указате-
лем. Результат всегда имеет тип int.
выражение_логического_или:
выражение || выражение
Операция || группируется слева направо. Она возвращает 1,
если один из операндов отличен от нуля, и 0 в противном слу-
чае. В отличие от операции | операция || гарантирует вычис-
ление слева направо; более того, если первый операнд отличен
от нуля, то значение второго операнда вообще не вычисляется.
Операнды не обязаны быть одинакового типа, но каждый из
них должен быть либо одного из основных типов, либо указате-
лем. Результат всегда имеет тип int.
условное_выражение:
выражение ? выражение : выражение
Условные выражения группируются слева направо. Вычисляется
значение первого выражения, и если оно отлично от нуля, то
результатом будет значение второго выражения; в противном
случае результатом будет значение третьего выражения. Если
это возможно, проводятся обычные арифметические преобразова-
ния, с тем, чтобы привести второе и третье выражения к
общему типу; в противном случае, если оба выражения являются
указателями одинакового типа, то результат имеет тот же тип;
в противном случае одно выражение должно быть указателем, а
другое - константой 0, и результат будет иметь тип указа-
теля. Вычисляется только одно из второго и третьего выраже-
ний.
-18-
Имеется ряд операций присваивания, каждая из которых
группируется слева направо. Все операции требуют в качестве
своего левого операнда l_значение, а типом выражения присва-
ивания является тип его левого операнда. Значением выражения
присваивания является значение, хранимое в левом операнде
после того, как присваивание уже будет произведено. Две
части составной операции присваивания являются отдельными
лексемами.
выражение_присваивания:
l_значение = выражение
l_значение += выражение
l_значение -= выражение
l_значение *= выражение
l_значение /= выражение
l_значение %= выражение
l_значение >>= выражение
l_значение <<= выражение
l_значение &= выражение
l_значение ^= выражение
l_значение |= выражение
Когда производится простое присваивание '=', значение
выражения заменяет значение объекта, на которое ссылается
l_значение. Если оба операнда имеют арифметический тип, то
перед присваиванием правый операнд преобразуется к типу
левого операнда.
В выражение вида e1 оп= e2, где оп - одна из перечис-
ленных выше операций, эквивалентно выражению
e1 = e1 оп (e2), с тем отличием, что выражение e1 вычисля-
ется только один раз. В случае операций += и -= левый опе-
ранд может быть указателем, причем при этом (целочисленный)
правый операнд преобразуется таким образом, как объяснено в
п. 0.4; все правые операнды и все отличные от указателей
левые операнды должны иметь арифметический тип.
Используемые в ОС ДЕМОС компиляторы допускают присваи-
вание указателя целому, целого указателю и указателя указа-
телю другого типа. Такое присваивание является чистым копи-
рованием без каких-либо преобразований. Такое употребление
операций присваивания является непереносимым и может приво-
дить к указателям, которые при использовании вызывают ошибки
адресации. Тем не менее гарантируется, что присваивание ука-
зателю константы 0 дает нулевой указатель, который можно
отличать от указателя на любой объект.
-19-
Структуры могут быть присвоены, а также переданы функ-
циям в качестве аргументов и возвращены функциями. Типы
участвующих операндов должны совпадать.
выражение_с_запятой:
выражение , выражение
Пара выражений, разделенных запятой, вычисляется слева нап-
раво и значение левого выражения отбрасывается. Типом и зна-
чением результата является тип и значение правого операнда.
Эта операция группируется слева направо. В контексте, где
запятая имеет специальное значение, как, например, в списке
фактических аргументов функций или в списках инициализато-
ров, операция запятая, описываемая в этом разделе, может
появляться только в круглых скобках; например, функция
f(a,(t=3,t+2),c)
имеет три аргумента, второй из которых имеет значение 5.
В приводимой ниже таблице сведены правила старшинства и
ассоциативности всех операций. Операции, расположенные в
одной строке, имеют один и тот же уровень старшинства;
строки расположены в порядке убывания старшинства. Так, нап-
ример, операции *, "/" и "%" имеют одинаковый уровень стар-
шинства, который выше, чем уровень операций "+" и "-".
-20-
Таблица 2
------------------------------------------
| Оператор | Ассоциативность |
|____________________|___________________|
| () [] -> . | слева направо |
|____________________|___________________|
| ~ ++ -- - f | справа налево |
|(type) * & sizeof| |
|____________________|___________________|
| * / % | слева направо |
|____________________|___________________|
| + - | слева направо |
|____________________|___________________|
| << >> | слева направо |
|____________________|___________________|
| < <= > >= | слева направо |
|____________________|___________________|
| == != | слева направо |
|____________________|___________________|
| & | слева направо |
|____________________|___________________|
| ^ | слева направо |
|____________________|___________________|
| | | слева направо |
|____________________|___________________|
| && | слева направо |
|____________________|___________________|
| || | слева направо |
|____________________|___________________|
| ?: | справа налево |
|____________________|___________________|
| = += -= и т.п. | справа налево |
|____________________|___________________|
| , | слева направо |
|____________________|___________________|
Отметим, что уровень старшинства побитовых логических
операций &, ^ и | ниже уровня операций == и !=. Это приводит
к тому, что осуществляющие побитовую проверку выражения,
подобные
if ((х & mask) == 0) ...
для получения правильных результатов должны заключаться в
круглые скобки, в противном случае оно будет понято так:
выражение: x & mask == 0
понято как: x & ( mask == 0 )
-21-
Описания используются для указания интерпретации, кото-
рую язык Си будет давать каждому идентификатору; они не обя-
зательно резервируют память, соответствующую идентификатору.
Описания имеют форму
описание:
спецификаторы_описания список_описателей;
необ
Описатели в списке описателей содержат описываемые идентифи-
каторы. Спецификаторы описания представляют собой последо-
вательность спецификаторов типа и спецификаторов класса
памяти.
спецификаторы_описания:
с_типа с_описания
необ
с_класса_памяти с_описания
необ
где c_... - спецификатор_...
Список описателей должен быть согласованным в смысле, описы-
ваемом ниже.
ниже перечисляются спецификаторы класса памяти:
спецификатор_класса_памяти:
auto
static
extern
register
typedef
Спецификатор typedef не резервирует память и называется
"спецификатором класса памяти" только по синтаксическим
соображениям; это обсуждается ниже. Смысл различных классов
памяти был обсужден ранее (см. "Объекты языка Си").
Описания auto, static и register служат также в
качестве определений в том смысле, что они вызывают резерви-
рование нужного количества памяти. В случае extern должно
присутствовать внешнее определение указываемых идентификато-
ров где то вне функции, в которой они описаны.
Описание register лучше всего представлять себе как
описание auto вместе с намеком компилятору, что описанные
таким образом переменные будут часто использоваться. Эффек-
тивны только несколько первых таких описаний. Кроме того, в
-22-
регистрах могут храниться только переменные определенных
типов; на CM-ЭВМ это int, char или указатель. Существует и
другое ограничение на использование регистровых переменных:
к ним нельзя применять операцию взятия адреса &. При разум-
ном использовании регистровых описаний можно ожидать получе-
ния меньших по размеру и более быстрых программ, но в буду-
щем улучшение генерирования кодов может сделать их ненуж-
ными.
В компиляторе для СМ ЭВМ воспринимаются первые 3 описа-
ния register в каждой функции.
Описание может содержать не более одного спецификатора
класса памяти. Если описание не содержит спецификатора
класса памяти, то считается, что он имеет значение auto,
если описание находится внутри некоторой функции, и extern в
противном случае. Исключение: функции никогда не бывают
автоматическими.
Ниже перечисляются спецификаторы типа.
спецификатор_типа:
char
short
int
long
unsigned
float
double
спецификатор_структуры_или_объединения
спецификатор_перечисления
определяющее_тип_имя
Слова long, short и unsigned можно рассматривать как прила-