 Шолмов Л.И. Си как язык программирования общего назначения

Шолмов Л.И. Си как язык программирования общего назначения

Оглавление
Часть 1
Часть 2
Часть 3
Часть 4
Часть 5


                                       - 2 -

                                    Оглавление

                                                                  Лист

           1.      Введение.........................................5
           1.1.    Соглашения о нотации.............................6
           2.      Элементы MS C....................................8
           2.1.    Множества символов...............................8
           2.1.1.  Буквы и цифры....................................9
           2.1.2.  Пробельные символы...............................9
           2.1.3.  Энаки пунктуации и специальные символы..........10
           2.1.4.  ESC- последовательности.........................11
           2.1.5.  Операции........................................12
           2.2.    Константы.......................................14
           2.2.1.  Целые константы.................................14
           2.2.2.  Константы с плавающей точкой....................16
           2.2.3.  Константа- символ...............................17
           2.2.4.  Строковые литералы..............................18
           2.3.    Идентификаторы..................................18
           2.4.    Ключевые слова..................................19
           2.5.    Комментарии.....................................20
           2.6.    Лексемы.........................................21
           3.      Структура программы.............................22
           3.1.    Исходная программа..............................22
           3.2.    Исходные файлы..................................23
           3.3.    Выполнение программ.............................24
           3.4.    Время жизни и видимость.........................25
           3.5.    Классы имен.....................................28
           4.      Об"явления......................................30
           4.1.    Спецификаторы типов.............................31
           4.2.    Область значений величин........................33
           4.3.    Деклараторы.....................................34
           4.3.1.  Деклараторы массивов, функций и указателей......34
           4.3.2.  Составные деклараторы...........................35
           4.3.3.  Деклараторы со специальными ключевыми словами...37
           4.4.    Об"явления переменной...........................39
           4.4.1.  Об"явление простой переменной...................40
           4.4.2.  Об"явление перечисления.........................40
           4.4.3.  Об"явления структур.............................42
           4.4.4.  Об"явление совмещений...........................45
           4.4.5.  Об"явление массива..............................46
           4.4.6.  Об"явление указателей...........................48
           4.5.    Об"явление функций..............................49
           4.6.    Классы памяти...................................51
           4.6.1.  Об"явление переменной на внешнем уровне.........52
           4.6.2.  Об"явление переменной на внутреннем уровне......55
           4.6.3.  Об"явление функции на внешнем и внутреннем
                   уровнях.........................................56
           4.7.    Инициализация...................................57
           4.7.1.  Базовые типы и типы указателей..................57
           4.7.2.  Составные типы..................................58
           4.7.3.  Строковые инициализаторы........................61
           4.8.    Об"явления типов................................61
           4.8.1.  Типы структур, совмещений и перечислений........61
           4.8.2.  Об"явления typedef..............................62
           4.9.    Имена типов.....................................63

                                       - 3 -

           5.      Выражения и присваивания........................65
           5.1.    Введение........................................65
           5.2.    Операнды........................................65
           5.2.1.  Константы.......................................65
           5.2.2.  Идентификаторы..................................66
           5.2.3.  Строки..........................................66
           5.2.4.  Вызовы функций..................................67
           5.2.5.  Индексные выражения.............................67
           5.2.6.  Выражение выбора структурного элемента..........69
           5.2.7.  Выражения с операциями..........................70
           5.2.8.  Выражения в скобках.............................71
           5.2.9.  Type-cast выражения.............................71
           5.2.10. Константные выражения...........................71
           5.3.    Операции........................................72
           5.3.1.  Обычные арифметические преобразования...........73
           5.3.2.  Операции дополнения.............................73
           5.3.3.  Операции адресации и разадресации...............74
           5.3.4.  Операция sizeof.................................75
           5.3.5.  Мультипликативные операции......................76
           5.3.6.  Аддитивные операции.............................77
           5.3.7.  Операции сдвига.................................78
           5.3.8.  Операции отношения..............................79
           5.3.9.  Побитовые операции..............................80
           5.3.10. Логические операции.............................81
           5.3.11. Операция последовательного вычисления...........81
           5.3.12. Условная операция...............................82
           5.4.    Операторы присваивания..........................83
           5.4.1.  Lvalue-выражение................................83
           5.4.2.  Унарные инкремент и декремент...................84
           5.4.3.  Простое присваивание............................84
           5.4.4.  Составное присваивание..........................85
           5.5.    Старшинство и порядок выполнения................85
           5.6.    Побочные эффекты................................88
           5.7.    Преобразование типов............................89
           5.7.1.  Преобразование типов при присваивании...........89
           5.7.2.  Преобразование type-cast........................94
           5.7.3.  Преобразования, выполняемые операциями..........94
           5.7.4.  Преобразования при вызовах функций..............94
           6.      Операторы.......................................96
           6.1.    Введение........................................96
           6.2.    Оператор break..................................97
           6.3.    Составной оператор..............................97
           6.4.    Оператор continue...............................98
           6.5.    Оператор do.....................................99
           6.6.    Оператор-выражение..............................99
           6.7.    Оператор for...................................100
           6.8.    GOTO и помеченные операторы....................101
           6.9.    Оператор if....................................101
           6.10.   Оператор null..................................103
           6.11.   Оператор return................................103
           6.12.   Оператор switch................................104
           6.13.   Оператор while.................................107
           7.      Функции........................................107
           7.1.    Введение.......................................107
           7.2.    Определение функции............................108

                                       - 4 -

           7.2.1.  Класс памяти...................................108
           7.2.2.  Тип возврата...................................109
           7.2.3.  Формальные параметры...........................110
           7.2.4.  Тело функции...................................113
           7.3.    Об"явления функции.............................113
           7.4.    Вызовы функций.................................115
           7.4.1.  Фактические аргументы..........................117
           7.4.2.  Вызовы с переменным числом аргументов..........119
           7.4.3.  Рекурсивные вызовы.............................119
           8.      Директивы препроцессора и указания
                   компилятору....................................120
           8.1.    Поименованные константы и макросы..............120
           8.1.1.  Директива #define..............................121
           8.1.2.  Директива #undef...............................123
           8.2.    #include файлы.................................124
           8.3.    Условная компиляция............................125
           8.3.1.  Директивы #if, #elif, #else, #endif............125
           8.3.2.  Директивы #ifdef и #ifndef.....................128
           8.4.    Управление нумерацией строк....................129


                                       - 5 -

                                    1. ВВЕДЕНИЕ

               Настоящий документ разработан заведующим сектором Шолмовым
         Л.И. при помощи средств редактора текстов ЛЕКСИКОН.

               Си  -  это  язык программирования общего назначения, хорошо
         известный своей эффективностью, экономичностью, и переносимостью.
         Указанные преимущества Си обеспечивают хорошее качество разработ-
         ки почти любого вида программного продукта.  Использование  Си  в
         качестве  инструментального  языка  позволяет  получать быстрые и
         компактные программы. Во многих случаях программы, написанные  на
         Си,  сравнимы по скорости с программами, написанными на языке ас-
         семблера. При этом они имеют лучшую наглядность и их более просто
         сопровождать.
               Си сочетает эффективность и мощность в  относительно  малом
         по размеру языке. Хотя Си не содержит встроенных компонент языка,
         выполняющих ввод-вывод, распределение памяти, манипуляций с экра-
         ном  или управление процессами, тем не менее, системное окружение
         Си располагает библиотекой об"ектных модулей, в которой  реализо-
         ваны подобные функции. Библиотека поддерживает многие из функций,
         которые требуются.
               Это  решение  позволяет изолировать языковые особенности от
         специфики процессора, на котором выполняется результирующая прог-
         рамма. Строгое определение языка делает его независимым от  любых
         деталей  операционной  системы или машины. В то же время програм-
         мисты могут добавить в библиотеку специфические  системные  прог-
         раммы, чтобы более эффективно использовать конкретной особенности
         машины.

               Перечислим некоторые существенные особенности языка Си:

               Си  обеспечивает полный набор операторов структурного прог-
         раммирования.

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

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

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

                                      - 6 -

         может быть использован для любого текстового файла.

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

               Материал руководства организован следующим образом.

               В разделе 2 "Элементы MSC" описываются буквы, цифры и  сим-
         волы, которые могут быть использованы в программах на Си, а также
         комбинации  символов, которые имеют специальный смысл для Си-ком-
         пилятора.
               В разделе 3 "Сруктура программы"  обсуждаются  структура  и
         компоненты  программы,  а  также об"ясняется организация исходных
         файлов программ на Си.
               В разделе 4 "Об"явления" описывается то, каким образом спе-
         цифицировать атрибуты Си: переменные, функции, а также типы,  оп-
         ределенные пользователем. Си поддерживает некоторое число предоп-
         ределенных  типов  данных  и позволяет об"являть составные типы и
         указатели.
               В разделе 5 "Выражения и присваивания " описываются операн-
         ды и операции, из которых формирутся выражения и присваивания.  В
         разделе также обсуждаются вопросы преобразования типов и побочные
         эффекты, которые могут возникнуть при этом.
               В  разделе 6 "Операторы" описываются операторы, выполняющие
         программу на Си.
               В разделе 7 "Функции" обсуждаются особенности функций Си. В
         частности, в этом разделе об"ясняется, каким  образом определять,
         об"являть  и  вызывать функции, как описывать параметры функций и
         возвращаемые величины.
               В разделе 8 "Препроцессор директив и указаний  компилятору"
         описываются  команды,  узнаваемые препроцессором Си. Препроцессор
         Си- это текстовый процессор, автоматически вызываемый перед  ком-
         пиляцией.  В  этом  раэделе  также  рассмотрены "pragmas"- указа-
         ния(команды) компилятору,которые располагаются в файлах исходного
         текста.
               В разделе 9 "Приложения" дан краткий обзор отличий опреде-
         лений MSC от определений Си в книге Brian W. Kernighan and Denis
         M. Ritche "The C Programming Language", а также приведено краткое
         изложение синтаксиса MSC.

               1.1 Соглашения о нотации

               В руководстве приняты следующие соглашения о нотации.

                                      - 7 -

           Соглашение                          Значение
           ---------------------------------------------------------

           Жирный шрифт            Жирным шрифтом печатаются ключе-
                                   вые слова такие, как goto и char.

           Жирный шрифт            Жирным шрифтом в угловых скобках
           в угловых скоб-         отмечаются нетерминальные симво-
           ках                     лы в синтаксических конструкциях.
                                   Например,

                                           goto

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

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

                                      int x,y;
                                      .
                                      .
                                      .
                                      swap(&x,&y);

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

                                     {
                                      []
                                      .
                                      .
                                      .

                                      []
                                      .
                                      .
                                      .
                                     }
                                 Вертикальные     многоточия     также
                                 используются в примерах программ для
                                 обозначения части программы, которая
                                 пропущена.
                                 Горизонтальное  многоточие, следующее

                                      - 8 -

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

                                 ={[,]...}

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

           [] Квадратные         Квадратные скобки, ограничивающие син-
           скобки                таксическую конструкцию, обозначают ее
                                 возможное отсутствие. Например,

                                    return[];

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

               2. Элементы MSC

               В этом разделе описываются элементы языка программирования
         MSC. Элементами языка являются имена, числа и символы, используе-
         мые в конструкциях программ на Си. В частности, в этой главе опи-
         сывается следующее:

                -множества символов;
                -константы;
                -идентификаторы;
                -ключевые слова;
                -комментарии;
                -лексемы.

               2.1. Множества символов

               В программах на Си  используется  два  множества  символов:
         множество символов Си и множество представимых символов. Множест-
         во  символов Си содержит буквы, цифры и знаки пунктуации, которые
         имеют определенное значение для компилятора Си. Программы  на  Си
         строятся  путем комбинирования в осмысленные конструкции символов
         из множества Си.

                                      - 9 -

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

               2.1.1 Буквы и цифры

               Множество символов Си включает большие и малые буквы из ан-
         глийского алфавита и 10 десятичных арабских цифр:

                -большие английские буквы:

                A B C D E F G H I J K L M N O P Q R T U V W X Y Z

                -малые английские буквы:

                a b c d e f g h i j k l m n o p q r t u v w x y z

                -десятичные цифры:

                0 1 2 3 4 5 6 7 8 9

               Буквы и цифры используются при формировании констант, иден-
         тификаторов и ключевых слов. Все эти конструкции описаны ниже.
               Компилятор Си рассматривает одну и ту же  малую  и  большую
         буквы как отличные символы. Если в данной записи использованы ма-
         лые буквы, то замена малой буквы "a" на большую букву "A" сделает
         отличной данную запись от предшествующей.

               2.1.2. Пробельные символы

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

                                      - 10 -

         CONTROL-Z.
               Компилятор Си игнорирует пробельные символы,  если  они  не
         используются как разделители или как компоненты константы-символа
         или  строковых  литералов. Это нужно иметь в виду, чтобы дополни-
         тельно использовать пробельные символы для повышения  наглядности
         программы (например,для просмотра редактором текстов).

               2.1.3. Знаки пунктуации и специальные символы

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

           -----------------------------------------------------------
           Символ        Наименование       Символ        Наименование
           -----------------------------------------------------------

             ,           Запятая               !          Восклицатель-
                                                          ный знак
             .           Точка                 |          Вертикальная
                                                          черта
             ;           Точка с за-           /          Наклонная чер-
                         пятой                            та вправо
             :           Двоеточие             \          Наклонная чер-
                                                          та влево
             ?           Знак вопроса          ~          Тильда

             '           Одиночная ка          _          Подчеркивание
                         вычка
             (           Левая круглая         #          Знак номера
                         скобка
             )           Правая круглая        %          Знак процента
                         скобка
             {           Левая фигурная        &          Амперсанд
                         скобка
             }           Правая фигурная       ^          Caret
                         скобка
             <           Левая угловая         -          Знак минус
                         скобка
             >           Правая угловая        =          Знак равно
                         скобка
             [           Левая  квадратная     +          Знак плюс
                         скобка
             ]           Правая квадратная
                         скобка
            -----------------------------------------------------------

                Табл. 2.1. Знаки пунктуации и специальные символы

                                      - 11 -

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

               2.1.4. ESC- последовательности

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

                -------------------------------------------------
                ESC- последовательность      Наименование
                -------------------------------------------------

                       \n                    Новая строка

                       \t                    Горизонтальная табу-
                                             ляция
                       \v                    Вертикальная табуля-
                                             ция
                       \b                    Пробел

                       \r                    Возврат каретки

                       \f                    Новая страница

                       \a                    Звонок(сигнал)

                       \'                    Одиночная кавычка

                       \"                    Двойная кавычка

                       \\                    Наклонная черта влево

                       \ddd                  ASCII символ в восьми-
                                             ричном представлении
                       \xdd                  ASCII символ в шестнад-
                                             цатиричном представлении
                ------------------------------------------------------

                Табл. 2.2. ESC- последовательности

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

                                      - 12 -

         представляет символ "c" в литеральной строке или константе-симво-
         ле.
               Последовательности  \ddd и \xdd позволяют задать любой сим-
         вол в ASCII (Американский стандартный код  информационного интер-
         фейса) как последовательность трех восьмеричных цифр или двух ше-
         стнадцатеричных цифр. Например, символ пробела может  быть  задан
         как \010 или \x08. Код ASCII "нуль" может быть задан  как \0  или
         \x0 . В  восьмеричной  ESC- последовательности могут быть исполь-
         зованы от одной до трех восьмеричных цифр.

               Например, символ пробела может быть задан  как \10 .  Точно
         так же в шестнадцатеричной ESC- последовательности могут быть ис-
         пользованы от одной до двух шестнадцатеричных цифр. Так, шестнад-
         цатеричная последовательность для символа  пробела может быть за-
         дана  как \x08 или \x8 .

               Замечание:

               Когда используется восьмеричная или шестнадцатеричная  ESC-
         последовательность  в  строках,  то  нужно полностью задавать все
         цифры ESC- последовательности (три цифры для восьмеричной  и  две
         цифры для шестнадцатеричной ESC- последовательностей). Иначе, ес-
         ли  символ непосредственно следующий за ESC- последовательностью,
         случайно окажется восьмеричной или шестнадцатеричной  цифрой,  то
         он  проинтерпретируется  как  часть последовательности. Например,
         строка  \x7Bell  при  выводе на печать будет выглядеть как {ell ,
         поскольку  \x7B  проинтерпретируется как символ  левой  фигурной
         скобки({) . Строка  \x07Bell будет правильным представлением сим-
         вола "звонок" с последующим словом Bell.

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

               2.1.5. Операции

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

                                      - 13 -

               Операция sizeof не включена в эту таблицу. Она скорее пред-
         ставляет собой ключевое слово, чем символ.

                -------------------------------------------------
                Операция                     Наименование
                -------------------------------------------------

                   !                         Логическое НЕ

                   ~                         Побитовое дополнение

                   +                         Сложение

                   -                         Вычитание, арифмети-
                                             ческое отрицание
                   *                         Умножение

                   /                         Деление

                   %                         Остаток

                   <<                        Сдвиг влево

                   >>                        Сдвиг вправо

                   <                         Меньше

                   <=                        Меньше или равно

                   >                         Больше

                   >=                        Больше или равно

                   ==                        Равно

                   !=                        Не равно

                   &                         Побитовое И, адрес от

                   |                         Побитовое  включающее ИЛИ

                   ^                         Побитовое исключающее ИЛИ

                   &&                        Логическое И

                   ||                        Логическое ИЛИ

                   '                         Последовательное выполне-
                                             ние (запятая)
                   ?:                        Операция условного вы-
                                             ражения
                   ++                        Инкремент

                   --                        Декремент

                                      - 14 -

                   =                         Простое присваивание

                   +=                        Сложение с присваиванием

                   -=                        Вычитание с присваиванием

                   *=                        Умножение с присваиванием

                   /=                        Деление с присваиванием

                   %=                        Остаток с присваиванием

                   >>=                       Сдвиг вправо с присваива-
                                             иванием
                   <<=                       Сдвиг влево с присваива-
                                             нием
                   &=                        Побитовое И с присваива-
                                             нием
                   |=                        Побитовое включающее ИЛИ
                                             с присваиванием
                   ^=                        Побитовое исключающее ИЛИ
                                             с присваиванием

                -------------------------------------------------------

                          Табл. 2.3. Операции

               Замечание:

               Операция  условного выражения ?: -это тернарная, а не двух-
         символьная  операция.  Формат  условного   выражения   следующий:
         ?:

               2.2. Константы

               Константа- это число, символ или строка символов. Константы
         используются  в  программе  как неизменяемые величины. В языке Си
         различают четыре типа констант: целые константы, константы с пла-
         вающей точкой, константы-символы и строчные литералы.

               2.2.1 Целые константы

               Целая константа- это десятичное, восьмеричное или  шестнад-
         цатеричное число, которое представляет целую величину. Десятичная
         константа имеет следующий формат представления:

               ,

               где    - это одна или более десятичных цифр от 0 до
         9.
               Восьмеричная константа имеет следующий  формат  представле-
         ния:

                                      - 15 -

               0,

               где    - это одна или более восьмеричных цифр от 0
         до 7. Запись ведущего нуля необходима.
               Шестнадцатеричная константа имеет один из следующих  форма-
         тов представления:

               0x
               0X,

               где  одна или более шестнадцатеричных цифр.
               Шестнадцатеричная  цифра  может  быть  цифрой от 0 до 9 или
         буквой (большой или малой) от A до F. В  представлении  константы
         допускается  "смесь" больших и малых букв. Запись ведущего нуля и
         следующего за ним символа x или X необходима.
               Пробельные символы не допускаются между цифрами целой конс-
         танты. В Табл. 2.4 иллюстрируются примеры целых констант.

           -----------------------------------------------------------
           Десятичные           Восьмеричные         Шестнадцатеричные
           константы            константы            константы
           -----------------------------------------------------------
           10                   012                  0xa или 0xA
           132                  0204                 0x84
           32179                076663               0x7dB3 или 0x7DB3
           -----------------------------------------------------------

                      Табл. 2.4 Примеры констант

               Целые константы всегда специфицируют положительные  величи-
         ны.  Если требуется отрицательные величины, то необходимо сформи-
         ровать константное выражение из знака минус и  следующей  за  ним
         константы.  Знак  минус рассматривается как арифметическая опера-
         ция.
               Каждая целая константа специфицируется типом,  определяющим
         ее представление в памяти и область значений. Десятичные констан-
         ты могут быть типа int или long.
               Восьмеричные и шестнадцатеричные константы в зависимости от
         размера  могут  быть  типа  int,  unsigned int, long или unsigned
         long. Если константа может быть представлена как int, она  специ-
         фицируется  типом  int. Если ее величина больше, чем максимальная
         положительная величина, которая  может  быть  представлена  типом
         int,  но  меньше  величины, которая представляется в том же самом
         числе бит как и int, она задается типом  unsigned  int.  Наконец,
         константа,  величина  которой  больше чем  максимальная величина,
         представляемая  типом  unsigned  int,  задется  типом  long   или
         unsigned long, если это необходимо. В Табл. 2.5 показаны диапазо-
         ны  величин восьмеричных и шестнадцатеричных констант, представи-
         мых соответствующими типами на машине, где тип int имеет длину 16
         бит.

           -----------------------------------------------------------
           Шестнадцатеричные           Восьмеричные           Тип
           диапазоны                   диапазоны
           -----------------------------------------------------------

                                      - 16 -

           0x0-0x7FFF              0-077777                   int
           0x8000-0xFFFF           0100000-0177777            unsigned
                                                              int
           0x10000-0x7FFFFFFF      0200000-017777777777       long
           0x80000000-0xFFFFFFFF   020000000000-030000000000  unsigned
                                                              long
           -----------------------------------------------------------

                Табл.    2.5    Диапазоны   величин   восьмеричных   и
                                шестнадцатеричных констант

               Важность рассмотренных выше правил состоит в том, что вось-
         меричные и шестнадцатеричные  константы  не  содержат  "знаковых"
         расширений, когда они преобразуются к более длинным типам (преоб-
         разование типов смотри в разделе 5 "Выражения и присваивания").
               Программист  может определить для любой целой константы тип
         long, приписав букву "l" или "L" в конец константы. В  Табл.  2.6
         показаны примеры целых констант.

           ------------------------------------------------------------
           Десятичные          Восьмеричные           Шестнадцатеричные
           константы           константы              константы
           ------------------------------------------------------------

           10L                 012L                   0xaL или 0xAL
           79l                 0115l                  0x4fl или 0x4Fl
           ------------------------------------------------------------

                Табл. 2.6 Примеры целых констант типа long

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

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

               [][.][E[-]],

               где   - одна или более десятичных цифр (от 0 до 9),
         а E или e -символ экспоненты. Целая или дробная  части  константы
         могут  быть опушены, но не обе сразу. Десятичная точка может быть
         опущена только тогда, когда задана экспонента.
               Экспонента состоит из символа экспоненты, за которым следу-
         ет целочисленная величина экспоненты, возможно отрицательная.
               Пробельные символы не могут  разделять  цифры  или  символы
         константы.
               Константы  с  плавающей точкой всегда специфицируют положи-
         тельные величины. Если требуются отрицательные величины, то необ-
         ходимо сформировать константное выражение из знака минус и следу-
         ющей за ним константы. Знак минус рассматривается  как  арифмети-
         ческая операция.
               Примеры  констант  с плавающей точкой и константных выраже-

                                      - 17 -

         ний:

               15.75
               1.575E1
               1575e-2
               -0.0025
               -2.5e-3
               25e-4
               Целая часть константы с плавающей точкой может быть  опуще-
         на, например:

               .75
               .0075e2
               -.125
               -.175E-2

               Все константы с плавающей точкой имеют тип double.

               2.2.3. Константа-символ

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

               '',
               где   может быть любым символом иэ множества предста-
         вимых символов, включая любой ESC- символ, исключая одиночную ка-
         вычку ('), наклонную черту влево (\) и символ новой строки.
               Чтобы использовать одиночную кавычку  или  наклонную  черту
         влево  в  качестве  константы-символа,  необходимо вставить перед
         этими знаками наклонную черту влево. Чтобы представить символ но-
         вой строки, необходимо использовать запись '\n'.

                ----------------------------------------------
                Константа                   Название величины
                ----------------------------------------------
                'a'                          Малая буква а

                '?'                          Знак вопроса

                '\b'                         Знак пробела

                '0x1B'                       ASCII ESC- символ

                '\''                         Одиночная кавычка

                '\\'                         Наклонная черта вле-
                                             во
                -------------------------------------------------

                Табл. 2.7 Примеры констант-символов.

               Константы-символы имеют тип int.

                                      - 18 -

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

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

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

               "Long strings can be bro\
               cken into two pieces."

               идентичен строке:

               "Long strings can be brocken into two pieces."

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

               "This is a string literal"
               "First \\ Second"
               "\"Yes, I do,\" she said."
               "The following line shows a null string:"
               ""
               Заметим, что ESC- символы (такие как \\ и \") могут  появ-
         ляться в строковых литералах. Каждый ESC- символ считается одним
         отдельным символом.
               Символы строки запоминаются в отдельных байтах памяти. Сим-
         вол null (\0) является отметкой  конца  строки.  Каждая  строка в
         программе рассматривается как отдельный об"ект. Если в  программе
         содержатся  две идентичные строки, то каждая  из  них будет  хра-
         ниться в отдельном месте памяти.
               Строчные литералы имеют тип char[]. Под этим подразумевает-
         ся,  что  строка-  это  массив, элементы которого имеют тип char.
         Число элементов в массиве равно числу символов в строчном литера-
         ле плюс один, поскольку символ null (отметка конца  строки)  тоже
         считается элементом массива.

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

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

                                      - 19 -

         ветствующей ему переменной или функции.После этого его можно  ис-
         пользовать в последующих операторах программы. Идентификатор- это
         последовательность  из  одной  или  более  букв, цифр или подчер-
         ков(_), которая начинается с буквы или подчерка. Допускается  лю-
         бое число символов в идентификаторе, однако только первые 31 сим-
         вол распознаются компилятором. (Программы, использующие результат
         работы компилятора, такие как, линкер, могут распознавать меньшее
         число символов).
               При использовании подчерков в идентификаторе нужно быть ос-
         торожным, поскольку идентификаторы, начинающиеся с подчерка могут
         совпадать  (войти в конфликт) с именами "скрытых" системных прог-
         рамм.
               Примеры идентификаторов:
               temp1
               toofpage
               skip12
               Компилятор Си рассматривает буквы верхнего и нижнего регис-
         тров как различные символы. Поэтому можно создать отдельные неза-
         висимые идентификаторы, которые совпадают орфографически, но раз-
         личаются большими и малыми буквами. Например, каждый из следующих
         идентификаторов является уникальным:
               add
               ADD
               Add
               aDD

               Компилятор Си не допускает идентификаторов,  которые  имеют
         ту же самую орфографию, что и ключевые слова. Ключевые слова опи-
         саны в разделе 2.4.

               Замечание:

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

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

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

               Список ключевых слов:

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

                                      - 20 -

               do           if             static      volatile

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

               cdecl
               far
               fortran
               huge
               near
               pascal

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

               Комментарий- это последовательность символов, которая восп-
         ринимается компилятором как отдельный пробельный символ или, дру-
         гими словами, игнорируется.
               Комментарий имеет следующую форму представления:

               /**/,

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

               /* Comments can separate and document
               lines of a program. */
               /* Comments can contain keywords such as for
               and while */
               /*******************************************
                   Comments can occupy several lines.
               *******************************************/

               Так как комментарии не могут содержать вложенных  коммента-
         риев, то следующий пример будет ошибочным:

               /* You cannot/* nest */ comments */

               Компилятор распознает первую комбинацию */ после слова nest
         как  конец комментария. Затем, компилятор попытается обрабатывать
         оставшийся текст и выработает сообщение об ошибке.  Чтобы  обойти

                                      - 21 -

         компиляцию  комментариев больших размеров, нужно использовать ди-
         рективу #if препроцессора.

               2.6. Лексемы

               Когда компилятор обрабатывает программу, он разбивает прог-
         рамму на группы символов, называемых лексемами. Лексема- это еди-
         ница текста программы, которая имеет определенный смысл для  ком-
         пилятора  и которая не может быть разбита в дальнейшем. Операции,
         константы, идентификаторы и ключевые слова, описанные в этом раз-
         деле,являются примерами лексем. Знаки пунктуации, такие как квад-
         ратные скобки ([]), фигурные скобки ({}),  угловые  скобки  (<>),
         круглые  скобки и запятые, также являются лексемами. Границы лек-
         сем определяются пробельными символами и другими лексемами, таки-
         ми как операции и знаки пунктуации. Чтобы предупредить неправиль-
         ную работу компилятора, запрещаются пробельные символы между сим-
         волами идентификаторов, операциями, состоящими из нескольких сим-
         волов и символами ключевых слов.
               Когда  компилятор выделяет отдельную лексему, он последова-
         тельно об"единяет столько символов, сколько возможно, прежде  чем
         перейти  к обработке следующей лексемы. Поэтому  лексемы, не раз-
         деленные пробельными символами,  могут  быть  проинтерпретированы
         неверно.
               Например, рассмотрим следующее выражение:

                i+++j

               В  этом  примере  компилятор вначале создает из трех знаков
         плюс самую длинную из возможных операций (++), а затем обработает
         оставшийся знак +, как операцию сложения (+). Выражение проинтер-
         претируется как (i++)+(j), а не как (i)+(++j).  В  таких  случаях
         необходимо  использовать  пробельные  символы или круглые скобки,
         чтобы однозначно определить ситуацию.


                                     - 22 -

                              3. СТРУКТУРА ПРОГРАММЫ

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

               3.1. Исходная программа

               Исходная программа- это  совокупность  следующих  об"ектов:
         директив,  указаний компилятору, об"явлений и определений. Дирек-
         тивы задают действия препроцессора по преобразованию текста прог-
         раммы перед компиляцией. Указания компилятору- это  команды,  вы-
         полняемые  компилятором  во время процесса компиляции. Об"явления
         задают имена и атрибуты переменных, функций и типов, используемых
         в программе. Определения- это об"явления, определяющие переменные
         и функции.
               Определение переменной в дополнении к ее имени и типу зада-
         ет начальное значение об"явленной переменной. Кроме того, опреде-
         ление предполагает распределение памяти для переменной.
               Определение функции  специфицирует  ее  структуру,  которая
         представляет  собой смесь из об"явлений и операторов, которые об-
         разуют саму функцию. Определение функции также задает  имя  функ-
         ции, ее формальные параметры и тип возвращаемой величины.
               Исходная  программа  может  содержать любое число директив,
         указаний компилятору, об"явлений и определений. Любой из об"ектов
         программы имеет определенный синтаксис, описанный в этом руковод-
         стве,и каждая составляющая может появляться в любом порядке, хотя
         влияние порядка, в котором следуют  переменные  и  функции  может
         быть  использовано в программе (см. раздел 3.5 "Время жизни и ви-
         димость").
               Нетривиальная программа всегда содержит более одного  опре-
         деления  функции.  Функция определяет действия, выполняемые прог-
         раммой.
               В следующем примере иллюстрируется простая  исходная  прог-
         рамма на языке Си.

                int x = 1;                 /* Variable definitions */
                int y = 2;

                extern int printf(char *,...);/* Function declaration */

                main ()                       /* Function definition
                                                 for main function */
                {
                       int z;              /* Variable declarations */
                       int w;

                       z = y + x;          /* Executable statements */

                                      - 23 -

                       w = y - x;
                       printf("z = %d \nw = %d \n", z, x);
                }

               Эта  исходная  программа определяет функцию с именем main и
         об"являет функцию printf. Переменные x и y задаются своими  опре-
         делениями. Переменные z и w только об"являются.

               3.2. Исходные файлы

               Исходные  программы  могут быть разделены на несколько фай-
         лов. Исходный файл Си- это текстовый файл, который содержит часть
         или всю исходную программу. Он может, например, содержать  только
         некоторые  функции,  требуемые программе. При компиляции исходной
         программы  каждый из исходных файлов должен быть  прокомпилирован
         отдельно,  а затем обработан сборщиком. Отдельные исходные  файлы
         перед компиляцией можно соединять в один большой  исходный   файл
         посредством директивы #include.
               Исходный файл может содержать любую комбинацию наборов: ди-
         ректив,  указаний  компилятору,  об"явлений  и определений. Такие
         об"екты, как определения функций или большие структуры данных, не
         могут  разрываться, начинаясь в одном файле  и продолжаясь в дру-
         гом.
               Исходный файл не обязательно должен  содержать  выполняемые
         операторы.  Иногда  полезно размещать описания переменных в одном
         файле с тем, чтобы использовать их  путем  об"явления  ссылок  из
         других файлов. В этом случае определения становятся легко доступ-
         ными  для поиска и модификации. Из тех же самых соображений конс-
         танты  и  макросы часто организуют в отдельных #include- файлах и
         включают их, если требуется, в исходные файлы.
               Директивы исходного файла относятся только к этому исходно-
         му файлу и файлам, включающим его (посредством  #include).  Кроме
         того,  каждая  директива  относится только к части файла, которая
         следует за ней. Если множество директив должно относиться ко всей
         исходной программе, то все исходные файлы  должны  содержать  эти
         директивы.
               Указания компилятору обычно эффективны для отдельных облас-
         тей исходного файла. Специфические действия компилятора, задавае-
         мые указаниями, определяются содержанием последних.
               Подробнее о действиях отдельных указаний компилятору описа-
         но в разделе 8.5 руководства.
               В  нижеследующем примере исходная программа состоит из двух
         исходных файлов. Функции main и max представлены в отдельных фай-
         лах. Функция main использует функцию max при своем выполнении.

                /***********************************************
                      Sourse file 1 - main function
                ***********************************************/

                #define ONE      1
                #define TWO      2
                #define THREE    3

                                      - 24 -

                extern int max(int, int);   /* Function declaration */

                main ()                     /* Function definition  */
                {
                        int w = ONE, x = TWO, y = THREE;
                        int z = 0;
                        z = max(x,y);
                        w = max(z,w);
                }

                /*************************************************
                       Sourse file 2 - max function
                *************************************************/

                int max(a,b)               /* Function definition */
                int a, b;
                {
                        if ( a > b )
                                return (a);
                        else
                                return (b);
                }

               В первом исходном файле функция max об"явлена без ее  опре-
         деления. Это об"явление известно как "forward"- об"явление. Опре-
         деление функции main включает вызов функции max.
               Строки, начинающиеся с символа "номер" (#), являются дирек-
         тивами  препроцессора. Директивы инструктируют препроцессор о за-
         мене в первом исходном файле имен ONE, TWO, THREE на соответству-
         ющие им значения. Область действия деректив  не  распространяется
         на второй исходный файл.
               Второй исходный файл содержит описание функции max. Это оп-
         ределение соответствует вызовам max из первого исходного файла.

               3.3 Выполнение программ

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

                                      - 25 -

               Соглашение Си требует, чтобы первые два  параметра  функции
         main назывались argc и argv.
               Параметр argc определяет общее число аргументов, передавае-
         мых  функции  main. Параметр argv об"является как массив указате-
         лей, каждый элемент которого ссылается на строковое представление
         аргумента, передаваемого функции main.  Третий  параметр  функции
         main (если он есть) традиционно задается с именем envp. Однако Си
         не  требует  этого  имени. Параметр envp- это указатель на массив
         указателей строковых величин, которые определяют окружение, в ко-
         тором выполняется программа.
               Операционная система  поддерживает  передачу  значений  для
         argc,  argv, и envp параметров, а пользователь поддерживает зада-
         ние значений фактических параметров для функции main.  Соглашение
         о передаче параметров в большей степени определяется операционной
         системой, чем самим языком Си.
               Формальные параметры функции должны быть об"явлены во время
         описания функции.

               3.4. Время жизни и видимость

               Концепции "Время жизни" и "Видимость" являются очень важны-
         ми для понимания структуры программ на Си. Время жизни переменной
         может  быть или "глобальным" или "локальным". Об"ект с глобальным
         временем жизни характеризуется определенной памятью  и  значением
         на  протяжении  всей жизни программы. Об"ект с локальным временем
         жизни захватывает новую память при каждом входе в "блок", в кото-
         ром он определен или об"явлен. Когда блок завершается,  локальный
         об"ект пропадает, а следовательно пропадает его значение. Опреде-
         ление блоков описывается ниже.
               Об"ект считается "видимым" в блоке или исходном файле, если
         известны тип и имя об"екта в блоке или исходном файле. Об"ект мо-
         жет быть  "глобально видимым", когда имеется в виду, что он видим
         или может быть об"явлен видимым  на  протяжении  всего  исходного
         файла,  образующего  программу. Видимость между исходными файлами
         рассматривается в разделе 3.5 "Классы памяти".
               Блок- это составной оператор. Составные  операторы  состаят
         из  об"явлений  и операторов, как это описано в разделе 6.2 "Сос-
         тавной оператор". Блоки могут быть вложенными.
               Структура функции представляет собой совокупность составных
         операторов. Таким образом, функции имеют блочную структуру, блоки,
         в свою очередь, могут содержать внутри себя другие блоки.
               Об"явления и определения внутри блоков находятся на  "внут-
         реннем  уровне". Об"явления и определения вне блоков находятся на
         "внешнем уровне". Переменные и функции могут быть  об"явлены  как
         на  внешнем  уровне,  так и на внутреннем. Переменные также могут
         быть определены на внутреннем и на внешнем уровне, а функции  оп-
         ределяются только на внешнем уровне.
               Все  функции имеют глобальное время жизни, невзирая  на то,
         где они об"явлены. Переменные,  об"явленные  на  внешнем  уровне,
         всегда  имеют  глобальное время жизни. Переменные, об"явленные на
         внутреннем уровне, всегда имеют  локальное  время  жизни, однако,
         классы  памяти, специфицированные как static и extern, могут быть
         использованы для об"явления глобальных переменных или  ссылок  на
         них внутри блока.

                                      - 26 -

               Переменные, об"явленные или определенные на внешнем уровне,
         видимы из точки, в которой они об"являлись или  определялись,  до
         конца  файла.  Эти переменные можно сделать видимыми в других ис-
         ходных файлах путем их об"явления,  как  описано  в  разделе  3.5
         "Классы памяти".
               Однако,переменные,  заданные  классом  памяти  "static"  на
         внешнем уровне, видимы только внутри исходного файла,  в  котором
         они определены.
               В общем случае, переменные, об"явленные или определенные на
         внутреннем  уровне,  видимы от точки, в которой они об"явлены или
         определены, до конца блока, в котором представлены об"явления или
         определения. Эти переменные называются локальными. Если  перемен-
         ная, об"явленная внутри блока, имеет то же самое имя, как и пере-
         менная,  об'явленная на внешнем уровне, то определение переменной
         в блоке заменяет (имеет предпочтение) определение внешнего уровня
         на протяжении блока. Видимость переменной внешнего уровня восста-
         навливается при завершении блока.
               Блочная видимость может вкладываться. Это значит, что блок,
         вложенный внутрь другого блока, может содержать об"явления, кото-
         рые переопределяют переменные, об"явленные во внешнем блоке.  Пе-
         реопределение  переменной имеет силу во внутреннем блоке, но пер-
         воначальное определение восстанавливается, когда управление возв-
         ращается внешнему блоку.  Переменные  из  внешних  блоков  видимы
         внутри всех внутренних блоков до тех пор, пока они не переопреде-
         лены во внутреннем блоке.
               Функции  класса памяти static видимы только в исходном фай-
         ле, в котором они определены. Все другие  функции  являются  гло-
         бально видимыми.
               В табл. 3.1 подытожены основные факторы, которые определяют
         время  жизни и видимость функций и переменных. В таблице, однако,
         не рассматриваются абсолютно все случаи. Более подробная информа-
         ция дана в разделе 4.5 "Классы памяти".

           ------------------------------------------------------------
           Уровень    Об"ект   Спецификатор   Время       Видимость
                               класса памяти  жизни
           ------------------------------------------------------------

           Внешний  Переменная    static     Глобальное  Ограничивается
                    об"явление                           отдельным ис-
                                                         ходным файлом.

                    Переменная    extern     Глобальное  Остаток исход-
                    об"явление                           ного файла.

                    Функция об"-  static     Глобальное  Ограничивается
                    явление или                          отдельным ис-
                    определение                          ходным файлом.

                    Функция об"-  extern     Глобальное  Остаток исход-
                    явление или                          ного файла.
                    определение

          Внутрен-  Переменная    extern     Глобальное  Блок

                                      - 27 -

          ний       об"явление    или
                    или опреде-   static
                    ление

                    Переменная     auto      Локальное   Блок
                    об"явление     или
                    или опреде-    register
                    ление
          -------------------------------------------------------------

                Табл. 3.1. Время жизни и видимость

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

                /* i defined at external level */
                int i = 1;

                /* main function defined at external level */
                main ()
                {

                   /* prints 1 (value of external level i) */
                   printf("%d\n", i);

                   /* first nested block */
                   {

                        /* i and j defined at internal level */
                        int i = 2, j = 3;

                        /* prints 2, 3 */
                        printf("%d\n%d\n", i, j);

                        /* second nested block */
                        {

                             /* i is redefined */
                             int i = 0;

                             /* prints 0, 3 */
                             printf("%d\n%d\n", i, j);

                        /* end of second nested block */
                        }

                        /* prints 2 (outer definition restored) */
                        printf("%d\n", i);

                   /* end of first nested block */
                   }

                   /* prints 1 (external level definition restored */
                   printf("%d\n", i);
                }

                                      - 28 -

               В  этом  примере показано 4 уровня видимости: самый внешний
         уровень и три уровня, образованных блоками.  Предполагается,  что
         функция  printf  определена где- нибудь в другом месте программы.
         Функция main печатает значения 1, 2, 3, 0, 3, 2, 1.

               3.5. Классы имен

               В любой программе на Си имена используются  для  ссылок  на
         различного  рода об"екты. Когда пишется программа на Си, то в ней
         используются идентификаторы для именования  функций,  переменных,
         формальных параметров, областей памяти и других об"ектов програм-
         мы. По определенным правилам в Си допускаются использование одно-
         го  и  того  же  идентификатора  для более чем одного прогаммного
         об"екта.
               Чтобы различать идентификаторы для различного  рода  об"ек-
         тов,  компилятор  устанавливает так называемые "Классы имен". Для
         избежания противоречий, имена внутри одного  класса  должны  быть
         уникальными, однако в различных классах могут появляться идентич-
         ные  имена.  Это  означает,  что можно использовать один и тот же
         идентификатор для двух или более различных об"ектов, если об"екты
         принадлежат к различным классам имен. Однозначное разрешение ссы-
         лок компилятор осуществляет по контексту данного идентификатора в
         программе. Ниже описываются виды об"ектов, которые  можно  имено-
         вать в программе на Си, а также правила их именования.

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

          Формальные параметры      Имена формальных параметров функции
                                    появляются  вместе  с  именами пере-
                                    менных функции, поэтому  имена  фор-
                                    мальных  параметров  должны быть от-
                                    личны от имен  переменных.  Переоб"-
                                    явление формальных параметров внутри
                                    функции приводит к ошибке.

          Перечислимые константы    Перечислимые константы относятся к
                                    тому же классу  имен,  что  и  имена

                                      - 29 -

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

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

          Tags                      Теги  перечислений,     структур   и
                                    совмещений  сгруппированы  вместе  в
                                    одном  классе   имен.   Каждый   тег
                                    перечисления,      структуры     или
                                    соединения должен  быть  отличен  от
                                    других   тегов   с   той   же  самой
                                    видимостью. Теги не конфликтуют с лю-
                                    быми другими именами.

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

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

                Пример:

                struct student {
                        char student[20];

                                      - 30 -

                        int class;
                        int id;
                        } student;

               В этом примере имя тега структуры, элемента структуры и пе-
         ременной  относятся к трем различным классам имен, поэтому не бу-
         дет противоречия между тремя об"ектами с именем student. Компиля-
         тор определит по контексту программы как интерпретировать  каждый
         из  случаев. Например, когда имя student появится после ключевого
         слова struct, это будет означать,  что  появился  tag  структуры.
         Когда имя student появится после операции выбора элемента (-> или
         .),  то  это  означает, что имя ссылается на элемент структуры. В
         другом контексте имя student является переменной структурного ти-
         па.

                                   4. ОБ"ЯВЛЕНИЯ

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

         [][][=]
                                      [,[=...],

                где:
                - спецификатор класса памяти;
                - имя определяемого типа;
                - идентификатор, который может быть модифициро-
         ван при об"явлении указателя, массива или функции;
               -  задает значение или последовательность зна-
         чений, присваиваемых переменной при об"явлении.
               Все переменные Си должны быть явно об"явлены перед  их  ис-
         пользованием.  Функции  Си могут быть об"явлены явно или неявно в
         случае их вызова перед определением.
               Язык Си определяет стандартное множество  типов  данных.  К
         этому  множеству можно добавлять новые типы данных посредством их
         об"явлений на типах данных уже определенных.
               Об"явление Си требует одного или более деклараторов. Декла-
         ратор- это идентификатор, который может быть определен с квадрат-
         ными скобками ([]), эвездочкой (*) или круглыми скобками  ()  для
         об"явления  массива,  указателя  или  функции.  Когда об'является
         простая переменная  (такая  как  символ,  целое  или  плавающее),
         структура  или  совмещение простых переменных, то декларатор- это
         идентификатор.
               В Си определено четыре спецификатора класса памяти, а имен-
         но: auto, extern, register и static.
               Спецификатор класса памяти определяет, каким образом об"яв-
         ляемый об"ект запоминается и инициализируется и из  каких  частей
         программы можно ссылаться на него. Расположение об"явления внутри
         программы, а также наличие или отсутствие других об"явлений- так-
         же важные факторы при определении видимости переменных.
               Об"явления функций описаны в разделе 4.4.

                                      - 31 -

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

               Язык  Си поддерживает определения для множества базовых ти-
         пов данных, называемых "основными" типами.  Названия  этих  типов
         перечислены в Табл. 4.1.

           ------------------------------------------------------------
              Типы целых         Типы плавающих       Другие типы
           ------------------------------------------------------------
             signed char         float                void
             signed int          double
             signed short int
             signed long int
             unsigned char
             unsigned int
             unsignet short int
             unsigned long int
           -----------------------------------------------------------
                            Табл. 4.1. Основные типы.

               Перечислимые  типы также рассматриваются как основные типы.
         Спецификаторы перечислимых типов рассмотрены в разделе 4.7.1. Ти-
         пы signed char, signed int, signed short int и  signed  long  int
         вместе  с  соответствующими двойниками unsigned называются типами
         целых.
               Спецификаторы типов float и double относятся к типу "плава-
         ющих". В об"явлениях переменых и функций можно использовать любые
         спецификаторы "целый" и "плавающий".
               Тип void может быть использован только для об"явления функ-
         ций, которые не возвращают значения. Типы функций  рассмотрены  в
         разделе 4.4.
               Можно задать дополнительные спецификаторы типа путем об"яв-
         ления typedef, описанного в разделе 4.7.2.
               При  записи  спецификаторов  типов допустимы сокращения как
         показано в  табл. 4.2.  В целых типах ключевое слово signed может
         быть опущено. Так, если ключевое слово unsigned опускается в  за-
         писи  спецификатора типа, то тип целого будет знаковым, даже если
         опущено ключевое слово signed.
               В некоторых реализациях могут быть использованы опции  ком-
         пилятора,  позволяющие изменить умолчание для типа char со знако-
         вого на беззнаковый. Когда задана такая  опция,  сокращение  char
         имеет  то же самое значение, что и unsigned char, и следовательно
         ключевое слово sidned должно быть записано  при  об"явлении  сим-
         вольной величины со знаком.

           -----------------------------------------------------------
                Спецификатор типа                 Сокращение
           -----------------------------------------------------------
                signed char                       char
                signed int                        signed, int
                signed short int                  short, signed short
                signed long int                   long, signed long
                unsigned char                     -

                                      - 32 -

                unsigned int                      unsigned
                unsigned short int                unsignet short
                unsignet long int                 unsignet long
                float                             -
                long float                        double
          ------------------------------------------------------------

                  Табл. 4.2. Спецификаторы и сокращения

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

           -----------------------------------------------------------
           Тип            Представление      Область значений
                          в памяти           величины
           -----------------------------------------------------------

           char            1 байт             -128 до 127

           int             зависит от
                           реализации
           short           2 байта            -32768 до 32767

           long            4 байта       -2.147.483.648 до 2.147.483.647

           unsigned char   1 байт              0 до 255

           unsigned        зависит от
                           реализации
           unsigned short  2 байта             0 до 65535

           unsigned long   4 байта             0 до 4.294.967.295

           float           4 байта             IEEE   стандартное
                                               соглашение
           double          8 байт              IEEE   стандартное
                                               соглашение
           ------------------------------------------------------------

            Табл 4.3 Размер памяти и область значений типов

               Тип char используется для запоминания буквы, цифры или сим-
         вола из множества представимых символов. Значением  об"екта  типа
         char является ASCII код, соответствующий данному символу. Так как
         тип  char  интерпретируется как однобайтовая целая величина с об-
         ластью значений от -128 до 127, то только величины от  0  до  127

                                      - 33 -

         имеют символьные эквиваленты. Аналогично, тип unsigned char может
         запоминать величины с областью значений от 0 до 255.
               Заметим,  что представление в памяти и область значений для
         типов int и unsigned int не определены в языке Си.  По  умолчанию
         размер int (со знаком и без знака) соответствует реальному разме-
         ру  целого  на данной машине. Например, на 16-ти разрядной машине
         тип int всегда 16 разрядов или 2 байта. На 32-ух разрядной машине
         тип int всегда 32 разряда или 4 байта. Таким образом, тип int эк-
         вивалентен типам short int или long int в зависимости от реализа-
         ции.
               Аналогично, тип unsigned int  эквивалентен  типам  unsigned
         short  или  unsigned long. Спецификаторы типов int и unsigned int
         широко используются в программах на Си, поскольку  они  позволяют
         наиболее  эффективно  манипулировать  целыми величинами на данной
         машине.
               Однако, размер типов int и unsigned int переменный, поэтому
         программы, зависящие от специфики размера int и unsigned int  мо-
         гут  быть  непереносимы.  Переносимость кода можно улучшить путем
         включения выражений с sizeof операцией.

               4.2 Область значений величин

               Область значений величин- это интервал целых величин от ми-
         нимального до максимального, который может быть представлен в за-
         данном числе бит. Тем не менее не всегда возможно (см. раздел  4.
         "Выражения  и  присваивания") использовать максимальное или мини-
         мальное значение для константы данного типа в выражении.
               Например, константное выражение -32768 состоит из арифмети-
         ческого отрицания (-), предшествующего величине константы  32768.
         Так  как  32768-  это слишком большое значение для типа short, то
         оно задается типом long  и  следовательно  константное  выражение
         -32768 будет иметь тип long. Величина -32768 может быть представ-
         лена  как short только путем преобразования ее типа к типу short.
         Информация не будет потеряна при преобразовании  типа,  поскольку
         -32768  может  быть представлено двумя байтами памяти. Аналогично
         такая величина, как 65000 может быть  представлена  как  unsigned
         short  только  путем  преобразования ее к типу unsigned short или
         заданием величины в восьмиричном или шестнадцатиричном  представ-
         лении.  Величина 65000 в десятичном представлении рассматривается
         как знаковая и задается типом long, так как 65000 не соответству-
         ет типу short. Эта величина типа long может быть преобразована  к
         типу  unsigned  short  без потери информации, так как 65000 можно
         разместить в двух байтах памяти.
               Восьмеричные и шестнадцатиричные константы могут быть  зна-
         кового  и  беззнакового типа в зависимости от их размера. Однако,
         метод, используемый для назначения типов этим константам гаранти-
         рует, что они всегда будут преобразованы к  беззнаковому  целому.
         Числа  с  плавающей  точкой  используют  IEEE (институт инженеров
         электриков и электронщиков) формат. Величины типа float  занимают
         4 байта, состоящих из бита знака и 7-ми битовой избыточной экспо-
         ненты и 24- ех битовой мантиссы. Мантисса представляет число меж-
         ду  1.0 и 2.0. Так как старший бит мантиссы всегда 1, он не запо-
         минается в памяти. Это представление дает область значений  приб-
         лизительно от 3.4E-38 до 3.4E38.

                                      - 34 -

               Величины  типа double занимают 8 байт. Их формат аналогичен
         float формату, за исключением того, что порядок занимает 11  бит,
         а  мантисса  52  бита  плюс бит, который опущен. Это дает область
         значений приблизительно от 1.7E-308 до 1.7E+308.

               4.3. Деклараторы

               Синтаксис:

               []
               [constant-expression>]
               *
               ()
               ()
               ()
               Си позволяет об"являть: массивы величин, указатели на вели-
         чины, величины возвратов функций.  Чтобы  об"явить  эти  об"екты,
         нужно использовать декларатор, возможно модифицированный квадрат-
         ными  скобками  ([]),  круглыми скобками () и звездочкой (*), что
         соответствует типам массива, функции или  указателя.  Деклараторы
         появляются в об"явлениях указателей, массивов и функций.

               4.3.1. Деклараторы массивов, функций и указателей

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

               1. int list[20]

               2. char *cp

               3. double func(void),

               где:

               1. Массив list целых величин

               2. Указатель cp на величину типа char

               3.  Функция  func  без  аргументов,  возвращающая  величину
         double

                                      - 35 -

               4.3.2.  Составные деклараторы

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

               char *(*(*var) ()) [10];
               ^   ^ ^ ^ ^   ^   ^
               7   6 4 2 1   3   5

               1. Идентификатор var об'явлен как

               2. Указатель на

               3. Функцию, возвращающую

               4. Указатель на

               5. Массив из 10 элементов, который состоит

               6. Из указателей на

               7. Величины типа char.

               В следующих примерах  показывается  каким  образом  круглые
         скобки могут поменять смысл об"явлений.
               1. int *var[5]; - массив указателей на величины типа int.

                                      - 36 -

               2. int (*var)[5]; - указатель на массив величин типа int.

               3.  long *var(long,long); - функция, возвращающая указатель
         на величину типа long.

               4. long (*var) (long,long); - указатель на функцию, возвра-
         щающую величину типа long.

               5. struct both {
                           int a;
                           char b;
                           } ( *var[5] ) ( struct both, struct both);
                массив указателей на функции, возвращающих структуры.

               6. double ( *var( double (*) [3] ) ) [3];
               функция, возвращающая указатель на массив из  трех  величин
         типа double.

               7. union sign {
                           int x;
                           unsigned y;
                           } **var[5] [5];
               массив массивов указателей на указатели совмещений.

               8. union sign *(*var[5]) [5];
               массив указателей на массив указателей на совмещения.

               Описание примеров:
               В  первом примере, модификатор массива имеет высший приори-
         тет, чем модификатор указателя, так что var об"является массивом.
         Модификатор указателя определяет тип элементов массива; элемента-
         ми являются указатели на величины типа int.
               Во втором примере скобки меняют значение об"явления первого
         примера. Теперь модификатор указателя имеет более высокий приори-
         тет, чем модификатор массива, и переменная  var  об"является  как
         указатель на массив из пяти величин типа int.

               В  третьем  примере модификатор функции имеет более высокий
         приоритет, чем модификатор  указателя,  так  что  переменная  var
         об"является  функцией,  возвращающей  указатель  на величину типа
         long. Функция об"явлена с двумя аргументами типа long.
               Четвертый пример похож на второй. Скобки задают более высо-
         кий приоритет модификатору указателя, и  поэтому  переменная  var
         об"является  как указатель на функцию, возвращающую величину типа
         long. По прежнему функция  об"явлена  с  двумя  аргументами  типа
         long.
               Элементы  массива  не  могут быть функциями. Взамен этому в
         пятом примере показано, как об"явить массив указателей  на  функ-
         ции.  В  этом примере переменная var об"явлена как массив из пяти
         указателей на функции, возвращающие структуры с двумя элементами.
         Оба аргумента функции об"явлены как структуры типа both. Заметим,
         что круглые скобки, в которые заключено выражение *var[5], обяза-
         тельны. Без них об"явление будет неверным, поскольку будет об"яв-
         лен массив функций:

                                      - 37 -

                                  /* ILLEGAL */
                struct both *var[5] ( struct both, struct both );

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

               4.3.3. Деклараторы со специальными ключевыми словами

               В MSC можно  использовать  следующие  специальные  ключевые
         слова:

               cdecl
               far
               fortran
               huge
               near
               pascal

               Эти ключевые слова используются для изменения смысла об"яв-
         лений  переменной и функции. Более полная информация о применении
         специальных ключей дана в системной документации по пакету MSC.
               Когда специальное ключевое слово встречается в деклараторе,
         то оно модифицирует об"ект,  расположенный  справа  от  ключевого
         слова.  Допускается более одного ключевого слова, модифицирующего
         об"ект. Например, идентификатор функции может быть  модифицирован
         двумя  ключевыми  словами  far  и pascal. Порядок ключевых слов в
         этом случае не важен, то есть far pascal и pascal far имеют  один
         и  тот же смысл. В различных частях об"явления могут быть исполь-
         зованы два или более ключевых слов для модификации смысла состав-
         ляющих частей об"явления.
               Например, в следующем  об"явлении  содержится  в  различных
         местах два ключевых слова far и ключевое слово pascal:

               int far * pascal far func(void);

               Идентификатор функции func модифицируется ключевыми словами
         pascal и far. func возвращает величину, об"явленную как far- ука-

                                      - 38 -

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

               \*************** Example 1 ********************\

               int huge database[65000];

               \*************** Example 2 *********************\

               char * far * x;

               \*************** Example 3 *********************\

               double near cdecl calc(double,double);
               double cdecl near calc(double,double);

               \*************** Example 4 *********************\

               char far fortran initlist[INITSIZE];
               char far *nextchar, far *prevchar, far *currentchar;
               \*************** Example 5 **********************\

               char far *(far *getint) (int far *);
                ^   ^     ^      ^     ^     ^
                6   5     2      1     3     4

               В  первом  примере  об"является  массив huge, поименованный
         database, содержащий 65000 элементов типа int. Декларатор массива
         модифицируется ключевым словом huge.
               Во втором примере ключевое слово far модифицирует  располо-
         женную  справа  звездочку, делая x far- указателем на указатель к
         величине типа char. Это об"явление эквивалентно и такой записи:

               char * (far *x);

               В примере 3  показано  два  эквивалентных  об"явления.  Оба
         об"являют calc как функции с near и cdecl атрибутами.
               В  примере  4  также  представлено  два  об"явления: первое
         об"являет far fortan- массив символов, поименованный initlist,  а
         второе  об"являет  три  far-  указателя,  поименованные  nexchar,
         prevchar и currentchar. Указатели могут быть использованы для за-
         поминания адресов символов массива initlist. Заметим, что  ключе-
         вое слово far должно быть повторено перед каждым декларатором.
               В  примере 5 показано более сложное об"явление с различными
         случаями расположения ключевого слова  far.  Интерпретация  этого
         об"явления состоит из следующих шагов:

               1. Идентификатор getint об"является как

               2. far- указатель на

                                      - 39 -

               3. Функцию, требующую

               4. Один аргумент, который является far- указателем на вели-
         чину типа int,

               5. И возвращающую far- указатель на

               6. Величину типа char

               Заметим, что ключевое слово far всегда модифицирует об"ект,
         который следует справа.

               4.4. Об"явления переменной

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

               Тип переменной         Описание

               Простая переменная     Переменная целого или плаваю-
                                      щего типа.

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

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

               Массив                 Переменная, представляющая на-
                                      бор элементов одного типа.

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

               Общий синтаксис об"явлений переменных следующий:

        []   [,...],

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


                                      - 40 -

         жет быть задана более чем одна переменная путем задания  множест-
         венного  об"явления,  в  котором  деклараторы разделены запятыми.
          задает класс памяти переменной. В некоторых  слу-
         чаях  переменные  могут быть инициализированы при их определении.
         Классы памяти и инициализация описаны в разделах 4.6 и 4.7  соот-
         ветственно.

               4.4.1. Об"явление простой переменной

               Синтаксис:

               [,...];

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

               Примеры

               int x;                            /* Example 1 */

               unsigned long reply, flag         /* Example 2 */

               double order;                     /* Example 3 */

               В  первом примере об"является простая переменная x. Эта пе-
         ременная может принимать любое значение  из  множества  значений,
         определяемых для типа int.
               Во  втором  примере об"явлены две переменные: reply и flag.
         Обе переменные имеют тип unsigned long.
               В третьем примере об"явлена переменная order, которая имеет
         тип double. Этой переменной могут быть присвоены величины с  пла-
         вающей запятой.

               4.4.2. Об"явление перечисления

               Синтаксис:

           enum[]{}[,...];

           enum[,...];

               Об"явление  перечисления задает имя переменной перечисления
         и определяет список именованных констант, называемый списком  пе-
         речисления.  Значением каждого имени списка является целое число.
         Переменная перечисления принимает значение одной  из  именованных
         констант  списка. Именованные константы списка имеют тип int. Та-

                                      - 41 -

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

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

               [=][,
                                       [= переопределяет последователь-
         ность значений, заданных по умолчанию.  Идентификатор,  следующий
         перед записью = принимает значение, задавае-
         мое  этим константным выражением. Константное выражение имеет тип
         int и может быть отрицательным. Следующий идентификатор в  списке
         ассоциируется  с  величиной, равной +1, если
         он явно не задается другой величиной.
               Перечисление может содержать повторяющиеся значения иденти-
         фикаторов, но каждый идентификатор должен быть уникальным.  Кроме
         того,  он должен быть отличным от всех других идентификаторов пе-
         речислений с той же видимостью. Например, двум различным  иденти-
         фикаторам  null и zero может быть задано значение 0 в одном и том
         же перечислении. Идентификаторы должны  быть  отличны  от  других
         идентификаторов  с той же самой видимостью, включая имена обычных
         переменных и идентификаторы других перечислений. Теги  перечисле-
         ний  должны  быть отличны от тегов перечислений, тегов структур и
         совмещений с той же самой видимостью.

               Примеры:

               /**************** Example 1 ***************/

               enum day {
                        saturday,
                        sunday = 0,
                        monday,

                                      - 42 -

                        tuesday,
                        wednesday,
                        thursday,
                        friday
                        } workday;

                /***************** Example 2 ***************/

                enum day today = wednesday;

               В первом примере определяется тип перечисления, поименован-
         ный day и об"является переменная workday этого типа перечисления.
         С saturday по умолчанию ассоциируется значение  0.  Идентификатор
         sunday  явно  устанавливается  в  0. Оставшиеся идентификаторы по
         умолчанию принимают значение от 1 до 5.
               Во втором примере переменной today типа enum day присваива-
         ется значение из перечисления. Заметим, что для присваивания  ис-
         пользуется  имя константы из перечисления. Так как тип перечисле-
         ния day был  предварительно  об"явлен,  то  достаточно  сослаться
         только на тег перечисления.

               4.4.3. Об"явления структур

               Синтаксис:

     struct[]{}[,...];

     struct[,...];

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

                                      - 43 -

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

               Битовые поля

               Об"явления битовых полей имеют следующий синтаксис:

                []:;

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

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

               Примеры

                /**************** Example 1 ****************/

                struct {
                           float x,y;
                    } complex;

                /**************** Example 2 *****************/

                                      - 44 -

                struct employee {
                            char name[20];
                            int id;
                            long class;
                    } temp;

                /**************** Example 3 ******************/

                struct employee student, faculty, staff;

                /**************** Example 4 ******************/

                struct sample {
                            char c;
                            float *pf;
                            struct sample *next;
                    } x;

                /***************** Example 5 ******************/

                struct {
                            unsigned icon : 8;
                            unsigned color : 4;
                            unsigned underline : 1;
                            unsigned blink : 1;
                    } screen[25][80];

               В первом примере об"является переменная  с  именем  complex
         типа структура. Эта структура состоит из двух элементов x и y ти-
         па float. Тип структуры не поименован.
               Во втором примере об"является переменная с именем temp типа
         структура. Структура состоит из трех элементов с именами name, id
         и  class.  Элемент  с именем name- это массив иэ 20- ти элементов
         типа char. элементы с именами id и class- это простые  переменные
         типа  int  и long соответственно. Идентификатор employee является
         тегом структуры.
               В третьем примере об"явлены три переменных типа структура с
         именами: student, faculty и staff. Каждая из структур состоит  из
         трех  элементов  одной  и той же конструкции. Элементы определены
         при об"явлении типа структуры с тегом employee в предыдущем  при-
         мере.
               В  четвертом примере об"является переменная с именем x типа
         структура. Первые два элемента структуры представлены  переменной
         c  типа  char и указателем pf на величину типа float. Третий эле-
         мент с именем  next  об"являются  как  указатель  на  описываемую
         структуру sample.
               В  пятом примере об"является двумерный массив поименованный
         screen, элементы которого имеют структурный тип.  Массив  состоит
         из 2000 элементов и каждый элементэто отдельная структура, состо-
         ящая  из  четырех  элементов типа bit-fild с именами icon, color,
         underline и blink.

                                      - 45 -

               4.4.4. Об"явление совмещений

               Синтаксис:

     union[]{}[,...];

     union[,...];

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

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

               Примеры:

                /************** Example 1 ********************/

                union sign {
                        int svar;
                        unsigned uvar;
                   } number;

                /************** Example 2 ********************/

                union {
                        char *a, b;
                        float f[20];
                    } jack;

                /*************** Example 2 *******************/

                union {
                        struct {
                                 char icon;
                                 unsigned color : 4;
                        } window1, window2, window3, window4;
                   } screen[25][80];

               В  первом  примере  об"является переменная типа совмещения,
         поименованная number. Список элементов совмещения состоит из двух

                                      - 46 -

         об"явлений переменных: svar типа int и uvar  типа  unsigned.  Это
         об"явление  позволяет запоминать текущее значение number в знако-
         вом или беззнаковом виде. Тип совмещения поименован идентификато-
         ром sign.
               Во втором примере об"является переменная типа совмещения  с
         именем jack. Список элементов об"явления состоит из трех об"явле-
         ний:  указателя a на величину типа char, переменной b типа char и
         массива f из 20 элементов типа float. Тип совмещения не  поимено-
         ван.
               Память,  распределенная  для переменной jack, равна памяти,
         распределенной под массив f, поскольку f  самый  большой  элемент
         совмещения.
               В третьем примере об"является двумерный массив совмещений с
         именем  screen.  Массив  состоит из 2000 об"ектов. Каждый об"ект-
         это отдельное совмещение из четырех элементов: window1,  window2,
         window3,  window4, где каждый элемент- это структура. В любое за-
         данное время каждый об"ект совмещения поддерживается одним из че-
         тырех возможных элементов типа структура. Таким образом, перемен-
         ная screen- это композиция четырех возможных "windows".

               4.4.5. Об"явление массива

               Синтаксис:

                [];

                [];

               Здесь квадратные скобки- это терминальные символы. Об"явле-
         ние массива определяет тип массива и тип  каждого  элемента.  Оно
         может определять также число элементов в массиве. Переменная типа
         массив  рассматривается как указатель на элементы массива. Об"яв-
         ление массива может представляться в двух синтаксических  формах,
         указанных  выше.  Декларатор  задает  имя переменной.
         Квадратные скобки, следующие за декларатором, модифицируют декла-
         ратор     на     тип     массива.      Константное      выражение
         , заключенное в квадратные скобки, определя-
         ет  число элементов в массиве. Каждый элемент имеет тип, задавае-
         мый спецификатором типа , который может  специфи-
         цировать любой тип, исключая void и тип функции.
               Во  второй синтаксической форме опущено константное выраже-
         ние в квадратных скобках. Эта форма может быть использована толь-
         ко тогда, когда массив инициализируется или об"явлен как формаль-
         ный параметр или об"явлен как ссылка на массив, явно определенный
         где-то в программе.
               Массив массивов или многомерный массив  определяется  путем
         задания списка константных выражений в квадратных скобках, следу-
         щего за декларатором:

               []
                                           []...

               Каждое константное выражение-  в квад-
         ратных скобках определяет число  элементов  в  даннном  иэмерении

                                      - 47 -

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

                                  char A[2][3]

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

               Примеры:

                /*************** Example 1 ******************/

                int scores[10], game;

                /*************** Example 2 ******************/

                float matrix[10][15];

                /*************** Example 3 ******************/

                struct {
                            float x,y;
                            } complex[100];

                /*************** Example 4 *******************/

                char *name[20];

               В первом примере об"является переменная типа массив с  име-
         нем  scores  из  10  элементов типа int. Переменная с именем game
         об"явлена как простая переменная целого типа.
               Во втором примере об"является  двумерный  массив  с  именем
         matrix. Массив состоит из 150-ти элементов типа float.
               В  третьем примере об"является массив структур. Массив сос-
         тоит из 100 об"ектов. Каждый об"ект  массива  представляет  собой
         структуру, состоящую из двух элементов.
               В четвертом примере об"явлен массив указателей. Массив сос-
         тоит из 20-ти элементов, каждый из которых является указателем на
         величину типа char.

                                      - 48 -

               4.4.6. Об"явление указателей

               Синтаксис:

                 *;

               Об"явление  указателя определяет имя переменной типа указа-
         тель и тип об"екта, на который указывает эта переменная. Деклара-
         тор-  определяет имя переменной с возможной модифика-
         цией ее типа. Спецификатор типа-    задает  тип
         об"екта,  который  может  быть  базового типа, типа структуры или
         совмещения.
               Переменная типа указатель может указывать также на функции,
         массивы и другие указатели. Более полная информация о типах  ука-
         зателей дана в разделе 4.3.2. "Составные деклараторы".
               Если указатель не используется до определения типа структу-
         ры  или совмещения, то он может быть об"явлен ранее этого опреде-
         ления. Такие об"явления  допускаются,  поскольку  компилятору  не
         требуется знать размера структуры или совмещения, чтобы распреде-
         лить  память  под переменную типа указатель. Указатель может быть
         об"явлен посредством использования тега структуры или  совмещения
         (смотри ниже пример 4).
               Переменная, об"явленная как указатель, хранит адрес памяти.
         Размер  памяти,  требуемый  для адреса, и смысл адреса зависит от
         данной конфигурации машины. Указатели на различные типы не обяза-
         тельно имеют одну и ту же длину.
               Для некоторых реализаций используются специальные  ключевые
         слова  near,  far  и huge, чтобы модифицировать размер указателя.
         Об"явления, использующие специальные ключевые слова, были описаны
         в разделе 4.3.3. Информация о смысле ключевых слов дана в систем-
         ной документации.

               Примеры:

               char *message;                      /* Example 1 */

               int *pointers[10];                  /* Example 2 */

               int (*pointer)[10];                 /* Example 3 */

               struct list *next, *previous;       /* Example 4 */

               struct list {                       /* Example 5 */
                            char *token;
                            int count;
                            struct list *next;
                       } line;

               struct id {                         /* Example 6 */
                        unsigned int id_no;
                        struct name *pname;
                    } record;

                                      - 49 -

               В первом примере об"является переменная- указатель поимено-
         ванная message. Она указывает на величину типа char.
               Во втором примере об"явлен массив указателей, поименованный
         pointers. Массив состоит из 10  элементов.  Каждый  элемент-  это
         указатель на переменную типа int.
               В третьем примере об"явлена переменная- указатель, поимено-
         ванная  pointer.  Она указывает на массив из 10 элементов. Каждый
         элемент в этом массиве имеет тип int.
               В четвертом примере об"явлены две переменныхуказателя,  ко-
         торые ссылаются на величины структурного типа list (смотри следу-
         ющий  пример). Определение типа с именем list должно находиться в
         пределах видимости об"явления.
               В пятом  примере  об"является  переменная  с  именем  line,
         структурного  типа,  поименованного  list. Тип структуры с именем
         list определяется тремя элементами. Первый  элементэто  указатель
         на  величину  типа  char, второй- на величину типа int, а третий-
         это указатель на следующую структуру типа list.
               В шестом примере об"является переменная  с  именем  record,
         имеющая  тип структуры с именем id. Заметим, что третий элемент с
         именем pname об"явлен как указатель на  другой  тип  структуры  с
         именем  name.  Это  об"явление  может  появиться перед об"явление
         структуры с именем name.

               4.5. Об"явление функций

               Синтаксис:

      []([])[,...];

               Об"явление функции определяет имя, тип возврата функции  и,
         возможно,  типы  и  число ее аргументов. Об"явление функции также
         называется forward- об"явлением. Декларатор функции об"являет имя
         функции, а спецификатор типа задает тип возврата. Если специфика-
         тор  типа  опущен  в  об"явлении  функции, то предполагается, что
         функция возвращает величину типа int.
               Об"явление функции может включать спецификаторы класса  па-
         мяти extern или static.

               Список типов аргументов.

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

               [,...]

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

                                      - 50 -

         только многоточие (...), то число аргументов функции является пе-
         ременным или равно нулю.

               Замечание:

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

               Имя  типа-  для типов структуры, совмещения или
         базового типа состоит из спецификатора этого типа (такого как int
         ). Имена типов для указателей, массивов и функций формируются пу-
         тем комбинации спецификатора типа с  "абстрактным  декларатором".
         Абстрактный декларатор- это декларатор без идентификатора. В раз-
         деле  4.9 "Имена типов" об"ясняется, каким об"разом формировать и
         интерпретировать абстрактные деклараторы.
               Для того чтобы об"явить функцию, не имеющую аргументов, мо-
         жет быть использовано специальное ключевое слово  void  на  месте
         списка  типов аргументов. Компилятор вырабатывает предупреждающее
         сообщение, если в вызове такой функции будут специфицированы  ар-
         гументы.
               Еще одна специальная конструкция допускается в списке типов
         аргументов. Это фраза void *, которая специфицирует аргумент типа
         указатель. Эта фраза может быть использована в списке типов аргу-
         ментов вместо имени типа.

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

               Тип возврата

               Функции могут возвращать величины любого типа за исключени-
         ем массивов и функций. Для этого посредством спецификатора  типа-
         "type-specifier" в об"явлении функции можно специфицировать любой
         тип:  основной,  структуру  или совмещение. Идентификатор функции
         может быть модифицирован одной или несколькими  звездочками  (*),
         чтобы об"явить возвращаемую величину типа указателя.
               Хотя функции и не допускают возвратов массивов или функций,
         но  они  могут возвращать указатели на массивы или функции. Функ-
         ции, которые возвращают указатели на  величины  типа  массив  или
         функция, об"являются посредством модификации идентификатора функ-
         ции  квадратными  скобками, звездочкой и круглыми скобками, чтобы
         сформировать составной декларатор. Формирование  и  интерпретация
         составных деклараторов рассматривались в разделе 4.3.2.

                                      - 51 -

               Примеры:

               int add(int, int);                  /* Example 1 */

               double calc();                      /* Example 2 */

               char *strfind(char *,...);          /* Example 3 */

               void draf(void);                    /* Example 4 */

               double (*sum(double, double)) [3];  /* Example 5 */

               int (*select(void)) (int) ;         /* Example 6 */

               char *p;                            /* Example 7 */
               short *q;
               int prt(void *);

               В  первом  примере  об"является функция, поименованная add,
         которая требует два аргумента типа int и возвращает величину типа
         int.

               Во  втором примере об"является функция, поименованная calc,
         которая возвращает величину типа double. Список типов  аргументов
         не  задан.  В  третьем примере об"является функция, поименованная
         strfind, которая возвращает указатель на величину типа char. Фун-
         кция требует, по крайней мере один аргументуказатель на  величину
         типа char. Список типов аргументов заканчивается запятой с много-
         точием, обозначающим, что функция может потребовать большее число
         аргументов.
               В  четвертом  примере  об"является функция с типом возврата
         void (нет возвращаемой величины). Список типов  аргументов  также
         void, означающий отсутствие аргументов для этой функции.
               В  пятом  примере sum об"является как функция, возвращающая
         указатель на массив из трех величин типа double. Функция sum тре-
         бует два аргумента, каждый из  которых  является  величиной  типа
         double.
               В  шестом  примере функция, поименованная select, об"явлена
         без аргументов и возвращает указатель на функцию. Указатель возв-
         рата ссылается на функцию, требующую один  аргумент  типа  int  и
         возвращающую величину типа int.
               В  седьмом  примере  об"явлена функция prt, которая требует
         аргумент- указатель любого типа, и  которая  возвращает  величину
         типа  int. Любой указатель p или q могли бы быть использованы как
         аргументы функции без выдачи при этом предупреждающего сообщения.

               4.6. Классы памяти

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

                                      - 52 -

         ет свое значение.
               Хотя Си определяет два типа классов памяти, но, тем не  ме-
         нее, имеется следующих четыре спецификатора классов памяти:

               auto
               register
               static
               extern

               Об"екты классов auto и register имеют локальное время  жиз-
         ни. Спецификаторы static и extern определяют об"екты с глобальным
         временем  жизни. Каждый из спецификаторов класса памяти имеет оп-
         ределенный смысл, который влияет на видимость функций и  перемен-
         ных  в  той же мере, как и сами классы памяти. Термин "видимость"
         относится к той части программы, в которой могут  ссылаться  друг
         на друга функции и переменные. Об"екты с глобальным временем жиз-
         ни существуют на протяжении выполнения исходной программы, но они
         могут  быть  видимы не во всех частях программы. Видимость и свя-
         занная с ней концепция времени жизни рассмотрена в разделе 3.5.
               Месторасположение об"явления переменной или функции  внутри
         исходных  файлов  также влияют на класс памяти и видимость. Гово-
         рят, что об"явления вне определения всех функций и переменных от-
         носятся к внешнему уровню, а об"явления внутри определений  функ-
         ций относятся к внутреннему уровню.
               Точный смысл каждого спецификатора класса памяти зависит от
         того,  находится ли об"явление на внешнем или внутреннем уровне и
         от того, об"явлен ли об"ект функцией или переменной. В  следующем
         разделе  описывается  смысл спецификаторов класса памяти в каждом
         случае об"явления, а также  об"ясняется  режим  умолчания,  когда
         спецификатор  класса  памяти опущен при об"явлении переменной или
         функции.

               4.6.1. Об"явления переменной на внешнем уровне

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

               -переменная  на  внешнем уровне может быть определена путем
         ее об"явления со спецификатором класса памяти static. Такая пере-
         менная может быть явно инициализирована  константным  выражением.
         Если  инициализатор отсутствует, то переменная автоматически ини-
         циализируется нулем во время компиляции. Таким образом,  об"явле-
         ния  static  int  k = 16; и static int k; оба рассматриваются как
         определения;

               -переменная определяется, когда она  явно  инициализируется

                                      - 53 -

         на  внешнем уровне. Например, int j = 3; это определение перемен-
         ной.

               Так как переменная определяется на внешнем уровне,  то  она
         видима  в пределах остатка исходного файла, от места, где она оп-
         ределена. Переменная не видима выше своего определения в  том  же
         самом  исходном файле ни в других исходных файлах программы, если
         не об"явлена ссылка, которая делает ее видимой.
               Переменная может быть определена на внешнем  уровне  внутри
         исходного файла только один раз. Если задается спецификатор клас-
         са  памяти static, то в других исходных файлах могут быть опреде-
         лены переменные с тем  же  именем.  Так  как  каждое  определение
         static  видимо  только  в  пределах своего собственного исходного
         файла, то конфликта не возникнет.
               Спецификатор класса памяти extern используется для об"явле-
         ния ссылки на переменную, определенную где-то в другом месте. Та-
         кие об"явления используются в случае, когда нужно сделать видимым
         определение переменной в других исходных файлах или  выше  места,
         где  она определена в том же самом исходном файле. Так как ссылка
         на переменную об"явлена на внешнем уровне, то переменная видима в
         пределах остатка исходного файла от места об"явления ссылки.
               В об"явлениях, которые используют спецификатор класса памя-
         ти extern, инициализация не допускается, так как они ссылаются на
         переменные, чьи величины уже определены.
               Переменная, на которую делается ссылка extern, должна  быть
         определена  на  внешнем уровне только один раз. Определение может
         быть сделано в любом из исходных файлов, составляющих программу.
               Есть одно исключение из правил, описанных выше. Можно опус-
         тить из об"явления  переменной  на  внешнем  уровне  спецификатор
         класса  памяти и инициализатор. Например, об"явление int n; будет
         правильным внешним об"явлением. Это об"явление имеет два  различ-
         ных смысла в зависимости от контекста.

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

               2.  Если  нет такого определения, то об"явленной переменной
         распределяется память во время линкования и переменная  инициали-
         зируется  нулем.  Если  в программе появится более чем одно такое
         об"явление, то память распределится для  наибольшего  размера  из
         об"явленных переменных. Например, если программа содержит два не-
         инициализированных  об"явления переменной i на внешнем уровне int
         i; и char i; то память во время линкования распределится под  пе-
         ременную i типа int.

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

                                      - 54 -

               Пример:

                /*****************************************************

                                    SOURCE FILE ONE
                *****************************************************/

                extern int i;                 /* reference to i
                                                 defined below */

                main()
                {
                        i++;
                        printf("%d\n", i);    /* i equals 4 */
                        next();
                }

                int i = 3;                    /* definition of i */

                next()
                {
                        i++;
                        printf("%d\n", i);    /* i equals 5 */
                        other();
                }

                /*****************************************************
                                SOURCE FILE TWO
                *****************************************************/

                extern int i;                 /* reference to i in
                                                 first source file */

                other()
                {
                        i++;
                        printf("%d\n", i);    /* i equals 6 */
                }

               Два исходных файла  в  совокупности  содержат  три  внешних
         об"явления  i. Одно об"явление содержит инициализацию- int i = 3;
         , где глобальная переменная i определена  с  начальным  значением
         равным 3.
               Самое  первое  об"явление extern в первом файле делает гло-
         бальную переменную видимой выше ее определения в файле.
               Без об"явления extern функция main не смогла  бы  сослаться
         на  глобальную  переменную  i.  Об"явление extern переменной i во
         втором исходном файле делает глобальную переменную видимой в этом
         исходном файле.
               Все три функции выполняют одну и ту же задачу: они увеличи-
         вают i на 1 и печатают  получившееся  значение.  (Предполагается,
         что функция printf определена где-то еще в программе.). Печатают-
         ся величины равные 4, 5 и 6.
               Если бы переменная i не была бы инициализирована,она бы бы-
         ла  автоматически  установлена  в 0 при линковании. В этом случае
         напечатанные значения были бы равны 1, 2 и 3.

                                      - 55 -

               4.6.2. Об"явление переменной на внутреннем уровне

               Любой из четырех спецификаторов класса  памяти  может  быть
         использован  для об"явления переменной на внутреннем уровне. Если
         спецификатор класса памяти опускается в об"явлении переменной  на
         внутреннем уровне, то подразумевается класс памяти auto.
               Спецификатор  класса памяти auto об"являет переменную с ло-
         кальным временем жизни. Переменная видима только в том блоке, где
         она об"явлена. Об"явления переменных auto могут включать  инициа-
         лизаторы.  Переменные класса памяти auto автоматически не инициа-
         лизируются, а инициализируются явно при об"явлении или присваива-
         нии начальных значений, посредством операторов внутри блока. Если
         нет инициализации, то величина переменной auto считается  неопре-
         деленной.
               Спецификатор  класса памяти register сообщает компилятору о
         том, чтобы он распределил память под переменную в регистре,  если
         это  возможно. Использование регистровой памяти обычно приводит к
         более быстрому времени доступа и к меньшему размеру  результирую-
         щего кода. Переменные, об"явленные с классом памяти register име-
         ют ту же самую видимость, что и переменные auto.
               Число регистров, которое может быть использовано под память
         переменных,  зависит от машины. Когда компилятор встречает специ-
         фикатор класса памяти register в об"явлении, а свободного регист-
         ра не имеется, то для  переменной  распределяется  память  класса
         auto.  Компилятор  назначает  переменным регистровую память в том
         порядке, в котором появляются об"явления в исходном файле. Регис-
         тровая память, если она имеется, гарантирована только для  целого
         и адресного типов.
               Переменная,  об"явленная на внутреннем уровне со специфика-
         тором класса памяти static,имеет глобальное время жизни  и  имеет
         видимость только внутри блока, в котором она об"явлена. В отличие
         от переменных auto, переменные, об"явленные как static, сохраняют
         свое значение при завершении блока.
               Переменные класса памяти static могут быть инициализированы
         константным выражением. Если явной инициализации нет, то перемен-
         ная  класса памяти static автоматически устанавливается в 0. Ини-
         циализация выполняется один раз во время компиляции.  Инициализа-
         ция переменной класса памяти static не повторяется при новом вхо-
         де в блок.
               Переменная,  об"явленная  со  спецификатором  класса памяти
         extern, является ссылкой на переменную с тем же самым именем, оп-
         ределенную на внешнем уровне в любом исходном файле программы.
               Цель внутреннего об"явления extern  состоит  в  том,  чтобы
         сделать  определение  переменной  внешнего  уровня видимой внутри
         блока. Внутреннее об'явление extern не  изменяет  видимость  гло-
         бальной переменной в любой другой части программы.

               Пример:

               int i = 1;

               main()
                {       /* reference to i, defined above */
                        extern int i;

                        /* initial value is zero; a is

                                      - 56 -

                           visible only within main */
                        static int a;

                        /* b is stored in a register, if possible */
                        register int b = 0;

                        /* default storage class is auto */
                        int c = 0;

                        /* values printed are 1, 0, 0, 0 */
                        printf("%d\n%d\n%d\n%d\n", i, a, b, c);
                        other();
                }
                other()
                {
                        /* i is redefined */
                        int i = 16;

                        /* this a is visible only within other */
                        static int a = 2;
                        a += 2;
                        /* values printed are 16, 4 */
                        printf("%d\n%d\n", i, a);
                }

               Переменная i определяется на внешнем уровне с инициализаци-
         ей  1.  В  функции  main  об"явлена ссылка extern на переменную i
         внешнего уровня. Переменная класса  памяти  static  автоматически
         устанавливается  в 0, так как инициализатор опущен. Вызов функции
         print (предполагается, что функция print  определена  в  каком-то
         месте исходной программы.) печатает величины 1, 0, 0, 0.
               В  функции other, переменная i переопределяется как локаль-
         ная переменная с начальным значением 16. Это не влияет на  значе-
         ние внешней переменной i. Переменная a об"является как переменная
         класса памяти static с начальным значением 2. Она не противоречит
         переменной a, об"явленной в функции main, так как видимость пере-
         менных  класса памяти static на внутреннем уровне ограничена бло-
         ком, в котором она об"явлена.
               Значение переменной увеличивается на 2 и становится  равным
         4.  Если бы функция other была вызвана снова в той же самой прог-
         рамме, то начальное значение a стало бы равным 4. Внутренние  пе-
         ременные  класса памяти static сохраняют свои значения, когда за-
         канчивается выполнение блока, в котором они об"явлены.

               4.6.3. Об"явление функции на внешнем и внутреннем уровнях

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

                                      - 57 -

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

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

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

               Синтаксически, записи инициализатора предшествует знак рав-
         но (=)

                =

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

               4.7.1. Базовые типы и типы указателей

               Синтаксис:

               =

               Величина  выражения присваивается переменной. Для выражения

                                      - 58 -

         допустимы правила преобразования.
               Примеры:

                int x = 10;                    /* Example 1 */

                register int *px = 0;          /* Example 2 */

                int c = (3 * 1024);            /* Example 3 */

                int *b = &x;                   /* Example 4 */

               В первом примере x инициализируется константным  выражением
         10.  Во втором примере, указатель px инициализирован нулем, в ре-
         зультате чего получился "null" указатель. В третьем  примере  ис-
         пользуется константное выражение для инициализации c. В четвертом
         примере инициализируется указатель b адресом другой переменной x.

               4.7.2. Составные типы

               Синтаксис:

               ={}

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

               Пример:

               int p[4] [3] = {
                         { 1, 1, 1 },
                         { 2, 2, 2 },
                         { 3, 3, 3,},
                         { 4, 4, 4,},
                };

               В  примере  об"является  массив p размерности 4 строки на 3
         столбца. Элементы первой строки инициализируются 1, второй строки

                                      - 59 -

         2 и т. д. Заметим, что списки инициализаторов третьей и четвертой
         строк заканчиваются запятой. Последний список  инициализаторов  {
         4, 4, 4,} также заканчивается запятой.
               Эти  дополнительные  запятые  допускаются, но не требуются.
         Требуются только те запятые, которые разделяют константные  выра-
         жения  и  списки  инициализации.  Если  список инициализаторов не
         структурирован под составной об"ект, то его величины присваивают-
         ся в том порядке, в котором подстыкованы элементы об"екта. Поэто-
         му вышеприведенная инициализация эквивалентна следующей:

               int p[4] [3] = {
                         1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4
                };

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

               typedef struct {
                    int n1, n2, n3;
                } triplet;

                triplet nlist[2] [3] = {
                 { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } },  /* Line 1 */
                 { { 10,11,12}, { 13,14,15}, { 15,16,17} }   /* Line 2 */
                };

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

               1. Первая левая фигурная скобка Line 1 информирует компиля-
         тор о том, что это начало  инициализации  первой  строки  массива
         nlist(nlist[0]).
               2. Вторая левая фигурная скобка означает то, что начинается
         инициализация  первого  элемента первой строки массива ( nlist[0]
         [0] ).
               3. Первая правая фигурная скобка сообщает об окончании ини-
         циализации первого элемента- структуры  nlist[0]  [0].  Следующая
         левая  фигурная  скобка  сообщает  о начале инициализации второго
         элемента первой строки nlist[0] [1].
               4. Процесс продолжается до конца Line 1 и заканчивается  по
         последней правой фигурной скобке.

               Аналогично, Line 2 назначает величины второй строке массива
         nlist.
               Заметим, что внешние фигурные скобки инициализаторов Line 1
         и  Line 2 требуются. Следующая конструкция, в которой внешние фи-
         гурные скобки опущены будет неверной.

                            /* THIS CAUSES AN ERROR */

                                      - 60 -

                triplet nlist[2] [3] = {
                 { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 },  /* Line 1 */
                 { 10,11,12}, { 13,14,15}, {16,17,18 }   /* Line 2 */
               };

               В этом примере первая левая фигурная скобка в Line 1  стар-
         тует  инициализацию  nlist[0],  которая является массивом из трех
         структур. Величины 1, 2,  3  назначаются  трем  элементам  первой
         структуры. Когда встретится правая фигурная скобка (после величи-
         ны  3), инициализация nlist[0] закончится и две оставшиеся струк-
         туры автоматически инициализируются нулем. Аналогично, { 4, 5,  6
         }  инициализирует  первую структуру во второй строке nlist, а ос-
         тавшиеся две структуры nlist[1] установятся в нуль. Когда  компи-
         лятор встретит следующий список инициализации { 7, 8, 9 }, то это
         приведет  к  попытке инициализировать nlist[2]. Так как nlist со-
         держит только две строки, то будет выдано сообщение об ошибке.

               Примеры:

                /******************* Example 1 *********************/

                struct list {
                        int i, j, k;
                        float n[2] [3];
                        } x = {
                               1,
                               2,
                               3,
                               {4.0, 4.0, 4.0}
                       };

                /******************* Example 2 *********************/

                union {
                       char x[2] [3];
                       int i, j, k;
                       } y = {
                               {'1'},
                               {'4'}
                       };

               В первом примере три элемента int структурной переменной  x
         инициализированы  1,  2,  и 3 соответственно. Три элемента первой
         строки массива m инициализированы как 4.0. Элементы второй строки
         инициализированы нулем по умолчанию.
               Во втором примере инициализируется переменная y типа совме-
         щения. Первым элементом совмещения является массив, для  которого
         требуется составной инициализатор. Список инициализации {'1'} за-
         дает величины для первой строки массива. Поскольку в списке всего
         одна  величина, то только первый элемент строки массива инициали-
         зируется символом 1 , а оставшиеся два элемента в строке  инициа-
         лизируются  нулем  (символом  \0)  по умолчанию. Аналогично, первый
         элемент второй строки массива x инициализируется  символом  4,  а
         оставшиеся два элемента в строке инициализируются нулем.

                                      - 61 -

               4.7.3. Строковые инициализаторы

               Массив может быть инициализирован строчным литералом.
               Например,

                char code[ ] = "abc";

               инициализирует  code как массив символов из четырех элемен-
         тов. Четвертым элементом является символ \0, который завершает  все
         строковые литералы.
               Если  специфицируется  размер  массива, а строка больше чем
         специфицированный размер, то лишние символы отбрасываются. Следу-
         ющее об"явление инициализирует переменную code, как  трехэлемент-
         ный массив символов:

               char code[3] = "abcd"

               В примере только три первые символа инициализатора назнача-
         ются для массива code. Символ d и сивол нуль отбрасываются.
               Если  строка  короче, чем специфицированный размер массива,
         то оставшиеся элементы массива инициализируются  нулем  (символом
         \0).

               4.8. Об"явления типов

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

               4.8.1. Типы структур, совмещений и перечислений

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

                /******************** Example 1 ********************/

                                      - 62 -

                enum status {
                        loss = -1,
                        bye,
                        tie = 0,
                        win,
                        };

                /********************* Example 2 *******************/

                struct student {
                        char name[20];
                        int id, claas;
                        };

               В  первом примере об"является тип перечисления, поименован-
         ный status. Имя типа может быть использовано в  об'явлениях  пер-
         менных типа перечисления. Идентификатор loss явно устанавливается
         в  -1.  Идентификаторы  bye и tie ассоциируются со значением 0, а
         win принимает значение 1. Во втором примере об"является структур-
         ный тип, поименованный student. Теперь можно  использовать  такое
         об"явление, как struct student employee, чтобы об"явить структур-
         ную переменную employee типа student.

               4.8.2. Об"явления typedef

               Синтаксис:

           typedef [,...];

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

               Примеры:

                /******************** Example 1 ********************/

                typedef int WHOLE;

                                      - 63 -

                /******************** Example 2 ********************/

                typedef struct club {
                        char name[30];
                        int sise, year;
                        } GROUP;

                /******************** Example 3 ********************/

                typedef GROUP *PG;

                /******************** Example 4 ********************/

                typedef void DRAWE(int, int);

               В первом примере об"является WHOLE как синоним для int .
               Во втором примере об" является GROUP как структурный тип  с
         тремя  элементами.  Так как специфицирован также тег clab, то имя
         GROUP и тег club могу быть использованы в об"явлениях.
               В  третьем  примере используется предидущее имя typedef для
         об"явления адресного типа. Тип PG об"является  как  указатель  на
         тип GROUP, который в свою очередь определен как структурный тип.
               В  последнем  примере  представлен тип DRAWE для функции не
         возвращающей значения и требующей два аргумента типа int. Это оз-
         начает, например, что об"явление DRAWE box; эквивалентно об"явле-
         нию void box(int, int);

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

               Имя типа специфицирует особенности типа данных. Имена типов
         используются в трех контекстах: в списках типов  аргументов,  при
         об"явлении функций, в вычислениях cast (преобразованиях типов), и
         в  sizeof  операциях.  Списки  типов аргументов рассматривались в
         разделе 4.5. "Об"явления функций". Преобразования cast и операция
         sizeof обсуждаются в разделах  5.7.2.  и  5.3.4.  соответственно.
         Именами  для  основных,  перечисляющих, структурных и совмещающих
         типов являются спецификаторы типа для каждого из них.  Имена  для
         типов  указателя,  массива и функции задаются следующей синтакси-
         ческой формой:

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

                                      - 64 -

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

               long *                    /* Example 1 */

               int (*) [5]               /* Example 2 */

               int (*) (void)            /* Example 3 */

               В первом примере задано имя типа как указатель на тип long.
               Во втором и третьем примерах показано каким образом  скобки
         модифицируют составные абстрактные деклараторы. В примере 2 зада-
         но  имя типа для указателя на массив иэ пяти злементов. В третьем
         примере именуется указатель на функцию, не требующую аргументов и
         возвращающую значение типа int.


                                       - 65 -

                            5. ВЫРАЖЕНИЯ И ПРИСВАИВАНИЯ

               5.1. Введение

               В этом разделе описывается каким образом составлять выраже-
         ния и осуществлять присваивания в языке Си. Выражение- это комби-
         нация  операндов и операций, значением которой является отдельная
         величина. Операнд- это константная или переменная величина.  Каж-
         дый  операнд  выражения-  это также выражение, представляющее от-
         дельную величину. Операции определяют действия над операндами.
               В Си  присваиваются  значения  выражений.  Помимо  простого
         присваивания  посредством  операции "=",Си поддерживает составные
         операции присваивания, которые перед присваиванием выполняют  до-
         полнительные операции над своими операндами. Окончательное значе-
         ние  результата зависит от старшинства операций и от побочных эф-
         фектов, если они возникают.  Порядок  вычисления  устанавливается
         определенным группированием операндов и операций в выражении. По-
         бочный  эффект-  это изменения состояния машины, вызванные в про-
         цессе вычисления выражения.
               В выражении с побочным эффектом, вычисление одного операнда
         может зависеть от значения другого. Для одних и тех  же  операций
         от порядка, в котором вычисляются операнды, также зависит резуль-
         тат выражения.
               Величина, представляемая каждым операндом в выражении, име-
         ет  тип, который может быть преобразован к другим типам в опреде-
         ленном контексте. Преобразования типов имеют место в присваивани-
         ях, cast операциях, вызовах функций и при выполнении операций.

               5.2. Операнды

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

               5.2.1 Константы

               Операнду - константе соответствует значение и тип представ-
         ляющей его константы. Константа-символ имеет тип int. Целая конс-
         танта имеет типы: int, long, unsigned int или  unsigned  long,  в
         зависимости  от  размера целого и от того как специфицирована его
         величина. Константы с плавающей точкой всегда имеют  тип  double.
         Строковые  литералы рассматриваются как массивы символов и обсуж-
         даются в разделе 5.2.3.

                                      - 66 -

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

               Идентификаторы именуют переменные и функции. Каждый иденти-
         фикатор имеет тип, который устанавливается  при  его  об"явлении.
         Значение идентификатора зависит от типа следующим образом:

               - идентификаторы целых и плавающих типов представляют вели-
         чины соответствующего типа;

               -  идентификатор  перечисляющего типа представляет значение
         одной константы из множества значений  констант  в  перечислении.
         Значение идентификатора равно значению этой константы. Тип значе-
         ния есть int, что следует из определения перечисления;

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

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

               -  идентификатор, об"явленный как массив, представляет ука-
         затель, чье значение является адресом первого  элемента  массива.
         Тип  адресуемых  указателем  величин-  это тип элементов массива.
         Например, если series об"явлен как массив целых из 10- ти элемен-
         тов, то идентификатор series представляет  адрес  массива,  тогда
         как  индексное  выражение  series[5]  ссылается на шестой элемент
         массива. Индексные выражения рассмотрены в разделе  5.2.5.  Адрес
         массива  не изменяется во время выполнения программы, хотя значе-
         ния отдельных элементов  могут  изменяться.  Значение  указателя,
         представленное  идентификатором массива, не является переменной и
         поэтому идентификатор массива не может появляться в  левой  части
         операции присваивания;
               - идентификатор, об"явленный как функция, представляет ука-
         затель,  чье  значение  является адресом функции. Тип, адресуемый
         указателем , - это специфицированный тип функционального  возвра-
         та.  Адрес  функции  не изменяется во время выполнения программы.
         Меняется только значение возврата. Таким образом,  идентификаторы
         функции не могут появляться в левой части операции присваивания.

               5.2.3. Строки

               Строковый литерал состоит из  последовательности  символов,
         заключенных в двойные кавычки. Строковый литерал представляется в
         памяти  как  массив элементов типа char. Строковый литерал предс-
         тавляет адрес первого элемента этого массива. Адрес первого  эле-
         мента строки является константой, так же как и сама строка.
               Так  как  строковые литералы- это полноценные указатели, то
         они могут быть использованы в контексте, допускающем величины ти-
         па указателей, подчиняясь при этом  тем  же  самым  ограничениям.
         Строковые  литералы имеют все же одно дополнительное ограничение:
         они не изменяемы и не могут  появиться  в  левой  части  операции
         присваивания.
               Последним  символом  строки всегда является символ нуль "0".

                                      - 67 -

         Символ нуль не видим в строковом выражении, но он добавяется  как
         последний  элемент,  когда  строка  запоминается.  Таким образом,
         строка "abc" содержит четыре символа, а не три.

               5.2.4. Вызовы функций

               Синтаксис:

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

               5.2.5. Индексные выражения

               Синтаксис:

               []

               Здесь квадратные скобки - это терминальные символы. Индекс-
         ное  выражение  представляет  величину,  адрес которой состоит из
         суммы   значений   выражения1   ""   и   выражения2-
         "". Выражение1- это любой указатель, такой как иден-
         тификатор  массива, а выражение2- это целочисленная величина. Вы-
         ражение2 должно быть заключено в квадратные скобки "[]".
               Индексное выражение обычно используется для ссылок на  эле-
         менты  массива, тем не менее, индекс может появиться с любым ука-
         зателем.
               Индексное выражение вычисляется путем сложения целой  вели-
         чины  с значением указателя  c последу-
         ющим применением к результату операции разадресации "*". Операция
         разадресации  описана  в разделе 5.3.3. Нарпимер, для одномерного
         массива следующие четыре выражения эквивалентны в  предположении,
         что a- это указатель, а b- это целое.

               a[b]
               *(a + b)
               *(b + a) b[a]

               В  соответствии с правилами преобразования типов для опера-
         ции  сложения  (см. раздел 5.3.6.), целочисленная величина преоб-
         разуется к адресному представлению путем умножения ее  на  размер
         типа,  адресуемого указателем. Например, предположим, что иденти-

                                      - 68 -

         фикатор line ссылается на массив величин типа int.  Чтобы  вычис-
         лить выражение line[i], целая величина i умножается на размер ти-
         па  int.  Преобразованное  значение i представляет i позиций типа
         int. Это преобразованное значение складывается с начальным значе-
         нием указателя line, что дает адрес, который расположен на i  по-
         зиций типа int от line.
               Последним  шагом  вычисления  индексного выражения является
         операция разадресации, применяемая к полученному адресу.  Резуль-
         татом является значение элемента массива, который позиционирован.
               Заметим,  что индексное выражение line[0] представляет зна-
         чение первого элемента массива, так как отсчет  смещения  ведется
         от  нуля.  Следовательно, такое выражение, как line[5], ссылается
         на шестой элемент массива.

               Ссылки на многомерный массив.

               Индексное выражение может быть снова проиндексировано. Син-
         таксис такого выражения следующий:

               [][]...

               Данное индексное выражение интерпретируется слева  направо.
         Сначала    вычисляется    самое    левое   индексное   выражение-
         [].    Адрес    результата     сложения
           и   имеет смысл адресного выражения, с
         которым складывается  и т. д. Операция  разадресации
         осуществляется  после вычисления последнего индексного выражения.
         Однако, операции разадресации не производится, если значение пос-
         леднего указателя адресует величину типа массив (смотри третий  и
         четвертый примеры ниже).
               Выражения  с  несколькими  индексами  ссылаются на элементы
         многомерных массивов. Многомерный массив- это массив,  элементами
         которого являются массивы. Например, первым элементом трехмерного
         массива является массив с двумя измерениями.

               Примеры:

               int prop[3][4][6];

               int i, *ip, (*ipp)[6];

               i = prop[0][0][1];               /* Example 1 */

               i = prop[2][1][3];               /* Example 2 */

               ip = prop[2][1];                 /* Example 3 */

               ipp = prop[2];                   /* Example 4 */

               Maccив, поименованный prop, содержит 3 элемента, каждый  из
         которых-  это массив 4 на 6 величин типа int. В примере 1 показа-
         но, каким образом сделать ссылку на второй элемент третьего  мас-
         сива  4  на  6 prop. Массив заполнен построчно, так что последний
         индекс меняется наиболее быстро. Выражение prop[0][0][2] ссылает-

                                      - 69 -

         ся на следующий (третий) элемент массива и т. д.
               Во втором примере показана более сложная ссылка на  отдель-
         ный элемент prop. Выражение вычисляется следующим образом:

               1.  Первый индекс 2 умножается на размер типа int и на раз-
         мер массива 4 на 6 и складывается с значением указателя prop. Ре-
         зультат будет указывать на третий массив размера 4 на 6 элементов
         в трехмерном массиве prop.

               2. Затем, второй индекс 1 умножается на размер 6-ти элемен-
         тов типа int и складывается с адресом, соответствующим prop[2].
               3. Каждый элемент массива из шести элементов- это  величина
         типа  int,  поэтому  индекс 3 умножается на размер типа int перед
         сложением с адресом prop[2][1]. результирующий указатель адресует
         четвертый элемент массива из шести элементов.
               4. На последнем шаге вычисления выражения prop[2][1][3] вы-
         полняется разадресация указателя.  Результатом  является  элемент
         типа int, расположенный по этому адресу.

               В примерах 3 и 4 представлены случаи, когда разадресация не
         применяется.  В  примере  3   выражение prop[2][1]- это ссылка на
         массив из шести элементов в трехмерном массиве prop. Так как зна-
         чение указателя адресует массив, то операция разадресации не при-
         меняется. Аналогично, результатом выражения prop[2] в  примере  4
         является значение указателя, адресующее двумерный массив.

               5.2.6. Выражение выбора структурного элемента

               Синтаксис:

               .
                ->

               Выражение выбора структурного элемента ссылается на элемен-
         ты структур или совмещений. Выражение выбора имеет значение и тип
         выбранного элемента.
               В первой синтаксической форме . вы-
         ражение  представляет величину типа struct или union,
         а  идентификатор именует злемент, специфицированной структуры или
         совмещения.  Во  второй  синтаксической  форме     ->
           выражение    представляет  указатель на
         структуру или совмещение, а идентификатор именует элемент  специ-
         фицированной структуры.
               Обе синтаксические формы  выражений  выбора  элемента  дают
         одинаковый  результат. Действительно, запись, включающая операцию
         адресного выбора ->, является сокращенной версией записи  с  точ-
         кой, для случая, когда выражению, стоящему перед точкой, предшес-
         твует операция разадресации "*". Операция разадресации рассматри-
         вается в разделе 5.3.3.
               Поэтому  запись    ->  эквивалентна
         записи   (*   ).   ,    когда    значение
          является адресной величиной.

               Примеры:

                                      - 70 -

               struct pair {
                        int a;
                        int b;
                        struct pair *sp;
                        } item, list[10];

               item.sp = &item               /* Example 1 */

               (item.sp) -> a = 24           /* Example 2 */

               list[8].b = 12                /* Example 3 */

               В  первом  примере  адрес структуры item присваивается эле-
         менту sp этой структуры. Это имеет смысл того, что item  содержит
         указатель на саму себя.
               Во втором примере используется адресное выражение item.sp с
         операцией выбора -> , чтобы присвоить значение элементу a.
               В третьем примере показано, каким образом из массива струк-
         тур выбрать отдельный элемент структуры.

               5.2.7. Выражения с операциями

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

               Синтаксис:

               sizeof

               Бинарное выражение состоит из двух  операндов,  разделенных
         бинарной операцией- .

               Синтаксис:

               Тернарное  выражение состоит из трех операндов, разделенных
         тернарной операцией "?:"

               Синтаксис:

                ?  :

               Выражения присваивания используют унарные, бинарные  и  со-
         тавные  операции  присваивания.  Унарными операциями присваивания
         являются инкремент "++" и декремент "--". Бинарная операция прис-
         ваивания всего одна "=". Составные  операции  присваивания  будем
         обозначать  как  . Каждая составная операция
         присваивания- это комбинация бинарной операции с простой операци-
         ей присваивания.

                                      - 71 -

               Синтаксис выражений присваивания:

                ++

                --

               ++

               --

                =

               5.2.8. Выражения в скобках

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

               (10 + 5) / 5

         скобки, заключающие запись  10 + 5,  означают,  что  величина  10
         + 5 является левым операндом операции деления. Результат  выраже-
         ния  (10  + 5) / 5 равен 3. Без скобок значение записи 10 + 5 / 5
         равнялось бы 11. Хотя скобки влияют на то, каким путем  группиру-
         ются  операнды  в выражении, они не гарантируют детальный порядок
         вычисления выражения.

               5.2.9. Type-cast выражения

               Type-cast выражения имеют следующий синтаксис:

               ()

               Type-cast преобразования рассматриваются в разделе  5.7.2.,
         имена типов представлены в разделе 4.9.
               5.2.10. Константные выражения

               Константное  выражение- это выражение, результатом вычисле-
         ния которого является константа. Операндами константного  выраже-
         ния могут быть целые константы, константы символы, плавающие кон-
         станты,  константы  перечиисления,  type-cast  выражения целого и
         плавающего типов и другие константные выражения.  Операнды  могут
         комбинироваться и модифицироваться посредством операций точно так
         же  как это описано в разделе 5.2.7. Константные выражения не мо-
         гут использовать операции присваивания (смотри  раздел  5.4)  или
         бинарную  операцию последовательного вычисления. Унарная операция
         адресации "&" может быть использована только при некоторых иници-
         ализациях (смотри последний абзац раздела 5.2.10.).
               Константные выражения, используемые в директивах препроцес-
         сора, имеют дополнительные ограничения, поэтому называются  огра-

                                      - 72 -

         ниченными                 константными                выражениями
         . Ограниченые константные выраже-
         ния не могут содержать  sizeof-выражений,  констант  перечисления
         или type-cast выражений любого типа. Они могут, однако, содержать
         специальные константные, имеющие синтаксис defined().
               Более полная информация дана в  разделе  8.2.1.  "Директива
         #define".
               Эти дополнительные ограничения также относятся к  констант-
         ным  выражениям,  используемым  для  инициализации  переменных на
         внешнем уровне. Тем не менее, такие выражения допускают  примене-
         ние  унарной  операции  адресации "&" к любой переменной внешнего
         уровня с основными и структурными типами, а также с типом  совме-
         щения и массивом внешнего уровня, индексированным константным вы-
         ражением.
               В  этих выражениях допускается сложение или вычитание с ад-
         ресными подвыражениями.

               5.3. Операции

               Си-операции  требуют  один  операнд (унарные операции), два
         операнда (бинарные операции) или три операнда  (тернарная  опера-
         ция).  Операции  присваивания- это унарные или бинарные операции.
         Операции присваивания описываются в разделе 5.4.
               Унарными операциями Си являются следующие:

               Символ                    Наименование

               - ~ !                     операции дополнения

               * &                       операции разадресации
                                         и адресации

               sizeof                    size- операция

               Интерпретация унарных операций производится справа налево.
               Бинарные операции интерпретируются слева направо.
               Бинарными операциями являются следующие:

               Символ                      Наименование

               * / %                      мультипликативные операции

               + -                        аддитивные операции

               << >>                      операции сдвига

               < > <= >= == !=            операции отношений

               & | ^                      операции с битами

               && ||                      логические операции

                                      - 73 -

               ,                          операция последовательных
                                          вычислений

               В Си имеется одна тернарная операция- это операция  условия
         "?:". Она интерпретируется справа налево.

               5.3.1. Обычные арифметические преобразования.

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

               1. Операнды типа float преобразуются к типу double.

               2. Если один операнд типа double, то второй операнд  преоб-
         разуется к типу double.

               3. Любые операнды типов char или short преобразуются к int.

               4.  Любые  операнды  типов unsigned char или unsigned short
         преобразуются к типу unsigned int.

               5. Если один операнд типа unsigned long, то второй  операнд
         преобразуется к типу unsigned long.

               6.  Если один операнд типа long, то второй операнд преобра-
         зуется к типу long.

               7. Если один операнд типа unsigned int, то  второй  операнд
         преобразуется к unsigned int.

               5.3.2. Операции дополнения

               Арифметическое отрицание "-".

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

               Двоичное дополнение "~"

                                      - 74 -

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

               Логическое не "!"

               Операция  логического  не "!" вырабатывает значение 0, если
         операнд есть true и значение 1, если операнд есть false.  Резуль-
         тат имеет тип int. Операнд должен быть целого, плавающего или ад-
         ресного типа.

               Примеры :
               / * ... example 1 ... * /
               short  x = 987;
               x = - x;
               / * ... example 2 ... */
               unsigned short  y = 0xaaaa;
               y = ~y;
               /* ... example 3 ...*/
               if (!(x < y));

               В  первом примере новое значение х является отрицанием 987,
         т.е. -987.
               Во втором примере переменной y присваивается  новое  значе-
         ние,  которое  является  дополнением беззнаковой величины 0xaaaa,
         т.е. 0x5555.
               В третьем примере если x больше или равно y,  то  результат
         выражения  равен  1 (true). Если x меньше y, то результат равен 0
         (false).

               5.3.3. Операция адресации и разадресации

               Разадресация "*"

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

               Адресация "&"

               Операция  адресации "&" вырабатывает адрес своего операнда.
         Операндом может быть любая величина, которая допустима в качестве
         любого операнда операции присваивания. (Операция присваивания об-
         суждается в разделе 5.4.). Результат операции адресации  является
         указателем на операнд. Тип, адресуемый указателем, является типом
         операнда.
               Операция адресации не может применяться к битовым полям па-

                                      - 75 -

         мяти  структур, она не может быть применена также к идентификато-
         рам класса памяти register.

               Примеры

               int *pa, x;

               int a[20];

               double d;

               pa = &a[5];                       /* Example 1 */

               x = *pa;                          /* Example 2 */

               if (x == *&x)                     /* Example 3 */

               printf("True\n");

               d = *(double*)(&x);               /* Example 4 */

               В первом примере операция адресации вырабатывает адрес шес-
         того элемента массива a. Результат записывается в адресную  пере-
         менную (указатель) pa.
               Во  втором  примере использууется операция разадресации для
         доступа к величине типа int, адрес которой запомнен в pa.  Значе-
         ние результата присваивается целочисленной переменной x.
               В  третьем примере будет печататься слово True. Здесь пока-
         зано, что результат применения операции разадресации к адресу  x-
         это то же самое, что x.
               В  четвертом  примере  показано полезное приложение правил,
         рассмотренных в примере 3. Адрес x преобразуется cast-  операцией
         к  указателю на double. Затем применяется операция разадресации и
         результатом выражения является величина типа double.

               5.3.4. Операция sizeof

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

               sizeof(),

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

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

                                      - 76 -

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

               Пример

               buffer = calloc(100, sizeof(int));

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

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

               Мультипликативные  операции  выполняют  операции  умножения
         "*",  деления  "/" и получения остатка от деления "%". Операндами
         операции % должны быть целые числа. Операции умножения "*" и  де-
         ления  "/"  выполняются  над целыми и плавающими операндами. Типы
         первого и второго операндов могут  отличаться.  Мультипликативные
         операции  выполняют обычные арифметические преобразования операн-
         дов. Типом результата является тип операндов  после  преобразова-
         ния.
               Преобразования,  выполненные  посредством мультипликативных
         операций, не поддерживают ситуаций левого и правого переполнения.
         Информация теряется, если результат мультипликативной операции не
         может быть представлен в типе операндов после преобразования.

               Умножение "*"
               Операция  умножения  указывает  на  то, что ее оба операнда
         должны быть умножены.

               Деление "/"
               Операция деления указывает на то, что ее первый операнд де-
         лится на второй. Если две целые величины не  делятся  нацело,  то
         результат  усекается. Деление на 0 дает непредсказуемые результа-
         ты.

               Остаток от деления "%"
               Результатом операции является остаток  от  деления  первого
         операнда на второй.

               Примеры

               int i = 10, j = 3, n;
               double x = 2.0, y;

               y = x * i;   /* example 1 */
               n = i/j      /* example 2 */
               n = i % j    /* example 3 */

               В первом примере x умножается на i, в результате получается
         20.0. Результат имеет тип double.
               Во  втором  примере 10 делится на 3. Результат усекается до

                                      - 77 -

         3. и имеет тип int.
               В третьем примере n присваивается остаток от деления 10  на
         3, т.е. 1.

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

               Аддитивные операции выполняют сложение "+" и вычитание "-".
         Операндами  могут  быть  целые  и плавающие величины. В некоторых
         случаях аддитивные операции могут также выполняться  на  адресных
         величинах.  На  целых  и  плавающих операндах выполняются обычные
         арифметические преобразования. Типом результата является тип опе-
         рандов после преобразования. Преобразования, выполняемые аддитив-
         ными операциями, не подерживают левого и правого переполнения. Ин-
         формация теряется, если результат аддитивной  операции  не  может
         быть представлен типом операндов после преобразования.

               Сложение "+"

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

               Вычитание "-"

               Операция вычитает второй операнд из первого. Операнды могут
         быть  целого или плавающего типов. Типы первого и второго операн-
         дов могут отличаться. Допускается вычитание целого из указателя и
         вычитание двух указателей.
               Когда целая величина вычитается из указателя, то перед  вы-
         понением  операции производятся те же самые преобразования, что и
         при сложении целого с указателем. Результатом вычитания  является
         указатель,  адресующий  память,  расположенную на i позиций перед
         исходным адресом, где i целое, а каждая позиция - это длина типа,
         адресуемого указателем. Новый указатель адресует тот же самый тип
         данных, что и исходный указатель.
               Один указатель может быть вычтен из другого, если они  ука-
         зывают на один и тот же тип данных. Разность между двумя указате-
         лями  преобразуется к знаковой целой величине  путем деления раз-
         ности на длину  типа,  который  адресуется  указателями.Результат
         представляет  число позиций памяти этого типа между двумя адреса-
         ми.

               Адресная арифметика

                                      - 78 -

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

               Примеры
               int i = 4, j;
               float x [ ];
               float * px;
               px = & x [4] + i;          /* example 1 */
               j = & x [i] - & x [i-2];   /* example 2 */

               В первом примере целочисленный операнд i складывается с ад-
         ресом  пятого  элемента  х.  Значения  i умножается на длину типа
         float и складываются с & x [4]. Значение результирующего указате-
         ля представляет адрес x [8] элемента массива.
               Во втором примере адрес третьего элемента x (задается как x
         [i-2]) вычитается из адреса пятого элемента  x  (эадается  как  x
         [i]).  Полученная разность делится на длину типа float. В резуль-
         тате получается целая величина 2.

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

               Операции сдвига сдвигают свой первый операнд влево (<<) или
         вправо (>>) на число позиций, специфицированных вторым операндом.
         Оба операнда должны быть целыми величинами. Обычные  арифметичес-
         кие  преобразования  выполняются.  Тип результата -это тип левого
         операнда после преобразования. При сдвиге влево правые освобожда-
         ющиеся биты устанавливаются в нуль. При сдвиге вправо  метод  за-
         полнения освобождающихся левых битов зависит от типа, полученного
         после преобразования первого операнда. Если тип unsigned, то сво-
         бодные  левые  биты устанавливаются в нуль.В противном случае они
         заполняются копией знакового бита. Результат операции  сдвига  не
         определен, если второй операнд отрицательный.
               Преобразования,  выполняяемые операторами сдвига, не поддер-
         живают левого и правого переполнения. Информация  теряется,  если
         результат сдвига не может быть представлен типом первого операнда
         после преобразования.

                                      - 79 -

               Пример :
               unsigned  int x,y,z;
               x = 0x00aa;
               y = 0x5500;
               z = (x << 8) + (y >> 8);

               В  примере  x сдвигается влево на 8 позиций, а y сдвигается
         вправо на 8 позиций. Результаты сдвигов складываются, давая вели-
         чину 0xaa55, которая присваивается z.

               5.3.8. Операции отношений

               Бинарные операции отношений сравнивают  первый  операнд  со
         вторым и вырабатывают значение 1(true) и 0 (false). Типом резуль-
         тата является int. Имеются следующие операции отношений :

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

                 <          Первый операнд меньше, чем второй операнд
                 >          Первый операнд больше, чем второй операнд
                 <=         Первый  операнд меньше или равен  второму
                            операнду
                 >=         Первый  операнд больше или равен  второму
                            операнду
                 = =        Первый операнд   равен   второму операнду
                 ! =        Первый операнд не равен  второму операнду

               Операции могут быть целого, плавающего или адресного типов.
         Типы первого и второго операндов могут различаться. Обычные ариф-
         метические  преобразования  выполняются  над  целыми и плавающими
         операндами.
               Первый или второй операнды операций равенства "= =" или не-
         равенства "! =" могут быть типа enum. Величина типа enum преобра-
         зуется точно так же , как и величины типа int.  Операндами  любой
         операции  отношения могут быть два указателя одного и того же ти-
         па. Для операций равенства или  неравенства  результат  сравнения
         показывает, равны ли оба адреса в указателях. Результат сравнения
         указателей   для других операций (<,>,<=,>=) отражает относитель-
         ное положение двух адресов памяти.
               Так как адрес данной величины произволен, то сравнение меж-
         ду адресами двух несвязанных величин,  вообще  говоря,  не  имеет
         смысла.  Однако, сравнение между адресами различных элементов од-
         ного и того же массива может быть полезным, т.к. элементы массива
         хранятся  в последовательном порядке. Адрес первого элемента мас-
         сива меньше чем адрес следующего элемента.
               Адресная величина может быть сравнена на равенство или  не-
         равенство  с константой 0. Указатель, имеющий значение 0, не ука-
         зывает на область памяти. Он называется нулевым указателем.  Зна-
         чение  указателя  равно  нулю,  если  оно таким явно задано путем
         присваивания или инициализации.

               Примеры

                                      - 80 -

               int x = 0, y = 0;
               x < y         /* example 1 */
               y > x         /* example 2 */
               x <= y        /* example 3 */
               x >= y        /* example 4 */
               x = = y       /* example 5 */
               x != y        /* example 6 */

               Если x и y равны, то выражения 3,4 и 5 имеют значение 1,  а
         выражения 1,2 и 6 имеют значения 0.

               5.3.9. Побитовые операции

               Побитовые  операции  выполняют  побитовое И (&), включающее
         ИЛИ (!) и исключающее ИЛИ (^). Операнды побитовых операций должны
         быть целого типа, но их типы могут быть отличными. Обычные  ариф-
         метические преобразования выполняются. Тип результата определяет-
         ся типом операндов после преобразования.

               Побитовое И (&)

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

               Побитовое включающее ИЛИ "!"

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

               Побитовое исключающее ИЛИ "^"

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

               Примеры
               short  i = 0xab00;
               short  j = 0xabcd;
               short  n;

               n = i&j;      /* example 1 */
               n = i|j;      /* example 2 */
               n = i^j;      /* example 3 */

               В  первом  примере n присвоится результат равный значению i
         (шестнадцатеричное AB00). Во втором примере результатом побитовой
         операции включающего ИЛИ будет величина ABCD (шестнадцатеричная),

                                      - 81 -

         в то время как в третьем примере результатом исключающего ИЛИ бу-
         дет значение CD (шестнадцатеричное).

               5.3.10. Логические операции

               Логические операции выполняют логическое И (&&) и  логичес-
         кое  ИЛИ  (!!).  Операнды логических операций могут быть  целого,
         плавающего или адресного типа. Типы первого и  второго  операндов
         могут  быть различными. Операнды логических выражений вычисляются
         слева направо. Если значения первого операнда  достаточно,  чтобы
         определить результат операции, то второй операнд не вычисляется.
               Логические операции не выполняют стандартные арифметические
         преобразования. Вместо этого они вычисляют каждый операнд с точки
         зрения его эквивалентности нулю. Указатель имеет значение 0, если
         это  значение явно установлено путем присваивания или инициализа-
         ции. Результатом логической операции является 0 или  1.  Тип  ре-
         зультата есть int.

               Логическое И (&&)

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

               Логическое ИЛИ (!!)

               Логическая  операция  ИЛИ  выполняет  над своими операндами
         операцию включающего ИЛИ. Она вырабатывает значение  0,  если оба
         операнда  имеют  значение  0.; если какой-либо из операндов имеет
         ненулевое значение, то результат операции равен  1.  Если  первый
         операнд имеет ненулевое значение, то второй операнд не вычисляет-
         ся.

               Примеры
               int x,y;
               if (x < y && y < z)                   /* example 1 */
                    printf ("x is less than z/n");

               if (x = = y !! x = = z)               /* example 2 */
                     printf ("x is equal to either y or z/n");

               В первом примере вызывается функция printf для печати сооб-
         щения, если x меньше чем y и y меньше чем z. Если x больше чем y,
         то  второй  операнд  (y?:

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

                                      - 83 -

         структурного, совмещения или адресного типа. Тип результата будет
         тем же самым типом структуры, совмещения или адреса.
               3. Один из второго или третьего операндов может быть указа-
         телем, а другой константным выражением со значением 0. Типом  ре-
         зультата является адресный тип.

               Пример
               j = (i<0) ? (-i) : (i);

               В  примере  j  присваивается  абсолютное значение i. Если i
         меньше нуля, то j присваивается -i. Если i больше или равно нулю,
         то j присваивается i.

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

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

               Операциями присваивания являются следующие:

               Операция               Действие

               ++                Унарный инкремент
               --                Унарный декремент
               =                 Простое присваивание
               *=                Умножение с присваиванием
               /=                Деление с присваиванием
               %=                Остаток от деления с присваиванием
               +=                Сложение с присваиванием
               -=                Вычитание с присваиванием
               <<=               Сдвиг влево с присваиванием
               >>=               Сдвиг вправо с присваиванием
               &=                Побитовое and с присваиванием
               |=                Побитовое включающее ИЛИ с присваиванием
               ^=                Побитовое исключающее ИЛИ с присваиванием

               При присваивании тип правого операнда преобразуется к  типу
         левого операнда. Специфика преобразования детально описана в раз-
         деле 5.7.

               5.4.1. Lvalue-выражения

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

                                      - 84 -

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

               5.4.2. Унарные инкремент и дектемент

               Унарная операция присваивания (++ и --) инкрементирует  или
         декрементирует  свой операнд. Операнд должен быть целого, плаваю-
         щего или адресного  типа.  В  качестве  операнда  допустимо также
         Lvalue-выражение.Операнды целого или плавающего типа преобразуют-
         ся путем сложения или вычитания целой 1. Тип результата соответс-
         твует  типу операнда. Операнд адресного типа инкрементируется или
         декрементируется размером об"екта, который он адресует. Инкремен-
         тированный указатель адресует следующий об"ект,  а  декрементиро-
         ванный указатель - предыдущий.
               Операции инкремента (++) или дектемента  (--)  могут  появ-
         ляться  перед  или после своего операнда. Когда операция является
         префиксом своего операнда, то операнд инкрементируется или декте-
         ментируется и его новое значение является результатом  вычисления
         выражения. Когда операция является постфиксом своего операнда, то
         непосредственным результатом выражения является значение операнда
         перед  его инкрементированием или декрементированием. После этого
         результат используется в контексте,  а  операнд  инкрементируется
         или декрементируется.

               Примеры
               /* ... example 1 ... */
               if (pos++ > 0)
               *ptt = *qtt;

               /* ... example 2 ... */
               if (line [--i] ! = '\n')
               return;

               В первом примере переменная pos сравнивается с 0,  а  затем
         инкрементируется.
               Во  втором  примере  переменная i декрементируется перед ее
         использованием в качестве индекса line.

               5.4.3. Простое присваивание

               Операция простого присваивания (=) выполняет  присваивание.
         Правый  операнд  присваивается  левому операнду. При присваивании
         выполняются  некоторые  правила  преобразования  (смотри   раздел
         5.7.1.).

                                      - 85 -

               Пример
               double x;
               int y;
               x = y;

               Значение y преобразуется к типу double и присваивается x.

               5.4.4. Составное присваивание

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

                +=

         и может быть понято как:

                =  +

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

               Пример
               # define MASK 0xffff
               n = MASK

               В этом примере операция побитового включающего ИЛИ выполня-
         ется на n и MASK и результат присваивается n. Константа MASK явно
         определенная директивой препроцессора # define.

               5.5. Старшинство и порядок выполнения

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

                                      - 86 -

         !------------------------------------------------------------!
         !  Операция        Вид операции            Порядок выполнения!
         !------------------------------------------------------------!
         !  ()[].->         Выражение               Слева направо     !
         !                                                            !
         !  -~!* &                                                    !
         !  ++--sizeof      Унарный                 Справа налево     !
         !  casts                                                     !
         !                                                            !
         !  */%             Мультипликативный       Слева направо     !
         !                                                            !
         !  +-              Аддитивный              Слева направо     !
         !                                                            !
         !  << >>           Сдвиг                   Слева направо     !
         !                                                            !
         !  <> <= >=       Отношение (неравенство)  Слева направо     !
         !                                                            !
         !  == !=          Отношение (равенство)    Слева направо     !
         !                                                            !
         !  &              Побитовое И              Слева направо     !
         !                                                            !
         !  ^              Побитовое исключающее                      !
         !                 ИЛИ                      Слева направо     !
         !                                                            !
         !  :              Побитовое включающее                       !
         !                 ИЛИ                      Слева направо     !
         !                                                            !
         !  &&             Логическое И             Слева направо     !
         !                                                            !
         !  !!             Логическе ИЛИ            Слева направо     !
         !                                                            !
         !  ?:             Условная                 Справа налево     !
         !                                                            !
         !  = *= /= %=     Простое и составное                        !
         !  += -= <<= >>=  присваивание             Справа налево     !
         !  &= != ^=                                                  !
         !                                                            !
         !  ,              Последовательное                           !
         !                 преобразование           Слева направо     !                                                      |
         !------------------------------------------------------------!

                Табл.5.1. Старшинство и порядок выполнения
                             операций в Си

               Из табл.5.1. следует, что операнды, состоящие из константы,
         идентификатора, строки, вызова функций, индексного выражения, вы-
         ражения  выбора  элементов  или скобочного выражения имеют высший
         приоритет и выполняются слево направо.  Преобразование  type-cast
         имеет  то же самое старшинство и порядок выполнения как и унарные
         операторы. Выражения могут содержать  различные  операции  одного
         старшинства.  Когда  несколько  оперций  одного  и того же уровня
         старшинства появляется в выражении, то они отрабатываются в соот-

                                      - 87 -

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

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

               Логические операции  также  обеспечивают  вычисление  своих
         операндов слева направо. Однако логические операции вычисляют ми-
         нимальное число операндов, необходимое для определения результата
         выражения. Таким образом, некоторые операнды выражения могут быть
         не вычислены. Например, в выражении x && y ++  второй  операнд  y
         ++ вычисляется только тогда, когда x есть true (не нуль). Так что
         y не инкрементируется, когда x есть false (нуль).

               В  нижеследующих примерах показано группирование по умолча-
         нию для различных выражений.

                Выражение                  Группирование по умолчанию

                a & b !! c                       (a & b) !! c

                a = b !! c                       a = (b !! c)

                q && r !! s--                    (q && r) !! s--

               В первом примере побитовая операция  И  (&)  имеет  большее
         старшинство, чем логическая операци ИЛИ (!!), поэтому выражение a
         & b является первым операндом логической операции ИЛИ.
               Во  втором примере логическая операция ИЛИ (!!) имеет боль-
         шее старшинство, чем операция простого присваивания, поэтому  вы-
         ражение  b !! c представляет правый операнд в присваивании. Заме-
         тим, что значение , присваиваемое a, есть нуль или единица.
               В третьем примере показано корректно оформленное выражение,
         которое может выработать неожиданный результат. Логическая опера-
         ция И (&&) имеет более высокое старшинство, чем логическая опера-
         ция  ИЛИ  (!!), поэтому запись q && r группируется в операнд. Так
         как логические операции обеспечивают вычисление  операндов  слева
         направо, то выражение q && r вычисляется раньше, чем s--. Однако,
         если q && r выработает ненулевое значение, то s-- не будет вычис-
         ляться  и s не декрементируется. Чтобы корректно решить эту проб-
         лему, необходимо чтобы s-- появилось в качестве первого  операнда
         выражения, либо декрементировано отдельной операцией.

               В  следующем  примере  показано неверное выражение, которое

                                      - 88 -

         вырабатывает программную ошибку.

               Неверное выражение          Группирование по умолчанию

                p == 0 ? p += 1 : p += 2   (p == 0 ? p += 1 : p) += 2

               В этом примере операция эквивалентности (==) имеет наиболь-
         шее старшинство, поэтому p == 0 группируется в качестве операнда.
         Тернарный оператор (?:) имеет следующее старшинство.  Его  первым
         операндом является выражение p ==0, вторым операндом является вы-
         ражение p +=1. Однако, последним операндом тернарной операции бу-
         дет рассмотрен p, а не p += 2, так как в данном случае p по стар-
         шинству операций связан более тесно с тернарной операцией, а не с
         сотавной  операцией  присваивания.  В результате будет выработана
         синтаксическая ошибка, поскольку += 2 не  имеет  левого  операнда
         (Lvalue-выражения).
               Чтобы предупредить ошибки подобного рода и сделать програм-
         му более наглядной, рекомендуется использовать скобки. Предыдущий
         пример может быть корректно офрмлен следующим образом:

                (p == 0) ? (p += 1) : (p += 2)

               5.6. Побочные эффекты

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

                add ( i + 1, i = j +2)

               Аргументы вызова функции могут быть вычислены в  любом  по-
         рядке. Выражение i + 1 может быть вычислено перед i=j+2, или нао-
         борот, с различным результатом в каждом случае.
               Унарные  операции инкремента и декремента включают присваи-
         вание и могут быть причиной побочных эффектов, как это показано в
         следующем примере:

                d=0
                a=b++=c++=d++;

               Значение a непредсказуемо. Значение d (инициализируется ну-
         лем), могло быть присвоено c, затем b и затем a, прежде чем любая
         из  переменных  была  бы инкрементирована. В этом случае a должно
         было бы быть эквивалентно нулю.
               Второй способ вычисления этого выражения начинается  вычис-
         лением  операнда  c++=d++.  Значение d (инициализированное нулем)
         присваивается c, а затем d и c инкрементируются.  Затем  значение

                                      - 89 -

         c,  которое теперь равно 1, присваивается b и b инкрементируется.
         Наконец, инкрементированное значение b присваивается  a.  В  этом
         случае окончательное значение a равно 2.
               Т.к. язык Си не опеределяет порядок изменения состояний ма-
         шины  (побочных  эффектов) при вычислениях, то оба эти метода вы-
         числения корректны и могут быть выполнены. Операторы, которые за-
         висят от частностей порядка вычисления  побочных  эфектов, выдают
         непереносимый и неясный код.

               5.7. Преобразования типов

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

               5.7.1. Преобразование типов при присваивании

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

               Знаковое целое преобразуется к короткому  знаковому  целому
         (short signed int) посредством усечения старших битов.
               Знаковое  целое  преобразуется  к длинному знаковому целому
         (long signed int) путем размножения знака  влево.  Преобразование
         знаковых целых к плавающим величинам происходит без потери инфор-
         мации, за исключением потери некоторой точности, когда преобразу-
         ются величины long в float. При преобразовании знакового целого к
         беззнаковому  целому (unsigned int), знаковое целое преобразуется
         к размеру беззнакового целого и  результат  интерпретируется  как
         беззнаковая величина.
               Преобразование   знаковых   целых   типов   представлено  в
         Табл.5.2. В таблице предполагается, что типы char являются знако-
         выми по умолчанию. Если во время компиляции  используется  опция,
         которая изменяет умолчание типа char на безнаковый, то преобразо-
         вания типов unsigned char даны в табл.5.3.

         ! ---------------------------------------------------------!
         ! Из      В                          Метод                 !
         !                                                          !
         ! char   short                дополнение знаком            !
         ! char   long                 дополнение знаком            !
         ! char   unsigned char        сохранение битов; старший бит!
         !                             теряет функцию знакового бита!
         ! char   unsigned short       дополнение знаком до short,  !
         !                             преобразование short в       !
         !                             unsigned short               !

                                      - 90 -

         ! char   usigned long         дополнение знаком до long;   !
         !                             преобразование long в        !
         !                             unsigned long                !
         ! char   float                дополнение знаком до long;   !
         !                             преобразование long в float  !
         ! char   double               дополнение знаком до long;   !
         !                             преобразование long в double !
         ! short  char                 сохранение младшего байта    !
         ! short  long                 размножение знака            !
         ! short  unsigned char        сохранение битов; старший бит!
         !                             теряет функцию знакового бита!
         ! short  unsigned long        дополнение знаком до long;   !
         !                             преобразование long в        !
         !                             unsigned long                !
         ! short  float                дополнение знаком до long;   !
         !                             преобразование long к float  !
         ! short  double               дополнение знаком до long;   !
         !                             преобразование long в double !
         ! long   char                 сохранение младшего байта    !
         ! long   short                сохранение младшего слова    !
         ! long   unsigned char        сохранение младшего байта    !
         ! long   unsigned short       сохранение младшего слова    !
         ! long   unsigned long        сохранение всех битов;       !
         !                             старший бит теряет функцию   !
         !                             знакового бита               !
         ! long   float                представляется как float;    !
         !                             если long не может быть      !
         !                             представлено точно, то       !
         !                             происходит некоторая потеря  !
         !                             точности                     !
         ! long   double               представляется как double;   !
         !                             если  long не может быть     !
         !                             представлено точно как double!
         !                             происходит некоторая потеря  !
         !                             точности                     !
         !----------------------------------------------------------!

                 Табл.5.2. Преобразование знаковых целых типов

               Замечание:
               Тип int эквивалентен или short типу или типу long в зависи-
         мости  от  оборудования. Преобразование значений int производится
         как для short или long, в зависимости от того, что подходит.

               Преобразование беззнаковых целых типов.

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

                                      - 91 -

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

               Преобразование  беззнаковых  целых  типов  представлено   в
         табл.5.3.

                 Из            В                     Метод
           unsigned char   chart               сохраняются      все
                                               биты; старший бит
                                               становится знаковым
               unsigned char short             дополнение нулем
               unsigned char long              дополнение нулем
           unsigned char   unsigned short      дополнение нулем
           unsigned char   unsigned long       дополнение нулем
           unsigned char   float               преобразование к long,
                                               преобразование long
                                               в float
           unsigned char   double              преобразование к long,
                                               преобразование long к
                                               double
           unsigned short  char                сохранение младшего
                                               байта
           unsigned short  short               сохранение всех битов,
                                               старший бит становится
                                               знаковым
           unsigned short  long                дополнение нулем
           unsigned short  unsigned char       сохранение младшего
                                               байта
           unsigned short  unsigned long       дополнение нулем
           unsigned short  float               преобразование к long,
                                               преобразование long в
                                               float
           unsigned short  double              преобразование к long,
                                               преобразование long к
                                               double
           unsigned long   char                сохранение младшего
                                               байта
           unsigned long   short               сохранение младшего
                                               слова
           unsigned long   long                сохранение всех битов;
                                               старший бит становится
                                               знаковым
           unsigned long   unsigned char       сохранение младшего
                                               байта
           unsigned long   unigned short       сохранение младшего
                                               слова
           unsigned long   float               преобразование к long,
                                               преобразование long к
                                               float
           unsigned long   double              преобразование к long,
                                               преобразование long к
                                               double

              Табл.5.3. Преобразование беззнаковых целых типов

                                      - 92 -

               Замечание: Тип unsigned int эквивалентен или unsigned short,
         или unsigned long типам в зависимости от оборудования. Преобразо-
         вание  из  unsigned  int  производятся как для unsigned short или
         unsigned long в зависимости от того, что подходит.

               Преобразование плавающих типов.

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

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

               Преобразования плавающих типов сведены в табл.5.4.

              Из       В                     Метод

           float    char             преобразуется к long, long
                                     преобразуется к char
           float    short            преобразуется к long, long
                                     преобразуется в short
           float    long             усечение дробной части;
                                     результат неопределен, если
                                     он слишком велик для
                                     представления в long
           float    unsigned short   преобразуется к long, long
                                     преобразуется к unsigned
                                     short
           float    unsigned long    преобразуется к long, long
                                     преобразуется к unsigned
                                     long
           float    double           изменение внутреннего
                                     представления
           double   char             преобразование к float,
                                     преобразование float к char
           double   short            преобразование к float,
                                     преобразование float к short
           double   long             усечение дробной части;
                                     результат неопределен, если
                                     он слишком велик для
                                     представления в long
           double   unsigned short   преобразование к long,
                                     преобразование long к
                                     unsigned short
           double   unsigned long    преобразование к long,
                                     преобразование long к
                                     unsigned long
           double   float            представляется как float;
                                     если значение double не
                                     может быть точно

                                      - 93 -

                                     представлено как float, то
                                     точность теряется; если
                                     значение слишком велико для
                                     представления как float, то
                                     результат неопределен

               Преобразование адресных типов.

               Указатель на величину одного типа может быть преобразован к
         указателю на величину другого типа. Результат может быть, однако,
         неопределенным  из-за отличия в требованиях к выравниванию и раз-
         мерам памяти.
               В некоторых реализациях имеются специальные ключевые  слова
         near,  far,  huge, модифицирующие размер указателей в программах.
         Указатель может быть преобразован к  указателю  другого  размера;
         путь преобразования зависит от реализации. Например, на процессо-
         ре  8086 компилятор должен использовать значение регистра сегмен-
         та, чтобы преобразовать 16-разрядный указатель в  32-х  разрядный
         указатель. Смотрите системную документацию для получения информа-
         ции о преобразовани указателя.
               Значение указателя может быть преобразовано к целой величи-
         не.  Путь  преобразования  зависит от размера указателя и размера
         целого типа следующим образом:
               - если указатель имеет тот же самый или меньший размер, чем
         целый тип, то указатель преобразуется точно так же как  беззнако-
         вое целое, за исключением того, что он не может быть преобразован
         к плавающей величине;
               -  если  указатель меньше чем размер целого типа, то указа-
         тель вначале преобразуется к указателю с тем же  самым  размером,
         что  и целый тип, и затем преобразуется к целому типу. Метод пре-
         образования указателя к более длинному указателю зависит от  реа-
         лизации;  смотрите системную документацию чтобы получить информа-
         цию о таких преобразованиях.
               Целый тип может быть преобразован к  адресному  типу.  Если
         целый тип того же самого размера, что и адресный, то производится
         простое  преобразование  к  виду указателя (беззнакового целого).
         Если размер целого типа отличен от размера адресного типа, то це-
         лый тип вначале преобразуется к размеру указателя, используя  ме-
         тоды  преобразования  данных  в табл.5.2. и 5.3. Затем полученное
         значение представляется как указатель.
               Если поддерживаются специальные ключевые слова  near,  far,
         huge, то может быть сделано неявное преобразование адресных вели-
         чин.  В частности, компилятор может по умолчанию создавать указа-
         тели определенного размера и производить преобразования  получае-
         мых адресных величин, если в программе не появится forward обьяв-
         ление,  переопределяющее  это умолчание. Смотрите системную доку-
         ментацию для получения информации об адресных преобразованиях.

               Преобразования других типов.

               Из определения типа enum следует, что величины enum являют-

                                      - 94 -

         ся величинами типа int. Поэтому преобразования в и из  типа  enum
         осуществляется  как  для  int  типов.  Тип int эквивалентен типам
         short или long в зависимости от реализации.
               Не допустимы преобразования об"ектов типа структур и совме-
         щений.
               Тип void не имеет значения по определению.  Поэтому  он  не
         может  быть преобразован к любому другому типу, но любая величина
         может быть преобразована к типу void путем присваивания.  Тем  не
         менее,  величина  может  быть явно преобразована cast операцией к
         void, как это рассматривается в разделе 5.7.2.

               5.7.2. Преобразования type-cast

               Явное преобразование типа может  быть  сделано  посредством
         type-cast.  Преобразования type-cast имеют следующую синтаксичес-
         кую форму

               (,

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

               5.7.3. Преобразования, выполняемые операциями

               Преобразования, выполняемые операциями Си, зависят от самих
         операций и от типа операнда или операндов. Многие операции выпол-
         няют  "обычные  арифметические преобразования", которые описаны в
         разделе 5.3.1.
               Си разрешает некоторую арифметику с указателями. В адресной
         арифметике целые величины преобразуются  к  определенным  адресам
         памяти  (для  получения информации смотрите аддитивные операции в
         разделе 5.3.6. и индексные операции в разделе 5.2.5.).

               5.7.4. Преобразования при вызовах функций

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

                                      - 95 -

         цесс контроля типов подробно описан в разделе 7.4.1. "Фактические
         аргументы".
               Если forward об"явления не было, или если в forward  об"яв-
         лении опущен список типов аргументов, то над аргументами вызывае-
         мой функции производятся только обычные арифметические преобразо-
         вания.  Преобразования  производятся независимо для каждого аргу-
         мента вызова. Смысл этих преобразований сводится к тому, что  ве-
         личины  типа  float преобразуются к double, величины типов char и
         short  преобразуются  к  int,  величины  типов  unsigned  char  и
         unsigned  short преобразуются к unsigned int. Если поддерживаются
         специальные ключевые слова near, far, huge, то могут  быть  также
         сделаны  неявные  преобразования  адресных  величин, посылаемых в
         функцию. Эти неявные преобразования могут быть переопределены за-
         данием в forward об"явлении списка типов аргументов, что позволит
         компилятору выполнить контроль типов. Смотрите системную докумен-
         тацию о преобразованиях указателей.
.


                                      - 96 -

                                   6. ОПЕРАТОРЫ

               6.1. Введение

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

               continue
               do

               for
               goto
               if

               return
               switch
               while

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

               break
               default
               for
               return
               case
               dо
               goto
               switch
               continue
               else
               if
               while

               Выражениями в операторах Си являются выражения, описанные в
         разделе  5  "Выражения  и присваивания". Операторами, допустимыми
         внутри операторов Си, могут быть  любые  операторы,  описанные  в
         этом разделе. Оператор, который является компонентом другого опе-
         ратора,  называется  "телом"  включающего  оператора.Часто опера-
         тор-тело является составным оператором, состоящим из  одного  или
         более  операторов.  Составной  оператор  ограничивается фигурными
         скобками. Все другие операторы Си заканчиваются точкой с  запятой
         (;).
               Любой  из  операторов Си может быть спереди помечен меткой,
         состоящей из имени  и  двоеточия.  Операторные  метки  опознаются
         только  оператором  goto  и поэтому рассматриваются в разделе 6.8
         "goto и помеченные операторы".
               Порядок выполнения программы Си совпадает с порядком распо-
         ложения  операторов в тексте программы, за исключением тех случа-

                                      - 97 -

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

               6.2. Оператор break

               Синтаксис:
               breakk;

               Выполнение:
               Оператор  break  прерывает  выполнение  операторов do, for,
         switch или while, в которых он появляется. Управление  передается
         оператору,  следующему  за  прерванным. Появление оператора break
         вне операторов do, for, switch, while приводит к ошибке.
               Внутри вложенных операторов оператор break завершает только
         операторы do, for, switch или while.  Чтобы  передать  управление
         вне вложенной структуры, могут быть использованы операторы return
         и goto.

               Пример
                for (i=0; i]
                .
                .
                .

                []
                .
                .
                .
                }

                                      - 98 -

               Выполнение:
               Действия при выполнении составного оператора состоят в том,
         что выполнение его операторов осуществляется в порядке их появле-
         ния,  за исключением случаев, когда очередной оператор явно пере-
         дает управление в другое место.
               Синтаксические   правила   и   семантика    об"явлений    -
         ,  которые  могут  появиться  в заголовке составного
         оператора, описаны в разделе 4 "Об"явления".

                Пример
                if (i>0)  {
                     linne [i]=x;
                     x++;
                     i--;
                }

               Типично появление составного оператора в качестве тела дру-
         гого оператора, например, такого, как оператор if. В  приведенном
         примере,  если i больше нуля, то последовательно выполняются опе-
         ратры составного оператора.

               Помеченные операторы

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

               6.4. Оператор continue

               Синтаксис:

               continue;

               Выполнение:
               Оператор cоntinue передает управление на следующую итерацию
         в оператoрах цикла do, for, while, в которых он может  появиться.
         Оставшиеся  операторы  в теле   вышеперечисленных циклов при этом
         не выполняются. Внутри do или while циклов следующая итерация на-
         чинается с перевычисления выражения do или while операторов.  Для
         оператора  for  следующая  итерация  начинается с выражения цикла
         оператора for.

               Пример:
                while (i-->0)  {
                     x=f(i);
                     if (x==1)
                        continue;

                                      - 99 -

                     y=x*x;
                }

               Тело оператора выполняется , если i>0. Сначала f(i) присва-
         ивается x,  затем,  если  x  равно  1,  то  выполняется  оператор
         continue.  Остальные операторы тела игнорируются и выполнение во-
         зобновляется с заголовка цикла, т.е. вычисляется выражение i-->0.

               6.5. Оператор do

                Синтаксис:
                do

                while ();

               Выполнение:
               Тело оператора do выполняется один или несколько раз до тех
         пор, пока выражение   станет  ложным  (равным  нулю).
         Вначале  выполняется оператор  тела, затем вычисляется
         выражение . Если выражение ложно, то оператор do  за-
         вершается и управление передается следующему оператору в програм-
         ме.  Если  выражение  истинно  (не равно нулю), то тело оператора
         выполняется снова и снова проверяется выражение. Выполнение  тела
         оператора  продолжается  до  тех  пор,  пока  выражение не станет
         ложным. Оператор do может также завершить выполнение при выполне-
         нии операторов break, goto или return внутри тела оператора do.

               Пример:
                do {
                    y=f(x);
                    x--;
                } while (x>0);

               Вначале выполняются два оператора y=f(x); и x--; не обращая
         внимание на начальное значение  x.  Затем  вычисляется  выражение
         x>0. Если x>0, то тело оператора выполняется снова, и снова пере-
         вычисляется выражение x>0. Тело оператора выполняется до тех пор,
         пока x не станет меньше или равным нулю.

               6.6. Оператор-выражение

               Синтаксис:

               expression;

               Выполнение:
               Выражение   вычисляется в соответствии с прави-
         лами, изложенными в разделе 5 "Выражения и присваивания".

               Примеры
               x=(y+3);     / *example 1* /
               x++;         / *example 2* /
               f(x);        / *example 3* /

                                      - 100 -

               В Си присваивания являются выражениями. Значением выражения
         является  значение,   которое   присваивается   (так   называемое
         "право-сторонее значение").
               В первом примере x присваивается значение y+3.
               Во втором примере x инкрементируется.
               В  третьем примере показано выражение функционального вызо-
         ва.Значением выражения является значение, возвращаемое  функцией.
         Если  функция  возвращает  значение, то обычно оператор-выражение
         содержит операцию присваивания, чтобы запомнить значение возврата
         вызванной функции. Еси возвращаемая величина не используется, как
         в данном примере, вызов функции выполняется, но возвращаемая  ве-
         личина (если она есть) не используется.

               6.7. Оператор for

               Синтаксис:

         for ([];[];[])
                statement

               Тело  оператора  for  выполняется  нуль и более раз, до тех
         пор, пока условное выражение  не станет  ложным.
         Выражения     инициализации          и     цикла
          могут быть использованы для инициализации и мо-
         дификации величин во время выполнения оператора for.
               Первым шагом при выполнении оператора for является вычисле-
         ние выражения инициализации, если оно имеется.  Затем  вычисление
         условного выражения с тремя возможными результатами:
               1.  Если  условное  выражение  истинно  (не равно нулю), то
         выполняется тело оператора.  Затем  вычисляется  выражение  цикла
         (если оно есть). Процесс повторяется снова с вычислением условно-
         го выражения.
               2.  Если условное выражение опущено, то его значение прини-
         мается за истину и процесс выполнения продолжается, как  показано
         выше.  В  этом  случае  оператор for может завершиться только при
         выполнении в теле оператора операторов break, goto, return.
               3. Еси условное выражение ложно,  то  выполнение  оператора
         for  заканчивается и управление передается следующему оператору в
         программе.
               Оператор for может завершиться  при  выполнении  операторов
         break, return, goto в теле оператора.

               Пример
               for (i=space=tab=0; i;
               .
               .
               .
               :

               Выполнение:

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

               Пример
               if (errorcode>0)
                   goto exit;
               .
               .
               .
               exit:return (errorcode);

               В  примере  оператор  goto передает управление на оператор,
         помеченный меткой exit, когда происходит ошибка.

               Формат меток

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

               6.9. Оператор if

               Синтаксис:

               if ()

               [else

                                      - 102 -

                   ]

               Выполнение:
               Тело оператора if выполняется селективно, в зависимости  от
         значения  выражения  . Сначала вычисляется выражение.
         Если значение выражения истина (не нуль), то выполняется оператор
         . Если  выражение  ложно,  то  выполняется  оператор
         , непосредственно следующий за ключевым словом else.
         Если выражение  ложно и предложение else ... опущено,
         то  управление  передается на выполнение оператора, следующего за
         оператором if.

               Пример
               if (i>0)
                     y=x/i;

               else {
                     x=i;
                     y=f(x);
                }

               В примере выполняется оператор y=x/i;, если i больше  нуля.
         Если  i  меньше  или  равно  нулю,  то  значение  i присваивается
         переменной x и возврат функции f(x) присваивается переменной y.

               Вложения

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

               Примеры

               /****** example 1 ******/
               if (i>0) /* without braces */
                     if (j>i)
                          x=j;
                     else
                          x=i;

               /****** example 2 ******/
                if (i>0) {     /* with braces */
                     if (j>1)
                         x=j;
                }
                else
                    x=i;

               В первом примере else ассоциируется с внутренним оператором

                                      - 103 -

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

               6.10. Оператор null

               Синтаксис:
                   ;

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

               Пример
               for (i=0; i<10; linee [i++]=0)
                         ;

               Такие операторы, как do, for, if, while, требуют,  чтобы  в
         теле  оператора был хотя бы один оператор. Оператор null удовлет-
         воряет требованиям синтаксиса в случаях, когда не требуется  тела
         оператора.  В  приведенном примере третье выражение оператора for
         инициализирует первые 10 элементов массива line нулем. Тело  опе-
         ратора  включает  оператор  null, т.к. нет необходимости в других
         операторах.

               Помеченный оператор null

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

               6.11. Оператор return

               Синтаксис:

               return [];

               Выполнение:
               Оператор return заканчивает выполнение функции,  в  которой
         он  появляется, и возвращает управление в вызывающую функцию. Уп-
         равление передается в вызывающую функцию в точку, непосредственно
         следующую за вызовом. Значение выражения ,  если  оно
         есть,   возвращается   в   вызывающую   функцию.  Если  выражение
          опущено, то возвращаемая функцией величина не  опре-
         делена.

                                      - 104 -

               Пример
               main ()
               {
                       void draw (int,int);
                       long sq (int);

                       .
                       .
                       .
                       y=sq (x);
                       draw (x,y);
                       .
                       .
                       .
                }
                long sq (x)
                int x;
                {
                   return (x*x);
                }
                void draw (x,y)
                int x,y;
                {
                   .
                   .
                   .
                   return;
                }

               Функция  main  вызывает  две функции, sq и draw. Функция sq
         возвращает значение x*x в main. Величина  возврата  присваивается
         переменной y. Функция draw об"является как функция void и не воз-
         вращает значения. Попытка присвоить возвращаемое значение функции
         draw привело бы к ошибке.
               Выражение  оператора return заключено в скобки,
         как показано в примере. Язык не требует скобок.

               Отсутствие оператора return

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

               6.12. Оператор switch

               Синтаксис:

               switch () {
                     []
                     .
                     .

                                      - 105 -

                     .
                     [case :]

                     .
                     .
                     .
                          []
                          .
                          .
                          .
                     [default:
                              ]
                     [case :]
                     .
                     .
                     .
                          []
                          .
                          .
                          .
                }

               Выполнение:
               Оператор switch передает управление  одному  из  операторов
           своего  тела. Оператор, получающий управление, - это
         тот      оператор,      чье      case-константное       выражение
              равно     значению     switch-выражения
          в круглых скобках.
               Выполнение тела оператора начинается с выбранного оператора
         и продолжается до конца тела  или  до  тех  пор,  пока  очередной
         оператор  передает управление за пределы тела.
               Оператор  default выполнится, если case-константное выраже-
         ние    не  равно  значению  switch-выражения
         .  Если  default-оператор  опущен,  а соответствующий
         case  не  найден,  то  выполняемый   оператор   в   теле   switch
         отсутствует.  Switch-выражение   - это целая величина
         размера int или короче. Оно может быть также величиной типа enum.
         Если  короче чем int, оно расширяется до int.
               Каждое  case-константное  выражение
         преобразуется   к   типу   switch-выражения.   Значение   каждого
         case-константного выражения должно быть  уникальным  внутри  тела
         оператора.
               Case  и  default  метки в теле оператора switch существенны
         только при начальной проверке, когда определяется стартовая точка
         для выполнения тела оператора. Все операторы  появляющиеся  между
         стартовым  оператором и концом тела, выполняются, не обращая внима-
         ния на свои метки, если какой-то из операторов не передает управ-
         ления из тела оператора switch.
               В  заголовке составного оператора, формирующего тело опера-
         тора switch, могут появиться об"явления, но инициализаторы, вклю-
         ченные в об"явления, не  будут  выполнены.  Назначение  оператора
         switch  состоит  в том, чтобы передать управление непосредственно
         на выполняемый оператор внутри тела, обойдя строки,  которые  со-
         держат инициализацию.

                                      - 106 -

               Примеры:
               /***.....* example 1 *.....***/
               switch (c) {
                     case 'A':
                        capa++;
                     case 'a':
                        lettera++;
                     default:
                        total++;
                  }

               /***.....* example 2 *.....***/
               switch (i) {
                     case -1:
                             n++;
                             break;
                     case 0:
                             z++;
                             break;
                     case 1:
                             p++;
                             break;
                  }

               В первом примере все три оператора в теле switch выполняют-
         ся,  если c равно 'A'. Передача управления осуществляется на пер-
         вый оператор capa++, далее операторы  выполняются  в  порядке  их
         следования в теле.
               Если   c   равно   'a',   то  переменные  lettera  и  total
         инкрементируются. Наконец, если c не равно  ни  'A'  ни  'a',  то
         инкрементируется только переменная total.
               Во  втором  примере  в  теле switch после каждого оператора
         следует   оператор    break.    Оператор    break    осуществляет
         принудительный  выход  из  switch после выполнения одного из этих
         операторов. Последний оператор break  не  является  обязательным,
         поскольку  без  него управление было бы передано из тела на конец
         составного оператора, но он включен для единообразия.

               Множественные метки

               Оператор тела  switch  может  быть  помечен  множественными
         метками, как показано в нижеследующем примере:

               case 'a':
               case 'b':
               case 'c':
               case 'd':
               case 'e':
               case 'f': hexcvt (c);

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

                                      - 107 -

         операторы в блоке выполняются, не обращая внимания на свои метки.

               6.13. Оператор while

               Синтаксис:
               while ()

               Выполнение:
               Тело оператора while выполняется нуль или более раз до  тех
         пор,  пока  выражение    станет ложным (равным нулю).
         Вначале вычисляется выражение . Если  из-
         начально ложно, то тело оператора while не выполняется и управле-
         ние передается на следующий оператор программы. Если
         является истиной (не нуль), то выполняется тело оператора.  Перед
         каждым   следующим   выполнением   тела   оператора
         перевычисляется. Повторение выполнения тела оператора  происходит
         до  тех  пор, пока  остается истинным. Оператор while
         может также завершиться при выполнении  операторов  break,  goto,
         return внутри тела while.

               Пример
               while (i>=0) {
                      string1 [i] = string2 [i];
                      i--;
               }

               В  вышеприведенном  примере копируются символы из string2 в
         string1. Если i больше или равно  нулю,  то  string2[i]  присваи-
         вается индексной переменной string1[i] и i декрементируется. Ког-
         да  i  становится  меньше  нуля,то выполнение оператора while за-
         вершается.

                                    7. ФУНКЦИИ

               7.1. Введение

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

                                      - 108 -

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

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

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

               [][]
                                ([])
                                []

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

               7.2.1. Класс памяти

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

                                      - 109 -

               7.2.2. Тип возврата

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

               [] ,

               где  спецификатор типа  вместе с декларато-
         ром  определяет тип  возврата  и  имя  функции.  Если
           не  задан, то подразумевается, что тип возврата
         int. Спецификатор типа может специфицировать основной,  структур-
         ный и совмещающий типы. Декларатор состоит из идентификатора фун-
         кции, возможно модифицированного с целью об"явления адресного ти-
         па.  Функции не могут возвращать массивов или функций, но они мо-
         гут возвращать указатели на любой тип, включая массивы и функции.
         Тип возврата, задаваемый в определении функции, должен соответст-
         вовать типам возвратов, заданных в об"явлениях этой функции, сде-
         ланных где-то в программе. Функции с типом возврата int могут  не
         об"являться  перед вызовом. Функции с другими типами возвратов не
         могут быть вызваны прежде, чем они будут определены или  об"явле-
         ны.
               Тип  значения  возврата  функции используется только тогда,
         когда функция возвращает значение, которое  вырабатывается,  если
         выполняется  оператор return, содержащий выражение. Выражение вы-
         числяется, преобразуется к типу возврата, если это необходимо,  и
         возвращается  в точку вызова. Если оператор return не выполняется
         или если выполняемый оператор return не  содержит  выражения,  то
         значение возврата функции не определено. Если в этом случае вызы-
         вающая  функция ожидает значение возврата, то поведение программы
         также не определено.

               Примеры
               /*.....* example 1 *.....*/
                     /* return type is int */
               static add (x,y)
               int x,y
               {
                    return (x+y);
                }

               /*.....* example 2 *.....*/
               typedef struct {
                    char name [20];
                    int id;
                    long class;
               } STUDENT
                    /* return type is STUDENT */
                    STUDENT sortstu (a,b)
                    STUDENT a,b;
               {
                    return ((a.id0){
                      {
                      .
                      .
                      .
                      }
                }
                match (r,n)
                struct student *r;
                char *n;
                {
                     int i=0;
                     while (r->name [i] == n[i])
                            if (r->name [i++] == '\0')
                                   return (r->id);
                     return (0);
                }

               В   примере  содержатся  :  об"явление  структурного  типа,
         forward-об"явление  функции  match,  вызов  match  и  определение
         функции  match.  Заметим, что одно и то же имя student может быть
         использовано  без  противоречий  для  тела  структуры   и   имени
         структурной переменной.
               Функция  match  об"явлена с двумя аргументами. Первый аргу-
         мент - это указатель на структуру типа student, второй указа тель
         на об"ект типа char.
               У функции match два формальных параметра z и n.
               Параметр n об"явлен как указатель на об"ект типа char.
               Функция  вызывается  с  двумя  аргументами.  Оба  аргумента
         являются элементами переменной student структурного типа student.
               Поскольку  имеется  forward-об"явление  функци  match, ком-
         пилятор проверит соответствие типов действительных аргументов  со
         списками  типов  аргументов,  а затем действительных аргументов с
         формальными параметрами.
               Так  как  типы  соответствуют,  то  в  предупреждениях  или
         преобразованиях нет необходимости.
               Заметим,  что  имя  массива,  заданное  в  качестве второго
         аргумента в вызове функции преобразуется  к  указателю  на  char.
         Соответствующий  формальный параметр также об"явлен как указатель
         на char и используется в выражении как идентификатор массива. Так
         как идентификатор массива рассматривается как адресное выражение,

                                      - 113 -

         то результат об"явления формального параметра как char  *n  будет
         тем же самым, что и char n [].
               Внутри   функции  локальная  переменная  i  определяется  и
         используется  в  качестве  индекса  массива.  Функция  возвращает
         структурный  элемент  id, если элемент структуры name сравнился с
         массивом n, в противном случае функция возвращает нуль.

               7.2.4. Тело функции

               Тело функции - это  просто  составной  оператор.  Составной
         оператор содержит операторы, которые определяют действия функции,
         и  может  также  содержать  об"явления переменных, используемых в
         этих операторах. Информацию о составных операторах смотри в  раз-
         деле  6.3.  Все переменные, об"явленные в теле функции, имеют тип
         памяти auto,  если  они  не  об"явлены  иначе.  Когда  вызывается
         функция,  то создается память для локальных переменных и произво-
         дится их инициализация (если она задана).  Управление  передается
         первому  оператору  составного  оператора  и  начинается  процесс
         выполнения, который продолжается до тех пор, пока  не  встретится
         оператор  return  или  конец  тела  функции.  Управление при этом
         возвращается в точку вызова.
               Если функция возвращает значение, то должен  быть  выполнен
         оператор  return,  содержащий  выражение.  Значение  возврата  не
         определено, если не выполнен оператор return или  еслив  оператор
         return не было включено выражение.

               7.3. Об"явления функции

               Об"явление функции определяет имя, тип возврата и класс па-
         мяти данной функции и может задавать тип некоторых или всех аргу-
         ментов функции. Детальное описание синтаксиса об"явлений  функции
         дано  в  разделе  4.  Функции  могут  быть об"явлены неявно или в
         forward-об"явлениями. Тип возврата функции, об"явленный или неяв-
         но или в forward-об"явлении, должен соответствовать типу возврата
         в определении функции. Неявное об"явление имеет место всякий раз,
         когда функция вызывается без предварительного об"явления или  оп-
         ределения.  Си-компилятор  неявно  об"являет вызываемую функцию с
         типом возврата int. По умолчанию функция  об"является  с  классом
         памяти extern. Определение функции может переопределить класс па-
         мяти на static, обеспечив себе появление ниже об"явлений в том же
         самом исходном файле. Forward-об"явление функции устанавливает ее
         атрибуты,  позволяя вызывать об"явленную функцию перед ее опреде-
         лением или из другого исходного файла.
               Если   спецификатор   класса   памяти   static  задается  в
         forward-об"явлении,  то  функция  имеет  класс  static.   Поэтому
         определение  функции  должно  быть  также специфицировано классом
         памяти static.
               Если  задан   спецификатор   класса   памяти   extern   или
         спецификатор опущен, то функция имеет класс памяти extern. Однако
         определение  функции может переопределить класс памяти на static,
         обеспечив себе появление ниже об"явлений в том же самом  исходном
         файле
               Forward-об"явление имеет важные различные  применения.  Они

                                      - 114 -

         задают  тип  возврата  для  функций, которые возвращают любой тип
         значений, за исключением int. (Функции, которые возврщают  значе-
         ние  int,  могут также иметь forward-об"явления, но делать это не
         требуется).
               Если функция с типом возврата не int  вызывается  перед  ее
         определением   или   об"явлением,   то   результат   неопределен.
         Forward-об"явления могут быть использованы для задания типов  ар-
         гументов, ожидаемых в функциональном вызове.
               Список типов аргументов  forward-об"явления  задает  тип  и
         число   предполагаемых   аргументов.   (Число   аргументов  может
         меняться). Список типов  аргументов  -  это  список  имен  типов,
         соответствующих списку выражений в функциональном вызове.
               Если  список  типов аргументов не задан, то не производится
         контроль  типов.  Несоответствие  типов   между   действительными
         аргументами  и  формальными параметрами разрешено. Более детально
         контроль  типов  рассмотрен  в  разделе  7.4.1.   "Действительные
         аргументы".

               Пример:
                main ()
                {
                   int a=0, b=1;

                   float x=2.0, y=3.0;
                   double realadd (double,double);
                   a=intadd (a,b);
                   x=realadd (x,y);
                }
                intadd (a,b)
                int a,b;
                {
                   return (a+b);
                }
                double realadd (x,y)
                double x,y
                {
                   return (x+y);
                }

               В  примере функция intadd об"явлена неявно с возвратом типа
         int, т.к. она вызвана перед  своим  определением.  Компилятор  не
         проверит  типы аргументов в вызове, т.к. отсутствует список типов
         аргументов.
               Функция   realadd   возвращает   значение   типа    double.
         Forward-об"явление  realadd  в  функции  main  позволяет  вызвать
         realadd перед  ее  определением.  Заметим,  что  тип  возврата  в
         определении  (double)  соответствует  типу  возврата, заданному в
         forward-об"явлении. В forward-об"явлении  также  определены  типы
         двух  параметров  функции realadd. Типы действительных аргументов
         соответствуют  типам  заданным  в  forward-об"явлении   и   также
         соответствуют типам формальных параметров.

                                      - 115 -

               7.4. Вызовы функций

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

                ([])

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

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

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

               int (* fpointer)(int,int);

               Идентификатор  fpointer  указывает  на  функцию   с   двумя
         аргументами  типа  int  и  возвращающую  значение типа int. Вызов
         функции в этом случае будет выглядеть следующим образом:

               (* fpointer)(3,4);

               Здесь  используется  операция   разадресации   (*),   чтобы
         получить  адрес функции, на которую ссылается указатель fpointer.
         Адрес функции затем используется для ее вызова.

               Примеры:
                /*.....* example 1 *.....*/
                double *realcomp (double,double);
                double a,b,*rp;

                                      - 116 -

                .
                .
                .
                rp=realcomp (a,b);

                          /*.....* example 2 *.....*/
                main ()
                {
                     long lift(int), step(int), drop(int);
                     void work (int, long (*) (int));
                     int select, count;
                     .
                     .
                     .
                     select=1;
                     switch (select)  {
                            case 1: work (count, lift);
                                    break;
                            case 2: work (count, step);
                                    break;
                            case 3: work (count, drop);

                            default:
                                    break;

                     }
                }

                void work (n, func)
                int n;
                long (*func)(int);
                {
                     int i;
                     long j;

                     for (i=j=0; i
               # define ()

               Директива  #  define  заменяет все вхождения идентификатора
          в  исходной  программе  на  ,  определенный  в
         директиве  справа  от  идентификатора.  Идентификатор заменяется,
         если он оформлен в виде лексемы. Например, идентификатор  не  из-
         меняется,  если  он представлен внутри строки или как часть более
         длинного идентификатора. Если после идентификатора следует список
         параметров , то директива # define заменяет  каж-
         дое  вхождение  выражения ,
         модифицированный заменой формальных аргументов фактическими.
                представляет собой набор лексем, таких как  ключевые
         слова,   константы   или  составные  операторы.  Один  или  более
         пробельных символов могут разделять  от    (или
         от  заключенных  в скобки параметров). Если текст больше чем одна
         строка,  то  он  может  быть  продолжен   на   следующей   строке
         посредством  печати  символа новой строки с последующей наклонной
         чертой влево.
                может быть опущен. В этом случае  все  представители
         идентификатора    будут  удалены  из исходного текста
         программы. Тем не менее,  рассматривается как опреде-
         ленный и принимает значение 1, если  проверяется  директивой  #if
         (смотри раздел 8.4.1).
               Когда  задан  список  параметров  ,  то  он
         содержит  один  или  более  формальных  параметров,   разделенных
         запятыми.  Каждое  имя  в  списке должно быть уникальным и список
         должен быть заключен в круглые  скобки.  Не  допускаются  пробелы
         между     и  открывающей  скобкой.  Имена  формальных
         параметров в тексте    отмечают  места,  куда  должны  быть
         подставлены   фактические   значения.   Каждое   имя  формального
         параметра может появиться в тексте  более  одного  раза  в  любом
         порядке.
               Фактические   аргументы,   следующие   непосредственно   за
         идентификатором    в  исходном  файле,  соответствуют
         формальным  параметрам  списка  параметров  и мо-

                                      - 122 -

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

               Примеры:
                /*.....* example 1 *.....*/
                #define WIDTH    80
                #define LENGTH   (WIDTH + 10)

                /*.....* example 2 *.....*/
                #define FILEMESSAGE "Attempt too create file \
                failed because of insufficient space"

                /*.....* example 3 *.....*/
                #define REG1     register
                #define REG2     register
                #define REG3

                /*.....* example 4 *.....*/
                #define MAX(x,y)    ((x) > (y)) ? (x) : (y)

                /*.....* example 5 *.....*/
                #define MULT (a,b)  ((a) * (b))

               В  первом  примере  определяется  идентификатор  WIDTH, как
         целая константа 80,  и  определяется  идентификатор  LENGTH,  как
         (WIDTH + 10). Каждое вхождение LENGTH заменяется на (WIDTH + 10),
         которое  в свою очередь заменяется на выражение (80 + 10). Скобки
         являются  важными,  поскольку  они  управляют  интерпретацией   в
         операторах, подобных следующему:

               var = LENGTH * 20;

               После препроцессирования оператор будет таким:

               var = (80 + 10) * 20;

               Значение,  которое  присваивается,  равно  1800. Без скобок
         значение 80+10*20 равнялось бы 280.
               Во втором примере определяется  идентификатор  FILEMESSAGE.
         Определение  продолжается  на  вторую  строку путем использования
         символа "\".
               В  третьем  примере  определены  три  идентификатора, REG1,
         REG2, REG3. REG1 и REG2 определены как ключевые  слова  register.
         Определение  REG3  опущено и, таким образом, любое вхождение REG3
         будет удалено из исходного файла. Эти директивы  могут  быть  ис-
         пользованы  для того, чтобы обеспечить наиболее важным переменным
         программы  (заданным  с  REG1  и  REG2)  задание  класса   памяти
         register. В разделе 8.4.1 дана расширенная версия этого примера.
               В  четвертом  примере  дано макроопределение, поименованное

                                      - 123 -

         MAX. Каждое текущее вхождение макро-вызова MAX в  исходном  файле
         заменяется выражением ((x)>(y))?(x):(y), в котором формальные па-
         раметры x и y заменяются на фактические. Например, вхождение
                MAX(1,2)
         заменяется на
                ((1)>(2)?(1):(2),
         а вхождение
                MAX (i, s[i])
         заменяется на
                ((i)>(s[i]))?(i):(s[i])
               Макро-вызов  проще  читать,  чем соответствующее выражение,
         которое подставляется. Исходная программа  становится  проще  для
         понимания.
               Заметим,  что  в этом макро аргументы с побочными эффектами
         могут быть причиной непредсказуемых результатов.
               Например, макро-вызов MAX (i,s[i++]) заменится на

               ((i)>(s[i++]))?(i):(s[i++])

               Выражение s[i++] вычисляется дважды.  Результат  тернарного
         выражения  неопределен, т.к. его операторы могут быть вычислены в
         любом  порядке,  а  значение  переменной  i  зависит  от  порядка
         вычисления.
               В   пятом   примере   определяется  макро  с  именем  MULT.
         Макровызов MULT (3,5) в тексте программы заменяется  на  (3)*(5).
         Круглые  скобки,  в  которые заключаются  фактические  параметры,
         важны,   поскольку   они   управляют   интерпретацией   составных
         аргументов.  Например,  макровызов  MULT  (3+4,5+6)  заменится на
         (3+4)*(5+6), что эквивалентно 76. Без скобок результат подстанов-
         ки 3+4*5+6 равен 29.

               8.1.2. Директива #undef

               Синтаксис:

               #undef

               Директива #undef отменяет текущее определение #define иден-
         тификатора . Чтобы отменить макроопределение посредс-
         твом директивы #undef, достаточно задать его идентификатор. Зада-
         ние списка параметров не требуется.
               Директива #undef может  быть  применена  к  идентификатору,
         который ранее не определен. Это дополнительная гарантия того, что
         идентификатор не определен.
               Директива  #undef обычно используется с директивой #define,
         чтобы создать область исходной программы, в которой идентификатор
         имеет специальный смысл. Директива #undef  используется  также  с
         директивой  #if  (смотри раздел 8.4.1) для управления сравнениями
         участков исходной программы.

               Пример:

                #define WIDTH         80
                #define ADD(X,Y)      (X)+(Y)

                                      - 124 -

                .
                .
                .
                #undef WIDTH
                #undef ADD

               В этом примере директива #undef отменяет определение поиме-
         нованной  константы и макроса. Заметим, что в директивах задаются
         только идентификатор и имя макроса.

               8.2. #include файлы

               Синтаксис:

               #include ""
               #include <>

               Директива #include добавляет содержимое  заданного  include
         файла к другому файлу. Определения констант и макросов могут быть
         организованы  в  "include"  файлах и добавлены к любому исходному
         файлу #include директивой.  "include"  файлы  также  полезны  для
         об"явлений  общих  внешних  переменных  и составных типов данных.
         Типы, которые требуется об"явить  и  поименовать  однажды,  также
         создаются в #include-файлах.
               Директива #include сообщает препроцессору об обработке фай-
         ла,  как если бы этот файл появился в исходной программе в точке,
         где записана директива. Обработанный текст также может  содержать
         директивы  препроцессора. Препроцессор выполняет директивы из но-
         вого текста, а затем продолжает  процессирование  первоначального
         текста исходного файла.
               Имя  файла    -  это  имя  файла с предшествующей
         спецификацией директория. Синтаксис спецификации файла зависит от
         специфики операционной системы, в которой компилируется  програм-
         ма.
               Препроцессор использует концепцию "стандартного" директория
         или  директориев  для  поиска #include-файлов. Расположение стан-
         дартных директориев для #include-файлов зависит от  реализации  и
         операционной   системы.   Смотрите   системную  документацию  для
         получения информации о стандартных директориях.

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

                                      - 125 -

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

                Примеры:

               #include  /* example 1 */
               #include "defs.h" /* example 2 */

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

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

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

               8.3.1. Директивы #if, #elif, #else, #endif

               Синтаксис:

                #if
                   []
                [#elif ]
                [*elif ]
                .
                .
                .
                [#else ]
                #endif

               Директива  #if  вместе с директивами #elif, #else и #endiif
         управляет компиляцией частей исходного  файла.  Каждой  директиве
         #if в исходном файле должна соответствовать закрывающая директива
         #endif. Между директивами #if и #endif допускается нуль или более
         директив #elif и не более одной директивы #else. Директива #else,
         если  она  есть,  должна  быть  расположена непосредственно перед

                                      - 126 -

         директивой #endif.
               Препроцессор выбирает один из  участков  текста-  для
         дальнейшей   обработки.  Участок    -  это  любая  последо-
         вательность текста. Он может занимать более одной строки.  Обычно
         это участок программного текста, который  имеет  смысл  для  ком-
         пилятора  или препроцессора. Однако, это не обязательное требова-
         ние. Препроцессор можно использовать для обработки любого текста.
               Выбранный текст обрабатывается препроцессором и  посылается
         на  компиляцию.  Если  содержит директивы препроцессора, то
         эти директивы выполняются.
               Любой участок  текста,  не  выбранный  препроцессором,  иг-
         норируется на стадии препроцессирования и впоследствии  не компи-
         лируется.
               Препроцессор  выбирает  отдельный участок текста  на основе
         вычисления       ограниченного       константного       выражения
         ,  следующего  за  каждой #if или
         #elif директивой, пока не будет найдено  выражение  со  значением
         истина  (не  нуль).  Выбирается  ,  следующий  за  истинным
         константным выражением, до ближайшего знака номера (#).
               Если ограниченное константное  выражение  не  истинно   или
         отсутствует  директива  #elif,  то  препроцессор  выбирает
         после записи  #else.  Если  запись  #else  опущена,  а  выражение
         директивы #if ложно, то текст не выбирается.

               Ограниченное              константное             выражение
          описано в разделе 5.2.10.  Такое
         выражение  не  может содержать sizeof выражений, кастовых выраже-
         ний,  перечислимых  констант,  но  может  содержать   специальные
         константные  выражения  defined  ().  Это константное
         выражение истинно, если  заданный  идентификатор    в
         текущий  момент  определен,  в  противном случае выражение ложно.
         Идентификатор  ,  определенный   как   пустой  текст,
         рассматривается как определенный.
               Директивы  #if, #elif, #else, #endif могут быть вложенными.
         Каждая из вложенных директив #else, #elif, #endif  принадлежит  к
         ближайшей предшествующей директиве #if.

               Примеры:

               /*.....* example 1 *.....*/
                #if defined (CREDIT)
                    credit ();
                #elif defined (DEBIT)
                    debit ();
                #else
                    printerror ();
                #endif

                /*.....* example 2 *.....*/
                #if DLEVEL>5
                    #define SIGNAL  1
                    #if STACKUSE == 1
                                    #define STACK  200
                    #else
                                    #define STACK  100

                                      - 127 -

                    #endif
                #else
                    #define SIGNAL  0
                    #if STACKUSE == 1
                                    #define STACK  100
                    #else
                                    #define STACK  50
                    #endif
                #endif

                /*.....* example 3 *.....*/
                #if DLEVEL == 0
                    #define STACK 0
                #elif DLEVEL == 1
                    #define STACK 100
                #elif DLEVEL > 5
                    display ( debugptr );
                #else

                    #define STACK 200
                #endif

                /*.....* example 4 *.....*/
                #define REG1  register
                #define REG2  register

                #if defined(M_86)
                    #define REG3
                    #define REG4
                    #define REG5
                #else
                    #define REG3   register
                    #if defined(M_68000)
                            #define REG4   register
                            #define REG5   register
                    #endif
                #endif

               В  первом  примере  директивы  #if,  #endif  управляют ком-
         пиляцией одним из трех вызовов функций. Вызов функции credit ком-
         пилируется, если идентификатор CREDIT определен.  Если  определен
         идентификатор DEBIT, то компилируется функциональный вызов debit.
         Если  ни  один идентификатор не определен, то компилируется вызов
         printerror.  Заметим,  что  CREDIT  и  credit  -  это   различные
         идентификаторы в Си.
               В  следующих  двух  примерах  предполагается, что константа
         DLEVEL предварительно определена.
               Во втором примере показаны две последовательности вложенных
         #if, #else, #endif директив. Первая  последовательность  директив
         обрабатывается, если DLEVEL >5. В противном случае обрабатывается
         вторая последовательность.
               В третьем  примере  используются  директивы  #elif,  #else,
         чтобы  сделать  один  из  четырех выборов, основанных на значении
         константы DLEVEL. Здесь определяется константа  STACK  равной  0,
         100  или 200, в зависимости от значения DLEVEL. Если DLEVEL>5, то

                                      - 128 -

         компилируется вызов функции display (debugptr), а константа STACK
         не определяется.
               В четвертом примере  директивы  препроцессора  используются
         для  управления  об"явлениями  спецификатора  регистровой  памяти
         register в переносимом исходном файле.  Если  программа  содержит
         больше  об"явлений  переменных  класа  памяти register, чем может
         предоставить машина, то компилятор не об"явит  лишние  переменные
         как  регистровые.  REG1  и  REG2  определяются как ключевые слова
         register, чтобы об"явить регистровую  память  для  двух  наиболее
         важных  переменных  в  программе. Например, в следующем фрагменте
         переменные b и c имеют больший приоритет, чем a или d.

                func (a)
                REG3 int a;
                {
                   REG1 int b;
                   REG2 int c;
                   REG4 int d;
                   .
                   .
                   .
                   }

               Когда определен идентификатор  М_86,  препроцессор  удаляет
         идентификаторы  REG3  и  REG4 из файла путем замены его на пустой
         текст. Регистровую память в этом случае получат переменные b и c.
         Когда определен идентификатор М_68000, то все  четыре  переменные
         об"являются с классом памяти register.
               Когда  не  определены  оба идентификатора, то об"являются с
         регистровой памятью три переменные a,b и c.

               8.3.2. Директивы #ifdef и #ifndef

               Синтаксис:

               #ifdef
               #ifndef

               Директивы #ifdef и #ifndef выполняют те  же  самые  задачи,
         что  и  директива  #if,  использующая  defined(). Эти
         директивы  могут  быть  использованы  там  же,  где  используется
         директива  #if, и используются исключительно для компактности за-
         писи.
               Когда препроцессор обрабатывает  директиву  ifdef,  то  де-
         лается  проверка  идентификатора    на истинность (не
         нуль).
               Директива  #ifdef  является  отрицанием  директивы  #ifdef.
         Другими  словами, если  не определен (или его опреде-
         ление отменено директивой #undef), то его  значение  истинно  (не
         нуль). В противном случае значение ложно (нуль).

                                      - 129 -

               8.4. Управление нумерацией строк

               Синтаксис:

               #line ["filename"]

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

                Примеры:

               /*.....* example 1 *.....*/
                #line 151 "copy.c"

                /*..... example 2 *.....*/
                #define ASSERT(cond)      if(!cond)\
                {printf("assertion error line %d, file(%s)\n", \
                __LINE, __FILE);}else;

               В первом примере номер строки устанавливается равным 151  и
         имя файла изменяется на copy.c.
               Во  втором  примере  в макроопределении ASSERT используются
         предопределенные идщентификаторы __LINE__ и __FILE__  для  печати
         сообщения об ошибке, содержащего координаты исходного файла, если
         заданное  "утверждение"  ложно.  Заметим, что двойные кавычки при
         задании предопределенных идентификаторов не требуются.


www.dstu0752.narod.ru

dstu0752@yandex.ru

ICQ UIN 8107923