9. Приложение А: справочное руководство по языку "C"

9.1. Введение


    Это руководство описывает язык "C" для компьютеров DEC PDP-11, Honeywell 6000, IBM система/370 и Interdata 8/32. там, где есть расхождения, мы сосредотачиваемся на версии для PDP-11, стремясь в то же время указать детали, которые зависят от реализации. За малым исключением, эти расхождения непосредственно обусловлены основными свойствами используемого аппаратного оборудования; различные компиляторы обычно вполне совместимы.

10. Лексические соглашения


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

10.1. Комментарии

    Комментарий открывается символами /* и заканчивается символами /*. Комментарии не вкладываются друг в друга.

10.2. Идентификаторы (имена)

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

DEC PDP-11       7 символов, 2 регистра
Honeywell 6000   6 символов, 1 регистр
IBM 360/370      7 символов, 1 регистр
Interdata 8/32   8 символов, 2 регистра

10.3. Ключевые слова

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

  int        extern      else
  char       register    for
  float      typedef     do
  double     static      while
  struct     goto        switch
  union      return      case
  long       sizeof      default
  short      break       entry
  unsigned   continue
  *auto       if

    Ключевое слово entry в настоящее время не используется каким-либо компилятором; оно зарезервировано для использования в будущем. В некоторых реализациях резервируется также слова fortran и asm .

10.4. Константы

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

10.4.1. Целые константы

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

10.4.2. Явные длинные константы

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

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

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

новая строка                      nl/lf/    \n
горизонтальная табуляция          ht        \t
символ возврата на одну позицию   bs        \b
возврат каретки                   cr        \r
переход на новую страницу         ff        \f
обратная косая черта              \         \\
одиночная кавычка                 '         \'
комбинация битов                  ddd       \ddd

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

10.4.4. Плавающие константы

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

10.5. Строки


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

10.6. Характеристики аппаратных средств


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

           Таблица 1
---------------------------------------------------
  DEC PDP-11  Honeywell   IBM 370  Interdata 8/32
  ASCII       ASCII       EBCDIC     ASCII
  char 8 bits   9 bits     8 bits     8 bits
  int  16       36         32         32
  short 16      36         16         16
  long 32       36         32         32
  float 32      36         32         32
  double 64     72         64         64
  range -38/+3 -38/+38    -76/+76    -76/+76
---------------------------------------------------

11. Синтаксическая нотация


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

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

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

12. Что в имене тебе моем?


    Язык "C" основывает интерпретацию идентификатора на двух признаках идентификатора: его классе памяти и его типе. Класс памяти определяет место и время хранения памяти, связанной с идентификатором; тип определяет смысл величин, находящихся в памяти, определенной под идентификатором. Имеются четыре класса памяти: автоматическая, статическая, внешняя и регистровая. Автоматические переменные являются локальными для каждого вызова блока и исчезают при выходе из этого блока. Статические переменные являются локальными, но сохраняют свои значения для следующего входа в блок даже после того, как управление передается за пределы блока. Внешние переменные существуют и сохраняют свои значения в течение выполнения всей программы и могут использоваться для связи между функциями, в том числе и между независимо скомпилированными функциями. Регистровые переменные хранятся (ели это возможно) в быстрых регистрах машины; подобно автоматическим переменным они являются локальными для каждого блока и исчезают при выходе из этого блока.
В языке "C" предусмотрено несколько основных типов об'ектов:

    Об'екты, написанные как символы (char), достаточно велики, чтобы хранить любой член из соответствующего данной реализации внутреннего набора символов, и если действительный символ из этого набора символов хранится в символьной переменной, то ее значение эквивалентно целому коду этого символа. В символьных переменных можно хранить и другие величины, но реализация будет машинно-зависимой.
Можно использовать до трех размеров целых, описываемых как short int, int и long int. Длинные целые занимают не меньше памяти, чем короткие, но в конкретной реализации может оказаться, что либо короткие целые, либо длинные целые, либо те и другие будут эквивалентны простым целым. "Простые" целые имеют естественный размер, предусматриваемый архиитектурой используемой машины; другие размеры вводятся для удволетворения специальных потребностей.
Целые без знака, описываемые как unsigned, подчиняются законам арифметики по модулю 2**n, где n - число битов в их представлении. (На PDP-11 длинные величины без знака не предусмотрены).
Плавающие одинарной точности (float) и плавающие двойной точности (double) в некоторых реализациях могут быть синонимами.
Поскольку об'екты упомянутых выше типов могут быть разумно интерпретированы как числа, эти типы будут называться арифметическими. типы char и int всех размеров совместно будут называться целочисленными. Типы float и double совместно будут называться плавающими типами.
Кроме основных арифметических типов существует концептуально бесконечный класс производных типов, которые образуются из основных типов следующим образом:

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

    Вообще говоря, эти методы построения об'ектов могут применяться рекурсивно.

13. Об'екты и l-значения


    Об'ект является доступным обработке участком памяти; l-значение - это выражение, ссылающееся на об'ект. Очевидным примером выражения l-значения является идентификатор.
Существуют операции, результатом которых являются l-значения; если, например, e - выражение указанного типа, то *e является выражением l-значения, ссылающимся на об'ект e. Название "l-значение" происходит от выражения присваивания e1=e2, в котором левая часть должна быть выражением l-значения. При последующем обсуждении каждой операции будет указываться, ожидает ли она операндов l-значения и выдает ли она l-значение.

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


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

14.1. Символы и целые


    Символ или короткое целое можно использовать всюду, где можно использовать целое. Во всех случаях значение преобразуется к целому. Преобразование более короткого целого к более длинному всегда сопровождается знаковым расширением; целые являются величинами со знаком. Осуществляется или нет знаковое расширение для символов, зависит от используемой машины, но гарантируется, что член стандартного набора символов неотрицателен. Из всех машин, рассматриваемых в этом руководстве, только PDP-11 осуществляет знаковое расширение. Область значений символьных переменных на PDP-11 меняется от -128 до 127; символы из набора ASCII имеют положительные значения. Символьная константа, заданная с помощью восьмеричной условной последовательности, подвергается знаковому расширению и может оказаться отрицательной; например, '\377' имеет значение -1.
Когда более длинное целое преобразуется в более короткое или в char, оно обрезается слева; лишние биты просто отбрасываются.

14.2. Типы float и double


    Вся плавающая арифметика в "C" выполняется с двойной точностью. Каждый раз, когда об'ект типа float появляется в выражении, он удлиняется до double посредством добавления нулей в его дробную часть. Когда об'ект типа double должен быть преобразован к типу float, например, при присваивании, перед усечением double округляется до длины float.

14.3. Плавающие и целочисленные величины


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

14.4. Указатели и целые


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

14.5. Целое без знака


    Всякий раз, когда целое без знака об'единяется с простым целым, простое целое преобразуется в целое без знака и результат оказывается целым без знака. Значением является наименьшее целое без знака, соответствующее целому со знаком (по модулю 2**размер слова). В двоичном дополнительном представлении это преобразование является чисто умозрительным и не изменяет фактическую комбинацию битов.
Когда целое без знака преобразуется к типу long, значение результата совпадает со значением целого без знака. Таким образом, это преобразование сводится к добавлению нулей слева.

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


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

    Сначала любые операнды типа char или short преобразуются в int, а любые операнды типа float преобразуются в double. Затем, если какой-либо операнд имеет тип double, то другой преобразуется к типу double, и это будет типом результата. В противном случае, если какой-либо операнд имеет тип long, то другой операнд преобразуется к типу long, и это и будет типом результата. В противном случае, если какой-либо операнд имеет тип unsigned, то другой операнд преобразуется к типу unsigned, и это будет типом результата. В противном случае оба операнда будут иметь тип int, и это будет типом результата.

15. Выражения


    Старшинство операций в выражениях совпадает с порядком следования основных подразделов настоящего раздела, начиная с самого высокого уровня старшинства. Так, например, выражениями, указываемыми в качестве операндов операции + (п. 15.4), Являются выражения, определенные в п.п. 15.1-15.3. Внутри каждого подраздела операции имеет одинаковое старшинство. В каждом подразделе для описываемых там операций указывается их ассоциативность слева или справа. Старшинство и ассоциативность всех операций в выражениях резюмируются в грамматической сводке в п. 18.
В противном случае порядок вычислений выражений не определен. В частности, компилятор считает себя в праве вычислять подвыражения в том порядке, который он находит наиболее эффективным, даже если эти подвыражения приводят к побочным эффектам. Порядок, в котором происходят побочные эффекты, не специфицируется. Выражения, включающие коммутативные и ассоциативные операции (*,+,&,!,^), могут быть переупорядочены произвольным образом даже при наличии круглых скобок; чтобы вынудить определенный порядок вычислений, в этом случае необходимо использовать явные промежуточные переменные. При вычислении выражений обработка переполнения и проверка при делении являются машинно-зависимыми. Все существующие реализации языка "C" игнорируют переполнение целых; обработка ситуаций при делении на 0 и при всех особых случаях с плавающими числами меняется от машины к машине и обычно выполняется с помощью библиотечной функции.

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


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

    Первичное выражение:

    Идентификатор является первичным выражением при условии, что он описан подходящим образом, как это обсуждается ниже. Тип идентификатора определяется его описанием. Если, однако, типом идентификатора является "массив ...", то значением выражения, состоящего из этого идентификатора , является указатель на первый об'ект в этом массиве, а типом выражения будет "указатель на ...". Более того, идентификатор массива не является выражением l-значения. подобным образом идентификатор, который описан как "функция, возвращающая ...", за исключением того случая, когда он используется в позиции имени функции при обращении, преобразуется в "указатель на функцию, которая возвращает ...".
Константа является первичным выражением. В зависимости от ее формы типом константы может быть int, long или double. Строка является первичным выражением. Исходным ее типом является "массив символов"; но следуя тем же самым правилам, которые приведены выше для идентификаторов, он модифицируется в "указатель на символы", и результатом является указатель на первый символ строки. (Имеется исключение в некоторых инициализаторах; см. П. 16.6.)
Выражение в круглых скобках является первичным выражением, тип и значение которого идентичны типу и значению этого выражения без скобок. Наличие круглых скобок не влияет на то, является ли выражение l-значением или нет.
Первичное выражение, за которым следует выражение в квадратных скобках, является первичным выражением.
Интуитивно ясно, что это выражение с индексом. Обычно первичное выражение имеет тип "указатель на ...", индексное выражение имеет тип int, а типом результата является "...". Выражение e1[e2] по определению идентично выражению * ((e1) + (e2)). Все, что необходимо для понимания этой записи, содержится в этом разделе; вопросы, связанные с понятием идентификаторов и операций * и + рассматриваются в п.п. 15.1, 15.2 И 15.4 соответственно; выводы суммируются ниже в п. 22.3.
Обращение к функции является первичным выражением, за которым следует заключенный в круглые скобки возможно пустой список выражений, разделенных запятыми, которые и представляют собой фактические аргументы функции. Первичное выражение должно быть типа "функция, возвращающая ...", а результат обращения к функции имеет тип "...". Как указывается ниже, ранее не встречавщийся идентификатор, за которым непосредственно следует левая круглая скобка, считается описанным по контексту, как представляющий функцию, возвращающую целое; следовательно чаще всего встречающийся случай функции, возвращающей целое значение, не нуждается в описании.
Перед обращением любые фактические аргументы типа float преобразуются к типу double, любые аргументы типа char или short преобразуются к типу int, и, как обычно, имена массивов преобразуются в указатели. Никакие другие преобразования не выполняются автоматически; в частности, не сравнивает типы фактических аргументов с типами формальных аргументов. Если преобразование необходимо, используйте явный перевод типа (cast); см. П.п. 15.2, 16.7.
При подготовке к вызову функции делается копия каждого фактического параметра; таким образом, все передачи аргументов в языке "C" осуществляются строго по значению. функция может изменять значения своих формальных параметров, но эти изменения не влияют на значения фактических параметров. С другой строны имеется возможность передавать указатель при таком условии, что функция может изменять значение об'екта, на который этот указатель указывает. Порядок вычисления аргументов в языке не определен; обратите внимание на то, что различные компиляторы вычисляют по разному. Допускаются рекурсивные обращения к любой функции. Первичное выражение, за которым следует точка и идентификатор, является выражением. Первое выражение должно быть l-значением, именующим структуру или об'единение, а идентификатор должен быть именем члена структуры или об'единения. Результатом является l-значение, ссылающееся на поименованный член структуры или об'единения. Первичное выражение, за которым следует стрелка (составленная из знаков - и >) и идентификатор, является выражением. Первое выражение должно быть указателем на структуру или об'единение, а идентификатор должен именовать член этой структуры или об'единения. Результатом является l-значение, ссылающееся на поименованный член структуры или об'единения, на который указывает указательное выражение.
Следовательно, выражение e1->mos является тем же самым, что и выражение (*e1).mos. Структуры и об'единения рассматриваются в п. 16.5. Приведенные здесь правила использования структур и об'единений не навязываются строго, для того чтобы иметь возможность обойти механизм типов. См. П. 22.1.

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


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

    Унарное-выражение:

    Унарная операция * означает косвенную адресацию: выражение должно быть указателем, а результатом является l-значение, ссылающееся на тот об'ект, на который указывает выражение. Если типом выражения является "указатель на...", то типом результата будет "...".
Результатом унарной операции & является указатель на об'ект, к которому ссылается l-значение. Если l-значение имеет тип "...", то типом результата будет "указатель на ...".
Результатом унарной операции - (минус) является ее операнд, взятый с противоположным знаком. Для величины типа unsigned результат получается вычитанием ее значения из 2**n (два в степени n), где n-число битов в int. Унарной операции + (плюс) не существует.
Результатом операции логического отрицания ! Является 1, если значение ее операнда равно 0, и 0, если значение ее операнда отлично от нуля. Результат имеет тип int. Эта операция применима к любому арифметическому типу или указателям. Операция ~ дает обратный код, или дополнение до единицы, своего операнда. Выполняются обычные арифметические преобразования. Операнд должен быть целочисленного типа. Об'ект, на который ссылается операнд l-значения префиксной операции ++, увеличивается. значением является новое значение операнда, но это не l-значение. Выражение ++х эквивалентно х+=1. Информацию о преобразованиях смотри в разборе операции сложения (п. 15.4) и операции присваивания (п. 15.14).
Префиксная операция -- аналогична префиксной операции ++, но приводит к уменьшению своего операнда l-значения. При применении постфиксной операции ++ к l-значению результатом является значение об'екта, на который ссылается l-значение. После того, как результат принят к сведению, об'ект увеличивается точно таким же образом, как и в случае префиксной операции ++. Результат имеет тот же тип, что и выражение l-значения.
При применении постфиксной операции -- к l-значению результатом является значение об'екта, на который ссылается l-значение. После того, как результат принят к сведению, об'ект уменьшается точно таким же образом, как и в случае префиксной операции --. Результат имеет тот же тип, что и выражение l-значения.
Заключенное в круглые скобки имя типа данных,стоящее перед выражением , вызывает преобразование значения этого выражения к указанному типу. Эта конструкция называется перевод (cast). Имена типов описываются в п. 16.7.
Операция sizeof выдает размер своего операнда в байтах. (Понятие байт в языке не определено, разве только как значение операции sizeof. Однако во всех существующих реализациях байтом является пространство, необходимое для хранения об'екта типа char). При применении к массиву результатом является полное число байтов в массиве. Размер определяется из описаний об'ектов в выражении. Это выражение семантически является целой константой и может быть использовано в любом месте, где требуется константа. Основное применение эта операция находит при связях с процедурами, подобным распределителям памяти, и в системах ввода-вывода. Операция sizeof может быть также применена и к заключенному в круглые скобки имени типа. В этом случае она выдает размер в байтах об'екта указанного типа. Конструкция sizeof (тип) рассматривается как целое, так что выражение sizeof (тип) - 2 эквивалентно выражению (sizeof (тип)9 - 2.

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


    Мультипликативные операции *, /, и % группируются слева направо. Выполняются обычные арифметические преобразования.

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

    Бинарная операция * означает умножение. Операция * ассоциативна, и выражения с несколькими умножениями на одном и том же уровне могут быть перегруппированы компилятором. Бинарная операция / означает деление. При делении положительных целых осуществляется усечение по направлению к нулю, но если один из операндов отрицателен, то форма усечения зависит от используемой машины. На всех машинах, охватываемых настоящим руководством, остаток имеет тот же знак , что и делимое. Всегда справедливо, что (a/b)*b+a%b равно a (если b не равно 0).
Бинарная операция % выдает остаток от деления первого выражения на второе. Выполняются обычные арифметические преобразования. Операнды не должны быть типа float.

15.4. Аддитивные операции


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

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

    Результатом операции + является сумма операндов. Можно складывать указатель на об'ект в массиве и значение любого целочисленного типа. во всех случаях последнее преобразуется в адресное смещение посредством умножения его на длину об'екта, на который указывает этот указатель. Результатом является указатель того же самого типа, что и исходный указатель, который указывает на другой об'ект в том же массиве, смещенный соответствующим образом относительно первоначального об'екта. Таким образом, если p является указателем об'екта в массиве, то выражение p+1 является указателем на следующий об'ект в этом массиве.
Никакие другие комбинации типов для указателей не разрешаются.
Операция + ассоциативна, и выражение с несколькими сложениями на том же самом уровне могут быть переупорядочены компилятором.
Результатом операции - является разность операндов. Выполняются обычные арифметические преобразования. Кроме того, из указателя может быть вычтено значение любого целочисленного типа, причем, проводятся те же самые преобразования, что и при операции сложения.
Если вычитаются два указателя на об'екты одинакового типа, то результат преобразуется (делением на длину об'екта) к типу int, представляя собой число об'ектов, разделяющих указываемые об'екты. Если эти указатели не на об'екты из одного и того же массива, то такое преобразование, вообще говоря, даст неожиданные результаты, потому что даже указатели на об'екты одинакового типа не обязаны отличаться на величину, кратную длине об'екта.

15.5. Операции сдвига


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

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

    Значением выражения e1<<e2 является e1 (интерпретируемое как комбинация битов), сдвинутое влево на e2 битов; освобождающиеся биты заполняются нулем. значением выражения e1>>e2 является e1, сдвинутое вправо на e2 битовых позиций. Если e1 имеет тип unsigne, то сдвиг вправо гарантированно будет логическим (заполнение нулем); в противном случае сдвиг может быть (и так и есть на PDP-11) арифметическим (освобождающиеся биты заполняются копией знакового бита).

15.6. Операции отношения


    Операции отношения группируются слева направо, но этот факт не очень полезен; выражение a<b<c не означает того, что оно казалось бы должно означать.

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

    Операции < (меньше), > (больше), <= (меньше или равно) и >= (больше или равно) все дают 0, если указанное отношение ложно, и 1, если оно истинно. Результат имеет тип int. Выполняются обычные арифметические преобразования. Могут сравниваться два указателя; результат зависит от относительного расположения указываемых об'ектов в адресном пространстве. Сравнение указателей переносимо только в том случае, если указатели указывают на об'екты из одного и того же массива.

15.7. Операции равенства


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

    Операции == (равно) и != (не равно) в точности аналогичны операциям отношения, за исключением того, что они имеют более низкий уровень старшинства. (Поэтому значение выражения a<b==c<d равно 1 всякий раз, когда выражение a<b и c<d имеют одинаковое значение истинности).
Указатель можно сравнивать с целым, но результат будет машинно- независимым только в том случае, если целым является константа 0. Гарантируется, что указатель, которому присвоено значение 0, не указывает ни на какой об'ект и на самом деле оказывается равным 0; общепринято считать такой указатель нулем.

15.8. Побитовая операция 'и'


    Выражение-и:

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

15.9. Побитовая операция исключающего 'или'


    Выражение-исключающего-или:

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

15.10. Побитовая операция включающего 'или'


    Выражение-включающего-или:

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

15.11. Логическая операция 'и'


    Выражение-логического-и:

    Операция && группируется слева направо. Она возвращает 1, если оба ее операнда отличны от нуля, и 0 в противном случае. В отличие от & операция && гарантирует вычисление слева направо; более того, если первый операнд равен 0, то значение второго операнда вообще не вычисляется.
Операнды не обязаны быть одинакового типа, но каждый из них должен быть либо одного из основных типов, либо указателем. Результат всегда имеет тип int.

15.12. Операция логического 'или'


    Выражение-логического-или:

    Операция || группируется слева направо. Она возвращает 1, если один из операндов отличен от нуля, и 0 в противном случае. В отличие от операции | операция || гарантирует вычисление слева направо; более того, если первый операнд отличен от нуля, то значение второго операнда вообще не вычисляется.
Операнды не обязаны быть одинакового типа, но каждый из них должен быть либо одного из основных типов, либо указателем. Результат всегда имеет тип int.

15.13. Условная операция


    Условное-выражение:

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

15.14. Операция присваивания


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

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

    Когда производится простое присваивание c'=', значение выражения заменяет значение об'екта, на которое ссылается l-значение. Если оба операнда имеют арифметический тип, то перед присваиванием правый операнд преобразуется к типу левого операнда.
О свойствах выражения вида e1 оп = e2, где oп - одна из перечисленных выше операций, можно сделать вывод, если учесть, что оно эквивалентно выражению e1 = e1 оп (e2); однако выражение e1 вычисляется только один раз. В случае операций += и -= левый операнд может быть указателем, причем при этом (целочисленный) правый операнд преобразуется таким образом, как об'яснено в п. 15.4; все правые операнды и все отличные от указателей левые операнды должны иметь арифметический тип.
Используемые в настоящее время компиляторы допускают присваивание указателя целому, целого указателю и указателя указателю другого типа. такое присваивание является чистым копированием без каких-либо преобразований. Такое употребление операций присваивания является непереносимым и может приводить к указателям, которые при использовании вызывают ошибки адресации. Тем не менее гарантируется, что присваивание указателю константы 0 дает нулевой указатель, который можно отличать от указателя на любой об'ект.

15.15. Операция запятая


    Выражение-с-запятой:

    Пара выражений, разделенных запятой, вычисляется слева направо и значение левого выражения отбрасывается. Типом и значением результата является тип и значение правого операнда. Эта операция группируется слева направо. В контексте, где запятая имеет специальное значение, как, например, в списке фактических аргументов функций (п. 15.1) Или в списках инициализаторов (п. 16.6), Операция запятая, описываемая в этом разделе, может появляться только в круглых скобках; например, функция

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

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

16. Описания


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

  Описание:
  спецификаторы-описания список-описателей
           необ;

    Описатели в списке описателей содержат описываемые идентификаторы. Спецификаторы описания представляют собой последовательность спецификаторов типа и спецификаторов класса памяти.

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

    Список должен быть самосогласованным в смысле, описываемом ниже.

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


    Ниже перечисляются спецификаторы класса памяти:

    спецификатор-класса-памяти:

  auto
  static
  extern
  register
  typedef

    Спецификатор typedef не реализует памяти и называется "спецификатором класса памяти" только по синтаксическим соображениям; это обсуждается в п. 16.8. Смысл различных классов памяти был обсужден в п. 12.
Описания auto, static и register служат также в качестве определений в том смысле, что они вызывают резервирование нужного количества памяти. В случае extern должно присутствовать внешнее определение (п. 18) указываемых идентификаторов где-то вне функции, в которой они описаны. Описание register лучше всего представлять себе как описание auto вместе с намеком компилятору, что описанные таким образом переменные будут часто использоваться. Эффективны только несколько первых таких описаний. Кроме того, в регистрах могут храниться только переменные определенных типов; на PDP-11 это int, char или указатель. Существует и другое ограничение на использование регистровых переменных: к ним нельзя применять операцию взятия адреса &. При разумном использовании регистровых описаний можно ожидать получения меньших по размеру и более быстрых программ, но улучшение в будущем генерирования кодов может сделать их ненужными.
Описание может содержать не более одного спецификатора класса памяти. Если описание не содержит спецификатора класса памяти, то считается, что он имеет значение auto, если описание находится внутри некоторой функции, и extern в противном случае. исключение: функции никогда не бывает автоматическими.

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


    Ниже перечисляются спецификаторы типа.

    Спецификатор-типа:

  char
  short
  int
  long
  unsigned
  float
  double
спецификатор-структуры-или-об'единения
определяющее-тип-имя

    Слова long, short и unsigned можно рассматривать как прилагательные; допустимы следующие комбинации:

  short int
  long int
  unsigned int
  long float

    Последняя комбинация означает то же, что и double. В остальном описание может содержать не более одного спецификатора типа. Если описание не содержит спецификатора типа, то считается, что он имеет значение int. Спецификаторы структур и об'единений обсуждаются в п. 16.5; Описания с определяющими тип именами typedef обсуждаются в п. 16.8.

16.3. Описатели


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

    Список-описателей:

инициализируемый-описатель
инициализируемый-описатель, список-описателей
инициализируемый-описатель:
описатель-инициализатор
необ

    Инициализаторы описываются в п. 16.6. Спецификаторы и описания указывают тип и класс памяти об'ектов, на которые ссылаются описатели. Описатели имеют следующий синтаксис:

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

( описатель )
* описатель
описатель ()
описатель [константное-выражение
   необ]

    Группирование такое же как и в выражениях.

16.4. Смысл описателей


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

  t     di

где t - спецификатор типа (подобный int и т.д.), а di - описатель. Предположим, что это описание приводит к тому, что соответствующий идентификатор имеет тип "...t", где "..." пусто, если di просто отдельный идентификатор (так что тип x в "int x" просто int). Тогда , если di имеет форму

  *d

то содержащийся идентификатор будет иметь тип "... указатель на t".
Если di имеет форму

  d()

то содержащийся идентификатор имеет тип "... Функция, возвращающая t". Если di имеет форму

    d[константное-выражение] или

  d[ ]

то содержащийся идентификатор имеет тип "...массив t". В первом случае константным выражением является выражение, значение которого можно определить во время компиляции и которое имеет тип int. (Точное определение константного выражения дано в п. 23). Когда несколько спецификаций вида "массив из" оказываются примыкающими, то создается многомерный массив; константное выражение, задающее границы массивов, может отсутствовать только у первого члена этой последовательности. Такое опускание полезно, когда массив является внешним и его фактическое определение, которое выделяет память, приводится в другом месте. Первое константное выражение может быть опущено также тогда, когда за описателем следует инициализация. В этом случае размер определяется по числу приведенных инициализируемых элементов.
Массив может быть образован из элементов одного из основных типов, из указателей, из структур или об'единений или из других массивов (чтобы образовать многомерный массив). Не все возможности, которые разрешены с точки зрения указанного выше синтаксиса, фактически допустимы. Имеются следующие ограничения: функции не могут возвращать массивы, структуры, об'единения или функции, хотя они могут возвращать указатели на такие вещи; не существует массивов функций, хотя могут быть массивы указателей на функции. Аналогично, структуры или об'единения не могут содержать функцию, но они могут содержать указатель на функцию.
В качестве примера рассмотрим описание

  int i, *ip, f(), *fip(), (*pfi)();

в котором описывается целое i, указатель ip на целое, функция f, возвращающая целое, функция fip, возвращающая указатель на целое, и указатель pfi на функцию, которая возвращает целое. Особенно полезно сравнить два последних описателя. Связь в *fip() можно представить в виде *(fip()), так что описанием предполагается, а такой же конструкцией в выражении требуется обращение к функции fip и последующее использование косвенной адресации для выдачи с помощью полученного результата (указателя) целого. В описателе (*pfi)() дополнительные скобки необходимы, поскольку они точно так же, как и в выражении, указывают, что косвенная адресация через указатель на функцию выдает функцию, которая затем вызывается; эта вызванная функция возвращает целое.
В качестве другого примера приведем описание

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

в котором описывается массив чисел типа float и массив указателей на числа типа float. Наконец,

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

описывает статический трехмерный массив целых размером 3*5*7. более подробно, x3d является массивом из трех элементов; каждый элемент является массивом пяти массивов; каждый последний массив является массивом из семи целых. Каждое из выражений x3d, x3d[i], x3d[i][j] и x3d[i][j][k] может разумным образом появляться в выражениях. Первые три имеют тип "массив", последнее имеет тип int.

16.5. Описание структур и об'единений


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

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

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

    структура-или-об'единение:

    список-описаний-структуры является последовательностью описаний членов структуры или об'единения:

    список-описаний-структуры:
описание-структуры
описание-структуры список-описаний-структуры
описание-структуры:
спецификатор-типа список-описателей-структуры
список-описателей-структуры:
описатель-структуры
описатель-структуры, список-описателей-структуры

    В обычном случае описатель структуры является просто описателем члена структуры или об'единения. Член структуры может также состоять из специфицированного числа битов.
Такой член называется также полем; его длина отделяется от имени поля двоеточием.

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

    Внутри структуры описанные в ней об'екты имеют адреса, которые увеличиваются в соответствии с чтением их описаний слева направо. Каждый член структуры, который не является полем, начинается с адресной границы, соответствующей его типу; следовательно в структуре могут оказаться неименованные дыры. Члены, являющиеся полями, помещаются в машинные целые; они не перекрывают границы слова. Поле, которое не умещается в оставшемся в данном слове пространстве, помещается в следующее слово. Поля выделяются справа налево на PDP-11 и слева направо на других машинах.
Описатель структуры, который не содержит описателя, а только двоеточие и ширину, указывает неименованное поле, полезное для заполнения свободного пространства с целью соответствия задаваемых извне схемам. Специальный случай неименованного поля с шириной 0 используется для указания о выравнивании следующего поля на границу слова. При этом предполагается, что "следующее поле" действиетльно является полем, а не обычным членом структуры, поскольку в последнем случае выравнивание осуществляется автоматически.
Сам язык не накладывает ограничений на типы об'ектов, описанных как поля, но от реализаций не требуется обеспечивать что-либо отличное от целых полей. Более того, даже поля типа int могут рассматриваться как неимеющие знака. На PDP-11 поля не имеют знака и могут принимать только целые значения. Во всех реализациях отсутствуют массивы полей и к полям не применима операция взятия адреса &, так что не существует и указателей на поля.
Об'единение можно представить себе как структуру, все члены которой начинаются со смещения 0 и размер которой достаточен, чтобы содержать любой из ее членов. В каждый момент об'единение может содержать не более одного из своих членов. Спецификатор структуры или об'единения во второй форме, т.е. один из

    struct идентификатор {список-описаний-структуры}

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

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

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

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

    Такая структура содержит массив из 20 символов, целое и два указателя на подобные структуры. Как только приведено такое описание, описание

  struct tnode s, *sp;

говорит о том, что s является структурой указанного вида, а sp является указателем на структуру указанного вида. При наличии этих описаний выражение

  sp->count

ссылается к полю count структуры, на которую указывает sp; выражение

  s.left

ссылается на указатель левого поддерева в структуре s, а выражение

  s.right->tword[0]

ссылается на первый символ члена tword правого поддерева из s.

16.6. Инициализация


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

    Инициализатор:

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

    Все выражения, входящие в инициализатор статической или внешней переменной, должны быть либо константными выражениями, описываемыми в п. 23, Либо выражениями, которые сводятся к адресу ранее описанной переменной, возможно смещенному на константное выражение. Автоматические и регистровые переменные могут быть инициализированы произвольными выражениями, включающими константы и ранее описанные переменные и функции.
Гарантируется, что неинициализированные статические и внешние переменные получают в качестве начальных значений 0;неинициализированные автоматические и регистровые переменные в качестве начальных значений содержат мусор. Когда инициализатор применяется к скаляру (указателю или об'екту арифметического типа), то он состоит из одного выражения, возможно заключенного в фигурные скобки. Начальное значение об'екта находится из выражения; выполняются те же самые преобразования, что и при присваивании. Когда описываемая переменная является агрегатом (структурой или массивом ), то инициализатор состоит из заключенного в фигурные скобки и разделенного запятыми списка инициализаторов для членов агрегата. Этот список составляется в порядке возрастания индекса или в соответствии с порядком членов. Если агрегат содержит подагрегаты, то это правило применяется рекурсивно к членам агрегата. Если количество инициализаторов в списке оказывается меньше числа членов агрегата, то оставшиеся члены агрегата заполняются нулями. Запрещается инициализировать об'единения или автоматические агрегаты.
Фигурные скобки могут быть опущены следующим образом. Если инициализатор начинается с левой фигурной скобки, то последующий разделенный запятыми список инициализаторов инициализирует члены агрегата; будет ошибкой, если в списке окажется больше инициализаторов, чем членов агрегата. Если однако инициализатор не начинается с левой фигурной скобки, то из списка берется только нужное для членов данного агрегата число элементов; оставшиеся элементы используются для инициализации следующего члена агрегата, частью которого является настоящий агрегат.
Последнее сокращение допускает возможность инициализации массива типа char с помощью строки. В этом случае члены массива последовательно инициализируются символами строки. Например,

  int x[] = {1,3,5} ;

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

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

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

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

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

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

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

  char msg[] = "syntax error on line %s\n";

демонстрирует инициализацию элементов символьного массива с помощью строки.

16.7. Имена типов


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

    Имя типа:

спецификатор-типа абстрактный-описатель
абстрактный-описатель:
пусто
(абстрактный-описатель)
* абстрактный описатель
абстрактный-описатель ()
абстрактный-описатель [константное выражение
необ]

    Во избежании двусмысленности в конструкции

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

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

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

16.8. typedef


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

    Определяющее-тип-имя
идентификатор

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

  typedef int miles, >*klicksp;
  typedef struct { double re, im; } complex;

конструкции

  miles distance;
  extern klicksp metricp;
  complex z, *zp;

становятся законными описаниями; при этом типом distance является int, типом metricp - "указатель на int", типом z специфицированная структура и типом zp - указатель на такую структуру.
Спецификатор typedef не вводит каких-либо совершенно новых типов, а только определяет синонимы для типов, которые можно было бы специфицировать и другим способом. Так в приведенном выше примере переменная distance считается имеющей точно такой же тип, что и любой другой об'ект, описанный в int.