Практикум для изучения дисциплины «Основы программирования»



Pdf көрінісі
бет67/81
Дата08.07.2020
өлшемі1,55 Mb.
#74978
түріПрактикум
1   ...   63   64   65   66   67   68   69   70   ...   81
Байланысты:
А.А. Тюгашев

ЗАМЕЧАНИЕ 
Логический сдвиг влево и вправо позволяет выполнить быстрое умножение и 
деление  беззнакового  целого  числа  на  делители,  представляющие  собой 
степени числа 2, не требующее накладных расходов команды div. 
Для  подобного  умножения  и  деления  чисел  со  знаком  можно  применять 
специально  предусмотренные  в  системе  команд  ассемблера  x86 
арифметические  сдвиги:  sal —  арифметический  сдвиг  влево,  sar — 
арифметический сдвиг вправо (рис. 22). 
 
Рис. 22 
Особенность  циклических  сдвигов  заключается  в  том,  что  разряды 
двигаются  по  кольцу.  Причем  существует  две  разновидности  сдвигов  в 
зависимости от использования флага процессора CF: rol — циклический 
сдвиг влево, ror — циклический сдвиг вправо (рис. 23). 
 
 
 
 


132 
 
 
Рис. 23 
В  команде  rol,  как  показано  на  рисунке,  бывший  самым  левым  бит 
заносится в качестве правого (младшего) и одновременно копируется в CF. 
Команды сдвига через перенос rcl — циклический сдвиг влево и rcr — 
циклический сдвиг вправо работают следующим образом (рис. 24). 
 
Рис. 24 
Поскольку  речь  зашла  о  флагах  процессора,  стоит  рассмотреть  их 
подробнее. Обычно они используются при выполнении команд условного 
перехода. В процессорах x86 используются следующие основные флаги: 
 
ZF
 — 
флаг нуля, устанавливается в единицу, если результат равен 0; 
 
SF
 — 
флаг положительного/отрицательного значения, устанавливается, 
если результат меньше 0; 
 
CF
 — 
бит переноса из старшего разряда результатов; 
 
OF
 — 
флаг  переполнения,  устанавливается,  если  результат  не 
поместился в разрядную сетку ЭВМ; 
 
PF
 — 
флаг  четности,  устанавливается,  если  в  8  младших  байтах 
результата содержится четное количество двоичных единиц. 
Значения  флагов  определяются  результатом  предыдущей  выполненной 
команды  преобразования  данных.  Кроме  этого,  сформировать  флаги 
можно с помощью специальной команды сравнения операндов cmp: 
cmp 
ax,bx; сравнение АХ и ВХ 
В зависимости от флагов можно выполнить условный переход к метке. В 
языке ассемблера x86 существует богатый набор команд переходов, часть 
из  которых  приведены  далее,  причем  допустимы  две  формы 
мнемонической  записи  (указаны  через  дробь),  что,  вообще  говоря, —  не 
лучший подход с точки зрения соблюдения стиля программирования: 
 
JE/JZ
 — 
переход по нулю (JE — переход по равенству после cmp); 
 
JNE/JNZ
 — 
если не ноль (не равны аргументы предыдущей cmp); 
 
JG/JNLE
 — 
переход, если больше для знаковых; 
 
JGE/JNL
 — 
переход, если больше или равно для знаковых; 
 
JAE/JNBE
 — 
переход, если больше или равно для беззнаковых; 
 
 
 
 


133 
 
 
JC/JNAE
 — 
переход, если бит 
СF установлен в единицу. 
Переходы  необходимы  для  изменения  линейного  порядка  выполнения 
действий  в  программе.  Для  этого  место  в  программе,  куда  передается 
управление  (с  которого  продолжается  выполнение  программы),  должно 
быть  помечено.  Рассмотрим  пример  нахождения  максимума  среди  чисел 
без знака: 
; максимум среди чисел X и Y → в ячейку Z 
 
mov 
ax,X; берем в AX значение числа из ячейки с именем X 
 
cmp 
ax,Y; сравнение — формирование флагов 
 
jae 
M; переход к метке М если >= 
 
mov 
ax,Y; 
M: mov 
z,ax   
Помимо  команд  условного  перехода  в  программах  на  языке  ассемблера 
используются  безусловный  переход  и  вызов  подпрограммы  с  возвратом. 
Команда безусловного перехода записывается так: 
jmp 
M1; продолжить с метки M1 
Если  требуется,  например,  в  целях  информационной  безопасности 
максимально затруднить понимание логики программы, например, при ее 
дизассемблировании 
злоумышленниками, 
можно 
насытить 
ее 
беспорядочными переходами с помощью jmp. 
При  исполнении  команды  вызова  подпрограммы  call  происходит 
следующее.  В системный  стек  заносится  текущее  значение  регистра — 
счетчика  команд  процессора  (IP —  Instruction  Pointer)  и  управление 
передается  по  указанному  в  команде  адресу  (метке).  По  окончании 
подпрограммы  в  ней  должна  встретиться  команда  ret,  при  выполнении 
которой  производится  обратное  действие —  извлекается  значение  с 
вершины стека и управление передается в вызвавшую программу: 
; вызов подпрограммы pecat 
call  pecat 
; сюда возвращается управление после исполнения подпрограммы 
; …  
 
; где-то в другом месте исходного текста 
; подпрограмма печати — комментарий необходим 
pecat:; … действия  
 
; действия 
 
ret ; возврат из подпрограммы 
Отметим,  что  требование  хорошего  стиля  программирования 
подразумевает, как и при написании программ на языках высокого уровня, 
что  следует  снабжать  подпрограмму  комментарием,  раскрывающим 


134 
 
выполняемые ею действия. 
Система  команд  процессоров  x86  довольно  богата —  в  ней  имеются 
специальные  команды  для  организации  циклов.  Рассмотрим  пример  с 
использованием команды loop. Пусть некий фрагмент программы нужно 
повторить N раз. Реализовать это можно, например, следующим образом: 
 
mov 
cx,N; регистр CX — счетчик, аналог переменной цикла 
L: 
;начало тела цикла 
 
;… действия цикла 
 
;…  
 
;…  
 
dec 
cx; CX=CX-1 
 
cmp 
cx,0; CX=0? 
 
jne 
L; CX еще не ноль — возврат к метке L 
Как  видите,  в  конце  цикла  используется  стандартная  тройка  команд.  Ее 
можно  заменить  одной —  loop!  Переписать  программу  в  этом  случае 
можно следующим образом: 
 
mov 
cx,N; регистр CX — счетчик, аналог переменной цикла 
L: 
;начало тела цикла 
 
;… действия цикла 
 
;…  
 
;…  
 
loop 
L; цикл, использует регистр CX по умолчанию 
Видно, что современные ассемблеры и архитектуры ЭВМ предоставляют 
довольно гибкие возможности — главное, умело ими пользоваться. 
Контрольные вопросы 
1. 
К какому поколению языков программирования относится ассемблер? 
2. 
В  чем  разница  между  ассемблером  и  автокодом?  Откуда  произошло 
название «автокод»? 
3. 
Как вы оцениваете перспективы ассемблера в XXI веке? 
4. 
Каковы  области  применения  ассемблера  в  современных  сложных 
программных проектах? 
5. 
Как работает команда xchg? Удобно ли использовать ее на ассемблере 
и  как  она  могла  бы  повлиять  на  языки  программирования  высокого 
уровня? 
6. 
В  чем  заключается  разница  между  арифметическим  и  логическим 
сдвигами? 
7. 
Что такое машинное слово? 


135 
 
8. 
В чем заключается механизм работы индексной адресации? 
9. 
Какие разновидности машинных команд существуют? 
10. 
Как работают команды условного перехода? Зачем они нужны? 
11. 
Для чего применяется команда loop? 
Сумма нечетных на ассемблере 
Приведенной ранее информации достаточно для того, чтобы приступить к 
написанию  программы  суммирования  нечетных  элементов  числовой 
последовательности, проходящей красной нитью через настоящую книгу, 
на языке ассемблера x86. Будем пользоваться бесплатно распространяемой 
версией Flat Assembler. Рассмотрим возможный вариант решения: 
; программа подсчета суммы нечетных в массиве chisla 
include 'win32ax.inc' 
 
.data 
chisla  dw 1,2,3,4,5,0,0,0,0,0 
N   
dw 10 
 
.code 
start:  ; 
начальная метка программы, в конце должнj быть .end start 
        mov esi,chisla 
        mov cx
,[N] ; количество элементов в таблице 
        
mov dx,0 ; в dx будем накапливать сумму нечетных, сначала 
обнуляем 
l1:     mov ax,[esi]
; очередное число заносим в АX 
        test 
ax,1 ; проверяем на нечетность – младший бит на 1 
        jz  l2 
; если младший бит двоичного числа равен нулю – 
четное! 
        
add dx,ax ; прибавляем очередной нечетный элемент 
l2:     add esi,2 
        loop 
l1 ; цикл по счетчику cx, пока не дойдет до нуля 
        
; вызываем outint для вывода целого значения — результата 
        mov ax,dx 
        call outint 
        invoke  ExitProcess,0 
Данная  программа  предполагает,  что  у  нас  есть  возможность  вывода  на 
экран результата — мы формируем его в регистре процессора AX. Первая 
строка — комментарий,  который, как и  положено, описывает  суть  нашей 
программы.  Строка  с  include  нужна,  чтобы  мы  смогли  использовать  в 
программе  системный  вызов  invoke  ExitProcess,0  для  корректного 


136 
 
завершения. Директива .data FlatAssembler указывает на начало сегмента 
описания данных в программе, директива .code — начало исполняемого 
блока команд. 
В  секции  описания  данных  у    находится  директива  chisla  dw 
1,2,3,4,5,0,0,0,0,0
,  с  помощью  которой  мы  заносим  в  память 
набор обрабатываемых чисел. В следующей строке — описание ячейки N, 
содержащей размер массива, в нашем случае 10. 
Правила  FlatAssembler  подразумевают,  что  начало  исполняемой 
программы  снабжается  меткой  start  (последней  строчкой  программы 
должна  в  этом  случае  быть  директива  .end  start).  Итак,  вначале  в 
регистр  ESI  заносится  адрес  начала  обрабатываемого  массива  в  памяти. 
Регистр  ESI —  так  называемая  расширенная  (32-разрядная)  версия 
регистра  процессора  SI,  ее  использование  в  данном  случае  обусловлено 
использованием современного компьютера. В следующей строке заносим в 
регистр  CX,  используемый  как  счетчик,  количество  чисел.  Квадратные 
скобки  необходимы,  чтобы  указать  на  необходимость  взять  в  качестве 
аргумента  число  по  адресу,  а  не  сам  адрес  ячейки, —  косвенную 
адресацию.  Затем  мы  обнуляем  регистр  DX —  в  нем  будем  накапливать 
сумму нечетных чисел. 
Со следующей строки начинается тело цикла, поэтому она имеет метку l1. 
В  цикле  мы  первым  делом  извлекаем  из  памяти  по  адресу,  на  который 
указывает ESI, очередное число (косвенную адресацию дают квадратные 
скобки)  и  заносим  его  в  регистр  AX.  Проверка  нечетности  числа 
осуществляется  путем  вполне  уместного  при  программировании  на 
ассемблере  использования  низкоуровневых  возможностей —  команды 
битового  тестирования  по  маске  test  ax,1.  Вспомним,  что  данные 
хранятся в памяти ЭВМ в двоичном виде в позиционном коде. Младший 
разряд числа отвечает при этом за степень 2
0
 
и равен нулю в случае, если 
число  делится  без  остатка  на  2,  и  единице —  в  противном  случае. 
Соответственно,  для  проверки  на  нечетность  нет  необходимости 
выполнять  операцию  деления  на  2.  Достаточно  проверить  содержимое 
младшего  бита  (маска  1).  Следующая  команда  условного  перехода  jz 
обеспечивает переход к метке l2 в случае, если результат нулевой (число 
четное),  с  помощью  чего  производится  обход  команд  программы, 
производящих суммирование.  
Следующая  операция,  которая  выполняется  либо  после  суммирования 
естественным путем, либо после перехода по команде jz l2, — переход к 
следующему  числу  в  памяти.  Для  этого  мы  увеличиваем  на  2  значение 
регистра  ESI.  Увеличение  на  2  связано  с  тем,  что  мы  храним 
обрабатываемые  числа  в  машинных  словах,  а  минимальная  единица 


137 
 
адресации  процессора  x86 — 1  байт.  В  случае  если  счетчик  не  дошел до 
нуля,  с  помощью  специальной  команды  цикла  loop    выполняется 
переход к повтору цикла с метки l1. 
После  выполнения  программы  сумма  нечетных  чисел  будет  накоплена  в 
регистре DX. Нам было бы интересно получить это значение на экране. К 
сожалению,  в  ассемблере  нет  удобных  средств,  подобных  writeln  в 
Паскале  или  cout  <<  в  С++,  позволяющих  печатать  значения 
производного  типа.  Flat  Assembler  предоставляет  системный  вызов 
MessageBox
,  отображающий  на  экране  окно  сообщения,  но  в  качестве 
аргументов  он  требует  строку.  Поэтому  нам  необходимо  преобразовать 
число —  сумму  нечетных —  в  строку,  иными  словами,  в  его  запись  с 


Достарыңызбен бөлісу:
1   ...   63   64   65   66   67   68   69   70   ...   81




©engime.org 2024
әкімшілігінің қараңыз

    Басты бет