Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

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

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
2 Мб
Скачать
6.3. Программирование нелинейных алгоритмов 261
В тридцатидвухбитном режиме, в частности, номеру1соответствует вы-
sys_exit(),2—sys_fork(),3—sys_read(),4—sys_write()
зов
64,72
функция ядра имеет свой набор параметров [ граммы вызовом
sys_exit()(eax =1)вebx
должен находиться код завершения
]. Так, при завершении про-
и т. д. Каждая
программы. Если требуется передать от семи параметров и выше, из них формиру­ется структура, адрес которой передаётся в ebx.
В шестидесятичетырёхбитном режиме номера иные, чем для тридцатидвух-
битного ядра [
71,72]:0—sys_read(),1—sys_write(),2—sys_open(),3
sys_close() ит.д.
6.3. Программирование нелинейных алгоритмов
То есть — происходящее ясно, но не поддаётся линейному описанию.
Б. Б. Гребенщиков. Козебар Мат
В языках высокого уровня существуют операторы цикла и условные операторы,
используемые для реализации нелинейных алгоритмов. Система машинных команд предлагает только команды условного и безусловного перехода (хотя в наборе
loop
команд x86 есть команда к тому же не рекомендуется к использованию из-за медленной работы).
Тем не менее, с помощью команд перехода можно реализовать все нелинейные
алгоритмические конструкции, а режим косвенной адресации позволяет обра­щаться к элементам составных структур данных. Для визуализации нелинейных алгоритмов воспользуемся схемами программы [31].
, она не является полноценным аналогом цикла,
6.3.1. Условие с операторами в одной ветви
Сл´ова — нет, есть движения в пространстве и его части — точек, площадей.
В. Хлебников. Зангези
Рассмотрим задачу, где в зависимости от некоторого условия требуется выпол-
нить либо не выполнить некоторое действие.
Пусть необходимо заменить значение целой знаковой переменнойxнулём, если
оно отрицательно.
x =
x, x  0 0,x<0
На языке C++ код, решающий эту задачу, будет использовать условный опера-
тор (листинг 6.24).
Листинг 6.24. Условие (6.1), C++
(6.1)
262 Глава 6. Программирование на языке Ассемблера
1 if (x < 0) 2 { 3 x=0; 4 }
Так как условный оператор включает только ветвь «да», а в ней — только
простой оператор присваивания, эту конкретную задачу можно решить с помощью команд условной пересылки. Начиная с Pentium Pro, это наиболее быстрый способ присваивания по условию.
Кроме того, можно воспользоваться командой условного перехода для обхода
кода ветви «да» в случае, когда условие не выполнено. Этот способ не только поддерживается начиная с оригинального 8086, но и универсален. Так можно реализовать условный оператор со сколь угодно сложным кодом внутри.
Условная пересылка
Сравнимxс нулём (оценим знак разности
x<0
выполнить в случае, когда less) или, что то же самое,
nge
как знаковое. Это соответствует условиюl(if
(if not greater or equal). С учётом размера типа
x 0
). Присваивание необходимо
int
можно также добавить суффикс размера l (листинг 6.25).
Листинг 6.25. Условие (6.1), cmovCC
1 int x = -6; 2 asm 3 ( 4 "cmpl␣$0,␣%[X]\n" 5 "cmovngel␣%[Zero],␣%[X]\n" 6 : [X]"+r"(x) 7 : [Zero]"rm"(0) 8 : "cc" 9 );
Так как источником для команды входной параметр
[Zero]
, расположенный в памяти или регистре и равный нулю.
cmovCC
не может быть константа, вводится
Обход части операторов с помощью команд условного перехода
Построим схему алгоритма (рис. 6.7, а) для решения этой задачи. В отличие от операторовifбольшинства языков высокого уровня, ветвь, где
должны выполняться операторы, лучше сделать ветвью «нет» (таким образом, по ветви «да» будет выполняться обход части программы).
Построим по данной схеме программу (рис. 6.7, б). Основная вертикаль схемы
будет соответствовать последовательности команд в памяти, потоки, отходящие
6.3. Программирование нелинейных алгоритмов 263
Начало
x 0
нет
x =0
Конец
а) б)
1 intx=-6; 2 asm 3 (
да
4 "cmpl␣$0,␣%[X]\n" 5 "jge␣end_check\n"
6 "movl␣$0,␣%[X]\n"
7 "end_check:\n"
8 : [X]"+rm"(x) 9 : : "cc"
10 );
Рис. 6.7. Алгоритм и реализация ветвления с операторами в одной
ветви
от вертикали — командам передачи управления, потоки, входящие в основную вертикаль — меткам, на которые передаётся управление. Если команда не явля­ется командой передачи управления, после неё выполняется непосредственно следующая, что соответствует движению вниз по основной вертикали.
Каждому блоку процесса здесь будет соответствовать линейный фрагмент
кода (строка 6); блок решения включает установку флагов и завершится условным переходом по метке (строки 4-5); точка соединения двух потоков соответствует метке на строке 7.
Подобным способом можно реализовать условный оператор со сколь угодно
объёмным кодом только в одной ветви. Необходимый код помещается на место строки 7, между командой условного обхода ветви и соответствующей меткой.
6.3.2. Условие с операторами в двух ветвях
Как на тенеписи, числаборцы пройдут перед вами, снятые в разных сечениях времени, в разных плоскостях времени.
В. Хлебников. Зангези
Рассмотрим задачу, где в зависимости от некоторого условия требуется выпол-
нить либо одно действие, либо другое.
Пусть в зависимости от условия необходимо выполнить один из двух операто-
ров:
y(x)=
1,x 0 0,x<0
(6.2)
264 Глава 6. Программирование на языке Ассемблера
На языке C++ код, решающий эту задачу, будет использовать условный опера-
тор с двумя ветвями (листинг 6.26).
Листинг 6.26. Условие (6.2), C++
1 if (x >= 0) 2 { 3 y=1; 4 } 5 else 6 { 7 y=0; 8 }
Из-за специфического кода ветвей данную задачу можно решить на ассемблере тремя способами.
Условная установка байта
Так какyравен либо нулю, либо единице, задачу можно решить с помощью
команды условной установки байта.
Единичное значениеyсоответствует случаю, когда
x 0
как знаковое (if
greater or equal), то есть мнемоника условия может быть записана как ge.
Листинг 6.27. Условие (6.2), setCC
1 int x = -7, y; 2 asm 3 ( 4 "cmpl␣$0,␣%[X]\n" 5 "movl␣$0,␣%[Y]\n" 6 "setgeb␣%[Y]\n" 7 :[Y]"=m"(y) 8 :[X]"rm"(x) 9 : "cc"
10 );
Здесь мы записываем ноль в четырёхбайтовую переменнуюy, находящуюся в памя­ти (это не изменяет флагов), а затем устанавливаем по условию её младший байт (так как платформа x86 использует порядок байтов Intel, адрес младшего байта
y
совпадает с адресомy). Оба возможных значенияyнеотрицательны, поэтому дополнение нулями является корректным расширением и для знаковой, и для беззнаковой их интерпретации.
Также можно было использовать для хранения параметра
[Y]
регистрA, уста-
новить по условию байтalи специальными командами расширить его вначале до
6.3. Программирование нелинейных алгоритмов 265
ax
, а затем до четырёхбайтового
eax
. После завершения вставки значение
eax
(параметра [Y]) будет скопировано в переменную y.
При использовании дляyоднобайтового типа
char
вместо четырёхбайтового
int расширение не будет нужным.
Условная пересылка
Данную конкретную задачу также можно решить с помощью команд услов­ной пересылки. Такая реализация так же компактна, как листинг 6.27, и гораздо компактнее и быстрее универсальной реализации ветвления при помощи команд условного перехода.
Листинг 6.28. Условие (6.2), cmovCC
1 int x = 10, y; 2 asm 3 ( 4 "cmpl␣$0,␣%[X]\n" 5 "movl␣$1,␣%[Y]\n" 6 "cmovll␣%[Zero],␣%[Y]\n" 7 : [Y]"+r"(y) 8 : [X]"rm"(x), [Zero]"rm"(0) 9 : "cc"
10 );
Вначалеxсравнивается с нулём, затемyинициализируется единицей (что не влияет на флаги), затем значение y заменяется нулём в случае x<0.
Обход части операторов с помощью команд условного перехода
Построим схему алгоритма (рис. 6.8, а) для решения этой задачи и затем выстроим блоки линейно вдоль одной вертикали (рис. 6.8, б).
Схема на рис. 6.8, б) не вполне соответствует ЕСПД (стандарт предписывает минимизировать количество изломов и не удлинять линии потоков без необходимо­сти, так как схемы алгоритмов предназначены в основном для чтения человеком) но при таком расположении можно однозначно сопоставить расположение блоков на схеме и расположение команд в памяти.
Сопоставим каждому блоку рис. 6.8, б) одну или несколько команд ассемблера (рис. 6.9). В этом случае излом потока (отход от базовой вертикали без ветвления) будет соответствовать безусловному переходу, блок решения включает вычис­ление условия и условный переход, а точки входа боковых потоков-переходов в основной вертикальный поток соответствуют меткам (рис. 6.9, б).
266 Глава 6. Программирование на языке Ассемблера
Начало
нет
y =0
Начало
x 0
да
y =1
Конец
а) б)
Рис. 6.8. Алгоритм ветвления
Начало
x 0
нет
y =0
y =1
1 int x = -6, y; 2 asm 3 (
да
4 "cmpl␣$0,␣%[X]\n" 5 "jge␣setting_1\n"
6 "movl␣$0,␣%[Y]\n"
7 "jmp␣end_check\n" 8 "setting_1:\n"
9 "movl␣$1,␣%[Y]\n"
x 0
нет
y =0
y =1
Конец
да
10 "end_check:\n"
Конец
11 :[Y]"=rm"(y) 12 :[X]"rm" (x) 13 : "cc"
14 );
а) б)
Рис. 6.9. Алгоритм и реализация ветвления
У схемы на рис. 6.8, а) два возможных варианта «вертикализации» — изоб-
ражённый на рис. 6.8, б) и тот, где ветвь «да» окажется выше ветви «нет». Эти
6.3. Программирование нелинейных алгоритмов 267
варианты равноправны и оба приведут к одинаково корректным, но различным между собой программам. В данном разделе рассматривается вариант рис. 6.8, б).
Соответственно, и в программе код ветви «нет» (строка 6) окажется выше кода
ветви «да» (строка 9).
В самом начале условного фрагмента вычисляется условие ветвления (стро­ка 4). После него следует условный обход ближайшей ветви, для рис. 6.9 — про­пуск ветви «нет», если условие
x 0
верно (ge, строка 5). Затем следует код ветви «нет» (строка 6; управление на неё передаётся естественным путём, если условие неверно). Чтобы после ветви «нет» выполнение естественным путём не перешло к ветви «да», в конце вставлена команда безусловного перехода к коду, следующему за условным фрагментом (переход к метке end_check, строка 7).
Затем следует метка начала ветви «да» и код самой ветви «да» (строки 8-9);
после его окончания управление естественным путём переходит к
end_check
которая завершает условный фрагмент (строка 10).
Описанный способ наиболее универсален и позволяет реализовать условный оператор со сколь угодно объёмным кодом ветвей. Необходимый код ветвей «нет» и «да» заместит соответственно строки6и9.
6.3.3. Цикл
Это войска пехотные Эм размололи глыбу объёма невозможного, камень-дикарь невозможного на муку
<...> — и целое стало мукой бесконечно малых частей.
В. Хлебников. Зангези
,
Цикл — единственная алгоритмическая конструкция высокого уровня, позво­ляющая передать управление назад. Соответственно, цикл на ассемблере можно реализовать только с помощью команд передачи управления.
Пусть необходимо найти сумму двоичных цифр беззнакового числа x.
Цикл с предусловием
Построим схему алгоритма для решения этой задачи, не используя парный блок цикла и выстроив блоки линейно вдоль одной вертикали (рис. 6.10, а), реализован цикл с предусловием).
При соединении блоков ещё немного отступим от требований ЕСПД и отделим точку, где боковой поток случая
CF =0
входит в основную вертикаль, от точки, где поток отходит от вертикали, чтобы перейти назад к началу итерации. Распо­ложение этих точек выберем так, чтобы поток основной вертикали на каждом её участке шёл в естественном направлении — сверху вниз (это не только позволяет не рисовать стрелку для обозначения направления, но и соответствует выполнению кода, не включающего команд передачи управления).
268 Глава 6. Программирование на языке Ассемблера
Начало
sum =0
x =0
нет
CF = x &1 x =(x>>1)
CF =1
да
sum = sum +1
Конец
а) б)
да
нет
1 int x = 7, sum; 2 asm 3 (
4 "xorl␣%[Sum],␣%[Sum]\n"
5 "begin_iteration:\n"
6 "cmpl␣$0,␣%[X]\n" 7 "je␣end_loop\n"
8 "shrl␣$1,␣%[X]\n"
9 "jnc␣end_iteration\n"
10 "incl␣%[Sum]\n"
11 "end_iteration:\n" 12 "jmp␣begin_iteration\n"
13 "end_loop:\n" 14 :[Sum]"=rm"(sum),[X]"+rm"(x) 15 : : "cc" 16 );
Рис. 6.10. Алгоритм и реализация цикла с предусловием
Теперь сопоставим каждому блоку одну или несколько команд ассемблера.
точки входа боковых потоков в основной вертикальный — меткам (рис. 6.10, б).
На рис. 6.10, б) в строках 1-4 представлена инициализация перед циклом, в строке 5 — метка начала итерации, строки 6-7 — вычисление и проверка усло­вия выхода из цикла. Строки 8-11 представляют тело цикла. Строка 12 — воз­врат управления после окончания итерации назад (к предусловию выхода). Метка в строке 13 показывает начало неповторяющихся действий после выхода из цикла; в строках 14-16 представлены действия после цикла. Искомая сумма двоичных цифр x накапливается в переменной sum.
Цикл с постусловием
Построим схему алгоритма, где условие анализируется в конце итерации цикла (рис. 6.11).
6.3. Программирование нелинейных алгоритмов 269
1 intx=7,sum; 2 asm 3 (
4 "xorl␣%[Sum],␣%[Sum]\n"
5 "begin_iteration:\n"
6 "shrl␣$1,␣%[X]\n"
нет
7 "jnc␣end_iteration\n"
8 "incl␣%[Sum]\n"
9 "end_iteration:\n"
10 "cmpl␣$0,␣%[X]\n"
11 "jne␣begin_iteration\n"
12 :[Sum]"=rm"(sum),[X]"+rm"(x) 13 : : "cc"
14 );
CF = x &1 x =(x>>1)
sum = sum +1
нет
Начало
sum =0
CF =1
да
x =0
да
Конец
а) б)
Рис. 6.11. Алгоритм и реализация цикла с постусловием
Здесь как схема, так и код компактнее, чем для цикла с предусловием, так как анализ условия цикла и возврат управления назад совмещены (строки 10-11 и соответствующий блок решения). Так как условие проверяется после итерации, тело цикла, независимо от начального значения условия выхода, выполнится хотя бы один раз.
Цикл с параметром можно реализовать либо как цикл с предусловием, как и в C/C++, либо как цикл с постусловием.
При этом, если счётчик цикла не является одновременно индексом массива в памяти, имеет смысл инициализировать счётчик максимальным значением
и уменьшать его на каждом шаге
i
max
i = i 1
. Это позволит избавиться от
i =
команды сравнения, так как команды декремента и вычитания не только изменяют операндi, но и выставляют флаги. При переходе от ненулевого значения к получим к отрицательному —
ZF =1
, при переходе от неотрицательного значения, в частности нуля,
CF =1
(при использовании вычитания, но не декремента)
и SF = OF .
0
270 Глава 6. Программирование на языке Ассемблера
6.4. Взаимодействие со структурами данных
Мы закрываем шапкой-невидимкой глаза и уши, чтобы иметь возможность отрицать самое существование чудовищ.
К. Маркс. Капитал
Языки высокого уровня скрывают расположение сложных структур данных в памяти, предоставляя взамен такие абстракции, как массивы, структуры, объ­екты, а также высокоуровневые операторы для обращения к их компонентам. Для обращения к компонентам сложных структур данных из ассемблерного кода необходимо уметь вычислять их адреса, а для этого необходимо представлять реализацию той или иной сложной структуры в одномерном пространстве памяти.
6.4.1. Массивы
Эм — распыление объёма на бесконечно малые части.
В. Хлебников. Зангези
Массив — структура данных в виде набора однородных компонент (элементов массива), расположенных в памяти непосредственно друг за другом (независимо от настроек выравнивания). Каждый элемент характеризуется своим номером — индексом. Элемент с наименьшим индексом располагается по младшему адресу.
В языке C++ наименьший индекс массива всегда равен0, а имя массива явля-
i
ется константным указателем на его начало. Таким образом, адрес массиваMравен
M + i · size
,где
size
— размер одного элемента и, в случае,
когда длина массива не равна нулю, может быть определён как sizeof(M[0]).
Для большинства простых типов (
float,double,size_t,ptrdiff_t,void*
char,bool,short,int,long,long long
) размер как на 32-, так и на 64-битной
платформе равен 1, 2, 4 или 8.
Соответственно, в ассемблере для получения элемента массива (если элементы массива имеют размер нент эффективного адреса
1, 2, 4
или8) будут использованы три из четырёх компо-
displacement(base, index, multiplier)
(адрес начала массива M ), индекс и масштаб (размер элемента).
В частности, адресi-го элемента массиваMиз чисел типа и многих 64-разрядных платформах равен как
(M, i, 4)
. При этом базаMи индексiдолжны быть 32-разрядны (на соот-
M + i · 4
int
, и элемент будет записан
ветствующей платформе) и располагаться в регистрах.
-го элемента
— база
на 32-разрядных
,
Листинг 6.29. Инициализация четырёхбайтового элемента массива M[i]
1 const int N = 8; 2 int M[N], i = 0;
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]