ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы.



 

Часть 4

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


?????? ???????????