Семинары по курсу «Архитектура ЭВМ и язык ассемблера» учебно-методическое пособие. Часть 2. - Е.А. Кузьменкова_ В.А. Падарян_ М.А. Соловьев (1110589), страница 2
Текст из файла (страница 2)
От того, насколько успешно решаются эти задачи, зависит длительность отладки кода и достигаемая программой производительность.Пример 1-1 Циклический сдвиг в массивеДан статический массив.enum { BUF_SIZE = 256 };static int buf[BUF_SIZE];Требуется привести фрагменты программ на языке Си и на языке ассемблера, которые осуществляют следующее преобразование массива: выполняется циклический сдвиг подмножества элементов массива, расположенных на расстоянии в 3элемента друг от друга, начиная с элемента с индексом 1.РешениеВ первую очередь приведем реализацию на языке Си.int previous = buf[1];for (int i = 4; i < 256; i += 3) {int tmp = buf[i];buf[i] = previous;previous = tmp;}buf[1] = previous;int i = 253;while (i >= 4) {int tmp;tmp = buf[i];buf[i] = buf[i - 3];buf[i - 3] = tmp;i -= 3;}Вариант «А»Вариант «Б»7Вариант «А» реализует обход элементов массива в прямом порядке – от младшихиндексов к старшим.
Если в теле цикла сразу выполнить присваиваниеbuf[i] = buf[i-3]; то текущее значение элемента buf[i] будет безвозвратно потеряно. На следующей итерации в присваивании будет участвовать значение не предыдущего элемента, а того, чье значение присваивалось на предыдущей итерации.В результате весь массив будет заполнен значением, взятым из элемента buf[1].Чтоб избежать этого, введены вспомогательные переменные previous и tmp. Перваясодержит в себе значение предыдущего элемента массива до присваивания.
Вторая (tmp) используется для того, чтоб это значение не потерялось при присваивании. Относительной простой такой реализации является простота условия окончания цикла – нельзя выходить за пределы массива.Вариант «Б» предполагает обход массива в обратном направлении, что позволяетполучить более элегантное решение. Особенно заметны отличия вариантов науровне ассемблерного кода. Идея заключается в том, что на каждой итерации элементы обмениваются своими значениями. Относительной сложностью становитсяопределение наибольшего индекса, участвующего в обмене. Обмен начинается совторого элемента (с индексом 1).
Индексы удовлетворяют условию 0 3 n 1 256, где n – целое число. Поскольку 255 делится на 3 нацело, наибольший индекс – 253.Два последних элемента массива в обмене не участвуют, а третий с конца – участвует.В результате обменов значение из buf[253] перемещается в buf[1], остальные значения сдвигаются по массиву в конец, как и требовалось в условии задачи.Имея реализацию на языке Си, переведем код на язык ассемблера.8;;;;Вариант «А»распределяем регистрыecx – int ieax – int tmpedx – int previousmovmovedx, dword [buf + 4]ecx, 16.loop:cmp ecx, 1024jge .loop_endmov eax, dword [buf + 4 * ecx]mov dword [buf + 4 * ecx], edxmov edx, eaxadd ecx, 12jmp .loop.loop_end:mov dword [buf + 4], edx; previous = buf[1];; i = 4; не забываем;sizeof(int);относится к;арифметике.;это регистр;;;;;;;умножать навсе то, чтоадреснойВ данном случае –ecxi v 256if (i >= 256) goto .loop_end;tmp = buf[i];buf[i] = previous;previous = tmp;i += 3;переходим на следующую итерацию цикла; buf[1] = previous;Вариант «Б»; распределяем регистры; ecx – int i; eax – вспомогательный регистр при обмене значениями между элементами;массива buf[i] и buf[i - 3]mov ecx, 1012; i = 253;.loop:mov eax, dword [buf + 4 * ecx]; инструкция xchg позволяет сократитьxchg eax, dword [buf + 4 * ecx - 12] ; количество инструкций при обменеmov dword [buf + 4 * ecx], eax; значениямиsub ecx, 12; i -= 3;cmp ecx, 16; i v 4jge .loop; if (i >= 4) goto .loop;; поскольку заранее известен размер массива, проверку можно перенести в; конец тела цикла, избавившись от инструкции безусловной передачи управленияКомпактное кодирование цикловИсторически в наборе команд IA-32 присутствует поддержка цикла do-while в видекоманд LOOP, LOOPNE, LOOPE, JCXZ/JECXZ.
Они выполняют условные переходы, исходяиз состояния регистра ECX (CX), который используется в качестве счетчика итераций.Выполнение команды LOOP/LOOPcc уменьшает значение ECX на единицу, после чегоделается условный переход, если выполняется соответствующее условие. Если ECXизначально быть равен 0, то после выполнения команды из семейства LOOP (и передачи управления на метку цикла), ECX станет 0xFFFFFFFF, что (скорее всего) приведет к ошибочной работе программы.9Для компактной записи проверки ECX на 0 следует воспользоваться командой JECXZ,позволяющей «перепрыгнуть» цикл, когда ECX равен 0.
Команда JCXZ работает аналогично, но с регистром CX.Еще одной особенностью является то, что условный переход в командахLOOP/LOOPcc может быть только в пределах [–128, 127] байтов от текущей позиции вкоде. Закодировать цикл с достаточно объемным телом не получится, т. к. ассемблер не сможет построить код для инструкции, выполняющей переход на слишкомудаленную метку.Таблица 1. Описание команд аппаратной поддержки цикла.КомандаОписаниеJCXZ/JECXZПереход выполняется, если значение регистра CX/ECX равно нулю.LOOPПереход выполняется, если значение регистра ECX не равно нулю.LOOPZ/LOOPEПереход выполняется, если значение регистра ECX не равно нулю ифлаг ZF установлен.LOOPNZ/LOOPNEПереход выполняется, если значение регистра ECX не равно нулю ифлаг ZF сброшен.Пример 1-2 Аппаратная поддержка циклаВ статическом буфере памяти B хранятся 32-х разрядные числа. Длина буфера (вэлементах) задана в регистре ECX, требуется найти в буфере B все нечетные числа.РешениеВажной особенностью применения команды LOOP является то, что счетчиком циклаобязательно становится регистр ECX, его нельзя использовать для хранения какихлибо иных данных.
Помимо того, при использовании команды LOOP счетчик циклане увеличивается от нуля до некоторого порогового значения, а наоборот, начинаяс порогового значения, уменьшается до единицы, что требует аккуратности при использовании ECX в вычислении индексных выражений. Ниже приведено решение,учитывающее эти особенности.moveax, 0jecxz .le; (1); (2).l:btadcloop.le:10dword [B + 4*ecx -4], 0eax, 0.l; (3); (4); (5)Регистр EAX используем для накопления числа нечетных элементов.
Во второй команде происходит проверка длины массива, если длина нулевая – сразу переходим на выход из цикла. В третьей команде проверятся нулевой бит элемента массива.Поскольку порядок обхода элементов массива не важен, непосредственно используем ECX в вычислении индексного выражения. Умножаем счетчик на размер типаи дополнительно вычитаем из адреса 4, т. к. значения счетчика сдвинуты на единицу относительно индексов массива. Первая итерация цикла будет выполняться сECX = N, где N – длина массива, а обращение к массиву должно быть B[N-1].
Последняя итерация происходит с ECX = 1, но обращаться необходимо по адресу B +0.Выполнившаяся команда BT поместит последний бит числа во флагследующей, четвертой команде будет добавлен к регистру EAX.CF,Последняя, пятая, команда будет возвращать управление на меткутело цикла не выполнится с ECX = 1..lкоторый вдо тех пор,Многомерный массивПри интерпретации объявления двумерного массива следует помнить о приоритете операций: с именем переменной ассоциируются ближайшие справа квадратныескобки, остальные размерности следует связывать типом элемента многомерногомассива.При объявлении переменной int m[20][14] в памяти необходимо последовательнои без промежутков разместить 20 переменных, имеющих тип int[14].
Если переменная статическая, в ассемблерной программе метка m будет использоваться дляобращения к началу этой памяти (в точности, как и в случае с одномерными массивами). Таким образом, в языке Си размещение двумерного массива в памяти происходит по строкам, поскольку первый индекс принято считать номером строки, авторой – номером столбца.Такая интерпретация позволяет использовать правила умолчания, когда размермассива неизвестен: например, допустимо объявление int q[][14], но недопустимо int qq[14][]. Правила умолчания предписывают в отсутствии размера создатьмассив из одного элемента. В первом случае массив q состоит из одного элементаполного типа int[14], а во втором тип неполон.
Создать массив qq из 14 элементовнеполного типа (его размер неизвестен) не получится.11При обращении к элементу матрицы адрес элемента определяется как сумма начального адреса массива и смещений по первому и второму индексам. Как и в случае с одномерным массивом смещение получается умножением значения индексного выражения на размер соответствующего типа. Следовательно, при подсчетесмещения, порождаемого первым индексом размер необходимо определять длятипа T[M].Итого, для обращения к элементу matrix[i][j] матрицывычислить адресный (базовый) адрес переменной matrix.T matrix[N][M]необходимо, где– началь-Пример 1-3 Матрица целых чиселНапишите фрагмент ассемблерной программы, в которой:1)в статической памяти выделено пространство для матрицы 32-х разрядныхцелых чисел размером N*N,2)выполняется поиск максимального по модулю элемента главной диагонали,3)найденное число печатается на стандартный вывод.РешениеДля размещения двумерного массива T array[N][N] необходимо выделить непрерывный блок памяти длиной в sizeof(T)*N*N байтов.
Поскольку в условии ничего неговорится о начальных значениях элементов массива, матрица будет размещена всегменте .bss.Ввод данных во фрагменте пропущен. Для вычислений используются три регистра:EAX – для хранения максимального модуля, ECX – для извлечения элементов массива, EDX – для работы с текущим элементом.Загрузка очередного элемента соответствует обращению к элементу Си-массива сдвойным индексным выражением.int a[N][N];a[i][i];Двумерный массив размещен в памяти построчно: начиная с базового адреса идетпервая строка (с индексом 0), затем вторая и т.
д. Между строками нет промежутков, внутри строки элементы также идут без промежутков, поскольку строка – одномерный массив целых чисел. Обращение к каждой последующей строке требуетдобавления к базовому адресу массива смещения величиной в размер строки.12Строка – массив из N элементов типа int, его размер – 4*N байтов. Обращение к последующему элементу в столбце – требует добавления 4 байтов. На каждой итерации происходит сдвиг на одну строку и один столбец, что означает дополнительное смещение от базового адреса на 4*(N+1) байтов. Это приращение происходит вконце цикла, после чего проверяется, не вышло ли суммарное смещение от началамассива на его пределы.section .bssN equ …; определяем константу N с некоторым значениемa resd (N * N) ; выделяем N*N элементов по 4 байтаsection .textglobal CMAINCMAIN:; ввод матрицыmoveax, 0movecx, 0.l:movcmpjgeneg.pos:cmpcmovgaddcmpjl;;;;В регистре eax – максимальное по модулюзначение диагональных элементовecx – счетчик итераций и заодно смещение отначала блока памяти; метка – начало тела циклаedx, dword [a + ecx] ; Загружаем очередной диагональный элементedx, 0; Сравниваем его с 0.pos; и если он меньше edx; меняем его знакedx,eax,ecx,ecx,.leaxedx4 * (N + 1)4 * (N * N);;;;;Сравниваем текущий модуль с максимальным иесли он больше – пересылаем его в eaxУвеличиваем смещение на одну строку и одинстолбец.
Сравниваем текущее смещение сграницей выделенной памяти.PRINT_DEC 4, eaxNEWLINExoreax, eaxretПример 1-4 Транспонирование матрицыДана матрица целых чисел: static int A[M][N]. Требуется написать фрагмент программы, транспонирующий эту матрицу с запоминанием результата в другой матрице: static int B[N][M].РешениеНа языке Си запись требуемого алгоритма имеет следующий вид.13static int A[M][N];static int B[N][M];for (int i = 0; i < M; i++) {for (int j = 0; j < N; j++) {B[j][i] = A[i][j];}}Переводим построенный код на язык ассемблера.