Опубликован: 01.03.2016 | Доступ: свободный | Студентов: 584 / 62 | Длительность: 03:55:00
Лекция 5:

Команды ассемблера

Кто-то скажет: а ещё есть цикл for()! Но цикл

for(init; cond; incr)
{
  body;
}
эквивалентен такой конструкции:
init;
while(cond)
{
  body;
  incr;
}

Таким образом, нам достаточно и уже рассмотренных двух видов циклов.

Логическая арифметика

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

 and   источник, приёмник
 or    источник, приёмник
 xor   источник, приёмник
 not   операнд
 test  операнд_1, операнд_2

Команды and, or и xor ведут себя так же, как и операторы языка Си &, |, ^. Эти команды устанавливают флаги согласно результату.

Команда not инвертирует каждый бит операнда (изменяет на противоположный), так же как и оператор языка Си \sim.

Команда test выполняет побитовое И над операндами, как и команда and, но, в отличие от неё, операнды не изменяет, а только устанавливает флаги. Её также называют командой логического сравнения, потому что с её помощью удобно проверять, установлены ли определённые биты. Например, так:

        testb $0b00001000, %al  /* установлен ли 3-й (с нуля) бит?   */
        je    not_set
        /* нужные биты установлены */
not_set:
        /* биты не установлены */

Обратите внимание на запись константы в двоичной системе счисления: используется префикс 0b.

Команду test можно применять для сравнения значения регистра с нулём:

        testl %eax, %eax
        je    is_zero
        /* %eax != 0 */
is_zero:
        /* %eax == 0 */

Intel Optimization Manual рекомендует использовать test вместо cmp для сравнения регистра с нулём3Intel® 64 and IA-32 Architectures Optimization Reference Manual, 3.5.1.7 Compares.

Ещё следует упомянуть об одном трюке с xor. Как вы знаете, a XOR a = 0. Пользуясь этой особенностью, xor часто применяют для обнуления регистров:

xorl  %eax, %eax
/* теперь %eax == 0 */

Почему применяют xor вместо mov? Команда xor короче, а значит, занимает меньше места в процессорном кэше, меньше времени тратится на декодирование, и программа выполняется быстрее. Но эта команда устанавливает флаги. Поэтому, если вам нужно сохранить состояние флагов, применяйте mov4Intel® 64 and IA-32 Architectures Optimization Reference Manual, 3.5.1.6 Clearing Registers and Dependency Breaking Idioms.

Иногда для обнуления регистра применяют команду sub. Помните, она тоже устанавливает флаги.

subl  %eax, %eax
/* теперь %eax == 0 */

К логическим командам также можно отнести команды сдвигов:

 /* Shift Arithmetic Left/SHift logical Left */
 sal/shl количество_сдвигов, назначение

 /* SHift logical Right */
 shr     количество_сдвигов, назначение 

 /* Shift Arithmetic Right */
 sar     количество_сдвигов, назначение

количество_сдвигов может быть задано непосредственным значением или находиться в регистре %cl. Учитываются только младшие 5 бит регистра %cl, так что количество сдвигов может варьироваться в пределах от 0 до 31.

Принцип работы команды shl:

                              До сдвига:
          +---+     +----------------------------------+
          | ? |     | 10001000100010001000100010001011 |
          +---+     +----------------------------------+
         Флаг CF    Операнд
 
                       Сдвиг влево на 1 бит:
          +---+     +----------------------------------+
          | 1 |  <-- | 00010001000100010001000100010110 |  <-- 0
          +---+     +----------------------------------+
         Флаг CF    Операнд
 
                      Сдвиг влево на 3 бита:
 +----+   +---+     +----------------------------------+
 | 10 |   | 0 |  <-- | 01000100010001000100010001011000 |  <-- 000
 +----+   +---+     +----------------------------------+
Улетели  Флаг CF    Операнд
в никуда

Принцип работы команды shr:

                  До сдвига:
        +----------------------------------+     +---+
        | 10001000100010001000100010001011 |     | ? |
        +----------------------------------+     +---+
        Операнд                                  Флаг CF

          Логический сдвиг вправо на 1 бит:
        +----------------------------------+     +---+
  0 -- > | 01000100010001000100010001000101 | -- > | 1 |
        +----------------------------------+     +---+
        Операнд                                 Флаг CF

          Логический сдвиг вправо на 3 бита:
        +----------------------------------+     +---+  +----+
000 -- > | 00010001000100010001000100010001 | -- > | 0 |  | 11 |
        +----------------------------------+     +---+  +----+
        Операнд                                 Флаг CF Улетели
                                                        в никуда

Эти две команды называются командами логического сдвига, потому что они работают с операндом как с массивом бит. Каждый "выдвигаемый " бит попадает в флаг cf, причём с другой стороны операнда "вдвигается " бит 0. Таким образом, в флаге cf оказывается самый последний "выдвинутый " бит. Такое поведение вполне допустимо для работы с беззнаковыми числами, но числа со знаком будут обработаны неверно из-за того, что знаковый бит может быть потерян.

Для работы с числами со знаком существуют команды арифметического сдвига. Команды shl и sal выполняют полностью идентичные действия, так как при сдвиге влево знаковый бит не теряется (расширение знакового бита влево становится новым знаковым битом). Для сдвига вправо применяется команда sar. Она "вдвигает " слева знаковый бит исходного значения, таким образом сохраняя знак числа:

                  До сдвига:
        +----------------------------------+     +---+
        | 10001000100010001000100010001011 |     | ? |
        +----------------------------------+     +---+
        Операнд                                  Флаг CF
        старший бит равен 1 == >
          == > значение отрицательное == >
          == >  "вдвинуть " бит 1 ---+
                                  |
  +-------------------------------+
  |
  V       Арифметический сдвиг вправо на 1 бит:
        +----------------------------------+     +---+
  1 -- > | 11000100010001000100010001000101 | -- > | 1 |
        +----------------------------------+     +---+
        Операнд                                 Флаг CF

          Арифметический сдвиг вправо на 3 бита:
        +----------------------------------+     +---+  +----+
111 -- > | 11110001000100010001000100010001 | -- > | 0 |  | 11 |
        +----------------------------------+     +---+  +----+
        Операнд                                 Флаг CF Улетели
                                                        в никуда

Многие программисты Си знают об умножении и делении на степени двойки (2, 4, 8…) при помощи сдвигов. Этот трюк отлично работает и в ассемблере, используйте его для оптимизации.

Кроме сдвигов обычных, существуют циклические сдвиги:

 /* ROtate Right */
 ror   количество_сдвигов, назначение

 /* ROtate Left */
 rol   количество_сдвигов, назначение

Объясню на примере циклического сдвига влево на три бита: три старших ( "левых ") бита "выдвигаются " из регистра влево и "вдвигаются " в него справа. При этом в флаг cfзаписывается самый последний "выдвинутый " бит.

Принцип работы команды rol:

                              До сдвига:
      +---+         +----------------------------------+
      | ? |         | 10001000100010001000100010001011 |
      +---+         +----------------------------------+
     Флаг CF        Операнд
 
                      Циклический сдвиг влево на 1 бит:
      +---+  1    1 +----------------------------------+
      | 1 |  <--+--- | 00010001000100010001000100010111 | ---+
      +---+    |    +----------------------------------+    |
     Флаг CF   V    Операнд                                 ^
               |                                            |
               +------------------- >--- >--- >----------------+
                1
 
                      Циклический сдвиг влево на 3 бита:
      +---+  0  100 +----------------------------------+
      | 0 |  <--+--- | 01000100010001000100010001011100 | ---+
      +---+    |    +----------------------------------+    |
     Флаг CF   V    Операнд                                 ^
               |                                            |
               +------------------- >--- >--- >----------------+
                100

Принцип работы команды ror:

                  До сдвига:
        +----------------------------------+         +---+
        | 10001000100010001000100010001011 |         | ? |
        +----------------------------------+         +---+
        Операнд                                     Флаг CF

          Циклический сдвиг вправо на 1 бит:
        +----------------------------------+ 1    1  +---+
   +--- | 11000100010001000100010001000101 | ---+-- > | 1 |
   |    +----------------------------------+    |    +---+
   ^    Операнд                                 V   Флаг CF
   |                                            |
   +------------------- <--- <--- <----------------+
                                               1

          Циклический сдвиг вправо на 3 бита:
        +----------------------------------+ 011  0  +---+
   +--- | 01110001000100010001000100010001 | ---+-- > | 0 |
   |    +----------------------------------+    |    +---+
   ^    Операнд                                 V   Флаг CF
   |                                            |
   +------------------- <--- <--- <----------------+
                                             011

Существует ещё один вид сдвигов - циклический сдвиг через флаг cf. Эти команды рассматривают флаг cf как продолжение операнда.

 /* Rotate through Carry Right */
 rcr   количество_сдвигов, назначение 

 /* Rotate through Carry Left */
 rcl   количество_сдвигов, назначение

Принцип работы команды rcl:

                              До сдвига:
         +---+      +----------------------------------+
         | X |      | 10001000100010001000100010001011 |
         +---+      +----------------------------------+
        Флаг CF     Операнд
 
                      Циклический сдвиг влево через CF на 1 бит:
      X  +---+      +----------------------------------+
    +- <- | 1 |  <--- | 0001000100010001000100010001011X | ---+
    |    +---+      +----------------------------------+    |
    V   Флаг CF     Операнд                                 ^
    |                                                       |
    +------------------------------ >--- >--- >----------------+
 
                      Циклический сдвиг влево через CF на 3 бита:
     X10 +---+      +----------------------------------+
    +- <- | 0 |  <--- | 01000100010001000100010001011X10 | ---+
    |    +---+      +----------------------------------+    |
    V   Флаг CF     Операнд                                 ^
    |                                                       |
    +------------------------------ >--- >--- >----------------+

Принцип работы команды rcr:

                   До сдвига:
         +----------------------------------+      +---+
         | 10001000100010001000100010001011 |      | X |
         +----------------------------------+      +---+
         Операнд                                  Флаг CF
 
           Циклический сдвиг вправо через CF на 1 бит:
         +----------------------------------+      +---+  X
    +--- | X1000100010001000100010001000101 | --- > | 1 | - >-+
    |    +----------------------------------+      +---+    |
    ^    Операнд                                  Флаг CF   V
    |                                                       |
    +------------------- <--- <--- <---------------------------+
 
           Циклический сдвиг вправо через CF на 3 бита:
         +----------------------------------+      +---+ 11X
    +--- | 11X10001000100010001000100010001 | --- > | 0 | - >-+
    |    +----------------------------------+      +---+    |
    ^    Операнд                                  Флаг CF   V
    |                                                       |
    +------------------- <--- <--- <---------------------------+

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

int main()
{
  int a = 0x11223344;
  int shift_count = 8;

  a = (a  < < shift_count) | (a  > > (32 - shift_count));

  printf( "%x\n ", a);
  return 0;
}